From 4510141ce5584a6d797304f391f0076803e9df48 Mon Sep 17 00:00:00 2001 From: Mitchell M Date: Thu, 25 Dec 2025 14:07:50 -0600 Subject: [PATCH] move generation --- tak_bot/src/main.rs | 13 +++- tak_lib/Cargo.toml | 1 + tak_lib/src/engine.rs | 135 ++++++++++++++++++++++++++----------- tak_lib/src/lib.rs | 35 +++++++++- tak_lib/src/piece.rs | 2 +- tak_lib/src/test_engine.rs | 6 +- 6 files changed, 145 insertions(+), 47 deletions(-) diff --git a/tak_bot/src/main.rs b/tak_bot/src/main.rs index e7a11a9..b1b1620 100644 --- a/tak_bot/src/main.rs +++ b/tak_bot/src/main.rs @@ -1,3 +1,14 @@ +use std::time::Instant; + +use tak_lib::Board5x5; + fn main() { - println!("Hello, world!"); + let board = Board5x5::new(); + // for b_move in board.moves_iter() { + // println!("{:?}", b_move); + // } + let before = Instant::now(); + let moves: Vec<_> = board.moves_iter().collect(); + let dur = before.elapsed(); + println!("{:?}", dur); } diff --git a/tak_lib/Cargo.toml b/tak_lib/Cargo.toml index 14c98bd..0d86370 100644 --- a/tak_lib/Cargo.toml +++ b/tak_lib/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" [dev-dependencies] pretty_assertions = "1.4.1" + diff --git a/tak_lib/src/engine.rs b/tak_lib/src/engine.rs index 1a4efa7..a1cc508 100644 --- a/tak_lib/src/engine.rs +++ b/tak_lib/src/engine.rs @@ -4,77 +4,130 @@ use crate::{ tile::Tile, }; +pub struct BoardIter<'b, const N: usize> { + /// The board we're iterating over. + board: &'b Board, + /// The most recent coordinate that we have finished with. + coord: (u8, u8), + /// The remaining move buffer. + /// + /// This contains all the moves that havent been returned yet, + /// and are in the tiles <= `coord`. + residual: Vec>, +} + impl Board { - /// Gets an iterator for the moves that can be made by - /// the active player on this board. - pub fn moves(&self) -> impl Iterator> { - let to_move = self.to_move; - let tiles = &self.tiles; - let can_place_caps = if Self::N_CAPS == 0 { - false - } else { - let caps_on_board = self - .tiles - .iter() - .flat_map(|col| col.iter()) - .filter(|t| { - let piece = t.top_piece(); - piece.color_opt() == Some(to_move) && piece.piece() == Piece::Caps - }) - .count() as u8; - caps_on_board < Self::N_CAPS - }; + /// Gets the possible moves for the given tile + pub fn get_moves_for_tile(&self, x: u8, y: u8, buf: &mut Vec>) { + let to_move = self.to_move(); + let can_place_caps = self.caps_left[to_move as usize] != 0; + let top_piece = self.tiles[x as usize][y as usize].top_piece(); + if top_piece == ColoredPiece::None { + // possible moves are place moves + get_placement_moves(x, y, can_place_caps, buf) + } else if top_piece.color() == to_move { + get_movement_moves(x, y, &self.tiles, buf) + } + } - let chords = (0..(N as u8)).flat_map(|x| (0..(N as u8)).map(move |y| (x, y))); + pub fn moves_iter<'b>(&'b self) -> BoardIter<'b, N> { + let mut residual = Vec::new(); + self.get_moves_for_tile(0, 0, &mut residual); + BoardIter { + board: self, + coord: (0, 0), + residual, + } + } - chords.flat_map(move |(x, y)| { - let top_piece = tiles[x as usize][y as usize].top_piece(); - if top_piece == ColoredPiece::None { - // possible moves are place moves - get_placement_moves(x, y, can_place_caps) - } else if top_piece.color() == to_move { - get_movement_moves(x, y, tiles) - } else { - Vec::with_capacity(0) + // TODO: update result + pub fn check_move(&self, m: Move) -> Result<(), ()> { + let _ = m; + todo!() + } + + pub fn unchecked_apply_move(mut self, m: Move) -> Self { + match m { + Move::Place { piece, at } => { + self.tiles[at.0 as usize][at.1 as usize] + .set_top_piece(ColoredPiece::new(self.to_move(), piece)); + self } - }) + Move::Move { + from, + count, + dir, + drop, + } => { + let from_tile = self.tiles[from.0 as usize][from.1 as usize]; + self + } + } } } -fn get_placement_moves(x: u8, y: u8, can_place_caps: bool) -> Vec> { - let mut moves = Vec::with_capacity(2 + can_place_caps as usize); - moves.push(Move::Place { +impl<'b, const N: usize> Iterator for BoardIter<'b, N> { + type Item = Move; + + fn next(&mut self) -> Option { + if let Some(v) = self.residual.pop() { + return Some(v); + } + + if self.coord == (N as u8 - 1, N as u8 - 1) { + return None; + } + + // increment the coord + self.coord.1 += 1; + self.coord.0 += self.coord.1 / N as u8; + self.coord.1 %= N as u8; + + self.board + .get_moves_for_tile(self.coord.0, self.coord.1, &mut self.residual); + + self.next() + } +} + +/// Gets the possible placement moves on (`x`, `y`). +/// +/// This does no checks on the tile, it just adds the moves. +fn get_placement_moves(x: u8, y: u8, can_place_caps: bool, buf: &mut Vec>) { + buf.push(Move::Place { piece: Piece::Flat, at: (x, y), }); - moves.push(Move::Place { + buf.push(Move::Place { piece: Piece::Wall, at: (x, y), }); if can_place_caps { - moves.push(Move::Place { + buf.push(Move::Place { piece: Piece::Caps, at: (x, y), }); } - moves } +/// Gets the possible moves by moving from (`x`, `y`). +/// +/// This function does not check the colors of anything, so the caller must make sure +/// the active player owns the piece on this tile, if you want to get only valid moves +/// for the active player. pub(crate) fn get_movement_moves( x: u8, y: u8, tiles: &[[Tile; N]; N], -) -> Vec> { - let mut moves = Vec::new(); + buf: &mut Vec>, +) { let tile = tiles[x as usize][y as usize]; let is_caps = tile.top_piece().piece() == Piece::Caps; let max_grab = u8::min(N as u8, tile.stack_len() + 1); for dir in Direction::ALL { - get_moves_in_dir(x, y, dir, tiles, &mut moves, is_caps, max_grab); + get_moves_in_dir(x, y, dir, tiles, buf, is_caps, max_grab); } - - moves } /// Gets the possible moves moving from (`x`, `y`) in the given direction `dir`. diff --git a/tak_lib/src/lib.rs b/tak_lib/src/lib.rs index 8a5aec2..5b52171 100644 --- a/tak_lib/src/lib.rs +++ b/tak_lib/src/lib.rs @@ -57,7 +57,12 @@ impl Direction { #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Board { - to_move: Color, + half_moves: u32, + /// The amount of stones that white and black have left. + stones_left: [u8; 2], + /// The amount of capstones that white and black have left. + caps_left: [u8; 2], + play_state: PlayState, tiles: [[Tile; N]; N], } pub type Board3x3 = Board<3>; @@ -86,12 +91,26 @@ impl Board { _ => panic!("N must be 3, 4, 5, 6, or 8"), }; + /// Constructs a new `Board` in the starting state. pub fn new() -> Self { Board { - to_move: Color::White, + half_moves: 0, + stones_left: [Self::N_STONES, Self::N_STONES], + caps_left: [Self::N_CAPS, Self::N_CAPS], + play_state: PlayState::WhitePlaceBlack, tiles: [[Tile::EMPTY; N]; N], } } + + /// Gets the player who is to move + #[inline] + pub fn to_move(&self) -> Color { + if self.half_moves % 2 == 0 { + Color::White + } else { + Color::Black + } + } } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] @@ -107,3 +126,15 @@ pub enum Move { drop: [u8; N], }, } + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum PlayState { + Normal, + WhitePlaceBlack, + BlackPlaceWhite, + WhiteWinRoad, + WhiteWinFlat, + BlackWinRoad, + BlackWinFlat, + Tie, +} diff --git a/tak_lib/src/piece.rs b/tak_lib/src/piece.rs index 348eed5..cb63d5c 100644 --- a/tak_lib/src/piece.rs +++ b/tak_lib/src/piece.rs @@ -41,7 +41,7 @@ pub enum ColoredPiece { impl ColoredPiece { /// Constructs a new `ColoredPiece`. - pub fn new(piece: Piece, color: Color) -> Self { + pub fn new(color: Color, piece: Piece) -> Self { let mut piece_val = (piece as u8) | ((color as u8) << 2); if piece_val == 4 { piece_val = 0; diff --git a/tak_lib/src/test_engine.rs b/tak_lib/src/test_engine.rs index e3139b4..f90b69b 100644 --- a/tak_lib/src/test_engine.rs +++ b/tak_lib/src/test_engine.rs @@ -290,7 +290,8 @@ fn test_get_moves() { ColoredPiece::WhiteFlat, &[Color::White, Color::Black, Color::White, Color::Black], ); - let actual = get_movement_moves(from.0, from.1, &tiles); + let mut actual = Vec::new(); + get_movement_moves(from.0, from.1, &tiles, &mut actual); const LEN1_DROPS: [(u8, [u8; 5]); 5] = [ (1, [1, 0, 0, 0, 0]), @@ -417,7 +418,8 @@ fn test_get_caps_moves() { tiles[1][0] = Tile::new(ColoredPiece::WhiteWall, &[]); tiles[1][3] = Tile::new(ColoredPiece::BlackWall, &[]); tiles[4][2] = Tile::new(ColoredPiece::BlackWall, &[]); - let actual = get_movement_moves(from.0, from.1, &tiles); + let mut actual = Vec::new(); + get_movement_moves(from.0, from.1, &tiles, &mut actual); const LEN1_DROPS: [(u8, [u8; 5]); 5] = [ (1, [1, 0, 0, 0, 0]),