move generation

This commit is contained in:
Mitchell Marino 2025-12-25 14:07:50 -06:00
parent 047c5fc02e
commit 4510141ce5
6 changed files with 145 additions and 47 deletions

View File

@ -1,3 +1,14 @@
use std::time::Instant;
use tak_lib::Board5x5;
fn main() { 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);
} }

View File

@ -7,3 +7,4 @@ edition = "2024"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"

View File

@ -4,77 +4,130 @@ use crate::{
tile::Tile, tile::Tile,
}; };
pub struct BoardIter<'b, const N: usize> {
/// The board we're iterating over.
board: &'b Board<N>,
/// 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<Move<N>>,
}
impl<const N: usize> Board<N> { impl<const N: usize> Board<N> {
/// Gets an iterator for the moves that can be made by /// Gets the possible moves for the given tile
/// the active player on this board. pub fn get_moves_for_tile(&self, x: u8, y: u8, buf: &mut Vec<Move<N>>) {
pub fn moves(&self) -> impl Iterator<Item = Move<N>> { let to_move = self.to_move();
let to_move = self.to_move; let can_place_caps = self.caps_left[to_move as usize] != 0;
let tiles = &self.tiles; let top_piece = self.tiles[x as usize][y as usize].top_piece();
let can_place_caps = if Self::N_CAPS == 0 { if top_piece == ColoredPiece::None {
false // possible moves are place moves
} else { get_placement_moves(x, y, can_place_caps, buf)
let caps_on_board = self } else if top_piece.color() == to_move {
.tiles get_movement_moves(x, y, &self.tiles, buf)
.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
};
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)| { // TODO: update result
let top_piece = tiles[x as usize][y as usize].top_piece(); pub fn check_move(&self, m: Move<N>) -> Result<(), ()> {
if top_piece == ColoredPiece::None { let _ = m;
// possible moves are place moves todo!()
get_placement_moves(x, y, can_place_caps) }
} else if top_piece.color() == to_move {
get_movement_moves(x, y, tiles) pub fn unchecked_apply_move(mut self, m: Move<N>) -> Self {
} else { match m {
Vec::with_capacity(0) 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<const N: usize>(x: u8, y: u8, can_place_caps: bool) -> Vec<Move<N>> { impl<'b, const N: usize> Iterator for BoardIter<'b, N> {
let mut moves = Vec::with_capacity(2 + can_place_caps as usize); type Item = Move<N>;
moves.push(Move::Place {
fn next(&mut self) -> Option<Self::Item> {
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<const N: usize>(x: u8, y: u8, can_place_caps: bool, buf: &mut Vec<Move<N>>) {
buf.push(Move::Place {
piece: Piece::Flat, piece: Piece::Flat,
at: (x, y), at: (x, y),
}); });
moves.push(Move::Place { buf.push(Move::Place {
piece: Piece::Wall, piece: Piece::Wall,
at: (x, y), at: (x, y),
}); });
if can_place_caps { if can_place_caps {
moves.push(Move::Place { buf.push(Move::Place {
piece: Piece::Caps, piece: Piece::Caps,
at: (x, y), 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<const N: usize>( pub(crate) fn get_movement_moves<const N: usize>(
x: u8, x: u8,
y: u8, y: u8,
tiles: &[[Tile; N]; N], tiles: &[[Tile; N]; N],
) -> Vec<Move<N>> { buf: &mut Vec<Move<N>>,
let mut moves = Vec::new(); ) {
let tile = tiles[x as usize][y as usize]; let tile = tiles[x as usize][y as usize];
let is_caps = tile.top_piece().piece() == Piece::Caps; let is_caps = tile.top_piece().piece() == Piece::Caps;
let max_grab = u8::min(N as u8, tile.stack_len() + 1); let max_grab = u8::min(N as u8, tile.stack_len() + 1);
for dir in Direction::ALL { 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`. /// Gets the possible moves moving from (`x`, `y`) in the given direction `dir`.

View File

@ -57,7 +57,12 @@ impl Direction {
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Board<const N: usize> { pub struct Board<const N: usize> {
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], tiles: [[Tile; N]; N],
} }
pub type Board3x3 = Board<3>; pub type Board3x3 = Board<3>;
@ -86,12 +91,26 @@ impl<const N: usize> Board<N> {
_ => panic!("N must be 3, 4, 5, 6, or 8"), _ => panic!("N must be 3, 4, 5, 6, or 8"),
}; };
/// Constructs a new `Board` in the starting state.
pub fn new() -> Self { pub fn new() -> Self {
Board { 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], 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)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
@ -107,3 +126,15 @@ pub enum Move<const N: usize> {
drop: [u8; N], drop: [u8; N],
}, },
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum PlayState {
Normal,
WhitePlaceBlack,
BlackPlaceWhite,
WhiteWinRoad,
WhiteWinFlat,
BlackWinRoad,
BlackWinFlat,
Tie,
}

View File

@ -41,7 +41,7 @@ pub enum ColoredPiece {
impl ColoredPiece { impl ColoredPiece {
/// Constructs a new `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); let mut piece_val = (piece as u8) | ((color as u8) << 2);
if piece_val == 4 { if piece_val == 4 {
piece_val = 0; piece_val = 0;

View File

@ -290,7 +290,8 @@ fn test_get_moves() {
ColoredPiece::WhiteFlat, ColoredPiece::WhiteFlat,
&[Color::White, Color::Black, Color::White, Color::Black], &[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] = [ const LEN1_DROPS: [(u8, [u8; 5]); 5] = [
(1, [1, 0, 0, 0, 0]), (1, [1, 0, 0, 0, 0]),
@ -417,7 +418,8 @@ fn test_get_caps_moves() {
tiles[1][0] = Tile::new(ColoredPiece::WhiteWall, &[]); tiles[1][0] = Tile::new(ColoredPiece::WhiteWall, &[]);
tiles[1][3] = Tile::new(ColoredPiece::BlackWall, &[]); tiles[1][3] = Tile::new(ColoredPiece::BlackWall, &[]);
tiles[4][2] = 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] = [ const LEN1_DROPS: [(u8, [u8; 5]); 5] = [
(1, [1, 0, 0, 0, 0]), (1, [1, 0, 0, 0, 0]),