Compare commits

...
4 Commits
Author SHA1 Message Date
mitchell 4510141ce5 move generation 2025-12-25 14:07:50 -06:00
mitchell 047c5fc02e move engine work 2025-12-24 10:59:43 -06:00
mitchell 96fb485c87 engine work 2025-12-22 11:32:20 -06:00
mitchell 13aa9ffffe engine WIP 2025-12-21 20:15:59 -06:00
8 changed files with 999 additions and 5 deletions
Generated
+25
View File
@@ -2,6 +2,22 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "tak_bot"
version = "0.1.0"
@@ -12,6 +28,9 @@ dependencies = [
[[package]]
name = "tak_lib"
version = "0.1.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "tak_server"
@@ -20,3 +39,9 @@ version = "0.1.0"
[[package]]
name = "tak_visualizer"
version = "0.1.0"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+12 -1
View File
@@ -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);
}
+4
View File
@@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
[dev-dependencies]
pretty_assertions = "1.4.1"
+244
View File
@@ -0,0 +1,244 @@
use crate::{
Board, Direction, Move,
piece::{ColoredPiece, Piece},
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> {
/// Gets the possible moves for the given tile
pub fn get_moves_for_tile(&self, x: u8, y: u8, buf: &mut Vec<Move<N>>) {
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)
}
}
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,
}
}
// TODO: update result
pub fn check_move(&self, m: Move<N>) -> Result<(), ()> {
let _ = m;
todo!()
}
pub fn unchecked_apply_move(mut self, m: Move<N>) -> 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
}
}
}
}
impl<'b, const N: usize> Iterator for BoardIter<'b, N> {
type Item = Move<N>;
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,
at: (x, y),
});
buf.push(Move::Place {
piece: Piece::Wall,
at: (x, y),
});
if can_place_caps {
buf.push(Move::Place {
piece: Piece::Caps,
at: (x, y),
});
}
}
/// 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>(
x: u8,
y: u8,
tiles: &[[Tile; N]; N],
buf: &mut Vec<Move<N>>,
) {
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, buf, is_caps, max_grab);
}
}
/// Gets the possible moves moving from (`x`, `y`) in the given direction `dir`.
///
/// 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.
fn get_moves_in_dir<const N: usize>(
x: u8,
y: u8,
dir: Direction,
tiles: &[[Tile; N]; N],
moves: &mut Vec<Move<N>>,
is_caps: bool,
max_grab: u8,
) {
// first, get the max distance we can go
let mut max_dist = max_grab.min(dir.dist_to_edge::<N>(x, y));
let mut end_w_capstone = false;
let (dx, dy) = dir.delta();
// step through the tiles to see if out movement gets blocked
for i in 1..=max_dist {
let xi = x as isize + (dx * i as isize);
let yi = y as isize + (dy * i as isize);
// we already clamp `max_distance` to the board edge, so indexing should be safe
let next_piece = tiles[xi as usize][yi as usize].top_piece().piece();
// check for pieces that will block us
if next_piece == Piece::Caps {
max_dist = i - 1;
break;
} else if next_piece == Piece::Wall {
// if we have a capstone, we can smash the wall
if is_caps {
end_w_capstone = true;
max_dist = i;
} else {
max_dist = i - 1;
}
break;
}
}
if max_dist == 0 {
return;
}
// now that we have our max distance, get all the possible moves
let mut insert_fn = |drops: [u8; N]| {
moves.push(Move::Move {
from: (x, y),
count: drops.iter().copied().sum::<u8>(),
dir,
drop: drops,
});
};
for grab in 1..=max_grab {
let mut buf = [0; N];
get_drop_amounts(&mut buf, 0, &mut insert_fn, grab, max_dist, end_w_capstone);
}
}
/// Gets the possible amounts of pieces to drop at each tile.
///
/// `pieces` is the number of pieces that are picked up.
/// `len` is the max length that the stack can be moved.
///
/// If the top piece of the stack is a capstone, and the end of the line
/// is a wall, `end_w_capstone` should be `true` and `len` should include
/// that tile.
pub(crate) fn get_drop_amounts<const N: usize>(
// shared state
buf: &mut [u8; N],
i: usize,
insert_fn: &mut impl FnMut([u8; N]),
// recursive parameters
pieces: u8,
len: u8,
end_w_capstone: bool,
) {
debug_assert!(len > 0 || pieces == 0);
// base cases
if pieces == 0 {
insert_fn(buf.clone());
return;
}
if pieces == 1 {
buf[i] = 1;
insert_fn(buf.clone());
buf[i] = 0;
return;
}
if len == 1 {
if end_w_capstone {
if pieces == 1 {
buf[i] = 1;
insert_fn(buf.clone());
buf[i] = 0;
}
} else {
buf[i] = pieces;
insert_fn(buf.clone());
buf[i] = 0;
}
return;
}
// recursive case
for n in 1..=pieces {
buf[i] = n;
get_drop_amounts(buf, i + 1, insert_fn, pieces - n, len - 1, end_w_capstone);
}
buf[i] = 0;
}
+132 -2
View File
@@ -1,10 +1,140 @@
pub mod engine;
pub mod piece;
#[cfg(test)]
mod test_engine;
pub mod tile;
use crate::{piece::Color, tile::Tile};
use crate::{
piece::{Color, Piece},
tile::Tile,
};
use std::fmt::Display;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Direction {
East,
North,
West,
South,
}
impl Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Direction::East => write!(f, "e"),
Direction::North => write!(f, "n"),
Direction::West => write!(f, "w"),
Direction::South => write!(f, "s"),
}
}
}
impl Direction {
pub const ALL: [Direction; 4] = [
Direction::East,
Direction::North,
Direction::West,
Direction::South,
];
pub fn delta(&self) -> (isize, isize) {
match self {
Direction::East => (1, 0),
Direction::North => (0, 1),
Direction::West => (-1, 0),
Direction::South => (0, -1),
}
}
#[inline]
pub fn dist_to_edge<const N: usize>(&self, x: u8, y: u8) -> u8 {
match self {
Direction::East => N as u8 - x - 1,
Direction::North => N as u8 - y - 1,
Direction::West => x,
Direction::South => y,
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
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],
}
pub type Board3x3 = Board<3>;
pub type Board4x4 = Board<4>;
pub type Board5x5 = Board<5>;
pub type Board6x6 = Board<6>;
pub type Board8x8 = Board<8>;
impl<const N: usize> Board<N> {
/// The number of capstones in each players' pouches.
pub const N_STONES: u8 = match N {
3 => 10,
4 => 15,
5 => 21,
6 => 30,
8 => 50,
_ => panic!("N must be 3, 4, 5, 6, or 8"),
};
/// The number of capstones in the players' pouches.
pub const N_CAPS: u8 = match N {
3 => 0,
4 => 0,
5 => 1,
6 => 1,
8 => 2,
_ => panic!("N must be 3, 4, 5, 6, or 8"),
};
/// Constructs a new `Board` in the starting state.
pub fn new() -> Self {
Board {
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)]
pub enum Move<const N: usize> {
Place {
piece: Piece,
at: (u8, u8),
},
Move {
from: (u8, u8),
count: u8,
dir: Direction,
drop: [u8; N],
},
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum PlayState {
Normal,
WhitePlaceBlack,
BlackPlaceWhite,
WhiteWinRoad,
WhiteWinFlat,
BlackWinRoad,
BlackWinFlat,
Tie,
}
+1 -1
View File
@@ -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;
+568
View File
@@ -0,0 +1,568 @@
use crate::{Direction, Move, engine::get_movement_moves, piece::ColoredPiece, tile::Tile};
#[cfg(test)]
fn expect_get_drop_ammounts<const N: usize>(
pieces: u8,
len: u8,
end_w_capstone: bool,
expected_drop_list: Vec<[u8; N]>,
) {
use crate::engine::get_drop_amounts;
let mut buf = [0; N];
let mut drop_list = Vec::new();
let mut insert_fn = |drops: [u8; N]| {
drop_list.push(drops);
};
get_drop_amounts(&mut buf, 0, &mut insert_fn, pieces, len, end_w_capstone);
if let Err(err) = assert_uo_list_eq(expected_drop_list, drop_list) {
panic!(
"incorrect drop list! (pieces={}, len={}, end_w_capstone={})\n{}",
pieces, len, end_w_capstone, err
);
}
}
#[test]
fn test_full_len_drop_amounts() {
expect_get_drop_ammounts(1, 1, false, vec![[1]]);
expect_get_drop_ammounts(1, 1, false, vec![[1, 0]]);
expect_get_drop_ammounts(1, 1, false, vec![[1, 0, 0]]);
expect_get_drop_ammounts(2, 2, false, vec![[1, 1, 0, 0, 0], [2, 0, 0, 0, 0]]);
expect_get_drop_ammounts(
3,
3,
false,
vec![
[1, 1, 1, 0, 0],
[1, 2, 0, 0, 0],
[2, 1, 0, 0, 0],
[3, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
4,
4,
false,
vec![
[1, 1, 1, 1, 0],
[1, 1, 2, 0, 0],
[1, 2, 1, 0, 0],
[1, 3, 0, 0, 0],
[2, 1, 1, 0, 0],
[2, 2, 0, 0, 0],
[3, 1, 0, 0, 0],
[4, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
5,
false,
vec![
[1, 1, 1, 1, 1],
[1, 1, 1, 2, 0],
[1, 1, 2, 1, 0],
[1, 1, 3, 0, 0],
[1, 2, 1, 1, 0],
[1, 2, 2, 0, 0],
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 1, 1, 1, 0],
[2, 1, 2, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
}
#[test]
fn test_truncated_drop_amounts() {
expect_get_drop_ammounts(
3,
2,
false,
vec![[1, 2, 0, 0, 0], [2, 1, 0, 0, 0], [3, 0, 0, 0, 0]],
);
expect_get_drop_ammounts(3, 1, false, vec![[3, 0, 0, 0, 0]]);
expect_get_drop_ammounts(
4,
3,
false,
vec![
[1, 1, 2, 0, 0],
[1, 2, 1, 0, 0],
[1, 3, 0, 0, 0],
[2, 1, 1, 0, 0],
[2, 2, 0, 0, 0],
[3, 1, 0, 0, 0],
[4, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
4,
2,
false,
vec![
[1, 3, 0, 0, 0],
[2, 2, 0, 0, 0],
[3, 1, 0, 0, 0],
[4, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
4,
false,
vec![
[1, 1, 1, 2, 0],
[1, 1, 2, 1, 0],
[1, 1, 3, 0, 0],
[1, 2, 1, 1, 0],
[1, 2, 2, 0, 0],
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 1, 1, 1, 0],
[2, 1, 2, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
3,
false,
vec![
[1, 1, 3, 0, 0],
[1, 2, 2, 0, 0],
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 1, 2, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
2,
false,
vec![
[1, 4, 0, 0, 0],
[2, 3, 0, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(5, 1, false, vec![[5, 0, 0, 0, 0]]);
}
#[test]
fn test_capstone_drops() {
expect_get_drop_ammounts(1, 1, true, vec![[1, 0]]);
expect_get_drop_ammounts(2, 2, true, vec![[1, 1, 0, 0, 0], [2, 0, 0, 0, 0]]);
expect_get_drop_ammounts::<5>(2, 1, true, vec![]);
expect_get_drop_ammounts(
3,
3,
true,
vec![
[1, 1, 1, 0, 0],
[1, 2, 0, 0, 0],
[2, 1, 0, 0, 0],
[3, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(3, 2, true, vec![[2, 1, 0, 0, 0], [3, 0, 0, 0, 0]]);
expect_get_drop_ammounts(
4,
4,
true,
vec![
[1, 1, 1, 1, 0],
[1, 1, 2, 0, 0],
[1, 2, 1, 0, 0],
[1, 3, 0, 0, 0],
[2, 1, 1, 0, 0],
[2, 2, 0, 0, 0],
[3, 1, 0, 0, 0],
[4, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
4,
3,
true,
vec![
[1, 2, 1, 0, 0],
[1, 3, 0, 0, 0],
[2, 1, 1, 0, 0],
[2, 2, 0, 0, 0],
[3, 1, 0, 0, 0],
[4, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(4, 2, true, vec![[3, 1, 0, 0, 0], [4, 0, 0, 0, 0]]);
expect_get_drop_ammounts::<5>(4, 1, true, vec![]);
expect_get_drop_ammounts(
5,
5,
true,
vec![
[1, 1, 1, 1, 1],
[1, 1, 1, 2, 0],
[1, 1, 2, 1, 0],
[1, 1, 3, 0, 0],
[1, 2, 1, 1, 0],
[1, 2, 2, 0, 0],
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 1, 1, 1, 0],
[2, 1, 2, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
4,
true,
vec![
[1, 1, 2, 1, 0],
[1, 1, 3, 0, 0],
[1, 2, 1, 1, 0],
[1, 2, 2, 0, 0],
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 1, 1, 1, 0],
[2, 1, 2, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(
5,
3,
true,
vec![
[1, 3, 1, 0, 0],
[1, 4, 0, 0, 0],
[2, 2, 1, 0, 0],
[2, 3, 0, 0, 0],
[3, 1, 1, 0, 0],
[3, 2, 0, 0, 0],
[4, 1, 0, 0, 0],
[5, 0, 0, 0, 0],
],
);
expect_get_drop_ammounts(5, 2, true, vec![[4, 1, 0, 0, 0], [5, 0, 0, 0, 0]]);
expect_get_drop_ammounts::<5>(5, 1, true, vec![]);
}
#[test]
fn test_get_moves() {
use crate::{Board5x5, Color};
let from = (1, 2);
let mut tiles = Board5x5::new().tiles;
tiles[from.0 as usize][from.1 as usize] = Tile::new(
ColoredPiece::WhiteFlat,
&[Color::White, Color::Black, Color::White, Color::Black],
);
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]),
(2, [2, 0, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
const LEN2_DROPS: [(u8, [u8; 5]); 15] = [
// 1
(1, [1, 0, 0, 0, 0]),
// 2
(2, [1, 1, 0, 0, 0]),
(2, [2, 0, 0, 0, 0]),
// 3
(3, [1, 2, 0, 0, 0]),
(3, [2, 1, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
// 4
(4, [1, 3, 0, 0, 0]),
(4, [2, 2, 0, 0, 0]),
(4, [3, 1, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
// 5
(5, [1, 4, 0, 0, 0]),
(5, [2, 3, 0, 0, 0]),
(5, [3, 2, 0, 0, 0]),
(5, [4, 1, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
const LEN3_DROPS: [(u8, [u8; 5]); 25] = [
// 1
(1, [1, 0, 0, 0, 0]),
// 2
(2, [1, 1, 0, 0, 0]),
(2, [2, 0, 0, 0, 0]),
// 3
(3, [1, 1, 1, 0, 0]),
(3, [1, 2, 0, 0, 0]),
(3, [2, 1, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
// 4
(4, [1, 1, 2, 0, 0]),
(4, [1, 2, 1, 0, 0]),
(4, [1, 3, 0, 0, 0]),
(4, [2, 1, 1, 0, 0]),
(4, [2, 2, 0, 0, 0]),
(4, [3, 1, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
// 5
(5, [1, 1, 3, 0, 0]),
(5, [1, 2, 2, 0, 0]),
(5, [1, 3, 1, 0, 0]),
(5, [1, 4, 0, 0, 0]),
(5, [2, 1, 2, 0, 0]),
(5, [2, 2, 1, 0, 0]),
(5, [2, 3, 0, 0, 0]),
(5, [3, 1, 1, 0, 0]),
(5, [3, 2, 0, 0, 0]),
(5, [4, 1, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
let east_list: Vec<_> = LEN3_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::East,
drop,
})
.collect();
let north_list: Vec<_> = LEN2_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::North,
drop,
})
.collect();
let west_list: Vec<_> = LEN1_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::West,
drop,
})
.collect();
let south_list: Vec<_> = LEN2_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::South,
drop,
})
.collect();
let expected: Vec<_> = east_list
.into_iter()
.chain(north_list.into_iter())
.chain(west_list.into_iter())
.chain(south_list.into_iter())
.collect();
let result = assert_uo_list_eq(expected, actual);
if let Err(err) = result {
panic!("incorrect move list!\n{}", err);
}
}
#[test]
fn test_get_caps_moves() {
use crate::{Board5x5, Color};
let from = (1, 2);
let mut tiles = Board5x5::new().tiles;
tiles[from.0 as usize][from.1 as usize] = Tile::new(
ColoredPiece::WhiteCaps,
&[Color::White, Color::Black, Color::White, Color::Black],
);
tiles[1][0] = Tile::new(ColoredPiece::WhiteWall, &[]);
tiles[1][3] = Tile::new(ColoredPiece::BlackWall, &[]);
tiles[4][2] = Tile::new(ColoredPiece::BlackWall, &[]);
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]),
(2, [2, 0, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
const LEN1_CAPS_DROPS: [(u8, [u8; 5]); 1] = [(1, [1, 0, 0, 0, 0])];
const LEN2_CAPS_DROPS: [(u8, [u8; 5]); 9] = [
// 1
(1, [1, 0, 0, 0, 0]),
// 2
(2, [1, 1, 0, 0, 0]),
(2, [2, 0, 0, 0, 0]),
// 3
(3, [2, 1, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
// 4
(4, [3, 1, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
// 5
(5, [4, 1, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
const LEN3_CAPS_DROPS: [(u8, [u8; 5]); 21] = [
// 1
(1, [1, 0, 0, 0, 0]),
// 2
(2, [1, 1, 0, 0, 0]),
(2, [2, 0, 0, 0, 0]),
// 3
(3, [1, 1, 1, 0, 0]),
(3, [1, 2, 0, 0, 0]),
(3, [2, 1, 0, 0, 0]),
(3, [3, 0, 0, 0, 0]),
// 4
(4, [1, 2, 1, 0, 0]),
(4, [1, 3, 0, 0, 0]),
(4, [2, 1, 1, 0, 0]),
(4, [2, 2, 0, 0, 0]),
(4, [3, 1, 0, 0, 0]),
(4, [4, 0, 0, 0, 0]),
// 5
(5, [1, 3, 1, 0, 0]),
(5, [1, 4, 0, 0, 0]),
(5, [2, 2, 1, 0, 0]),
(5, [2, 3, 0, 0, 0]),
(5, [3, 1, 1, 0, 0]),
(5, [3, 2, 0, 0, 0]),
(5, [4, 1, 0, 0, 0]),
(5, [5, 0, 0, 0, 0]),
];
let east_list: Vec<_> = LEN3_CAPS_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::East,
drop,
})
.collect();
let north_list: Vec<_> = LEN1_CAPS_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::North,
drop,
})
.collect();
let west_list: Vec<_> = LEN1_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::West,
drop,
})
.collect();
let south_list: Vec<_> = LEN2_CAPS_DROPS
.into_iter()
.map(|(count, drop)| Move::Move {
from,
count,
dir: Direction::South,
drop,
})
.collect();
let expected: Vec<_> = east_list
.into_iter()
.chain(north_list.into_iter())
.chain(west_list.into_iter())
.chain(south_list.into_iter())
.collect();
let result = assert_uo_list_eq(expected, actual);
if let Err(err) = result {
panic!("incorrect move list!\n{}", err);
}
}
fn assert_uo_list_eq<T: Clone + Eq + std::hash::Hash + std::fmt::Debug>(
expected: Vec<T>,
actual: Vec<T>,
) -> Result<(), String> {
use std::collections::HashSet;
let actual_set: HashSet<T> = actual.iter().cloned().collect();
let expected_set: HashSet<T> = expected.iter().cloned().collect();
if actual_set != expected_set {
Err(list_diff(expected, actual, expected_set, actual_set))
} else {
Ok(())
}
}
fn list_diff<T: Clone + Eq + std::hash::Hash + std::fmt::Debug>(
expected: Vec<T>,
actual: Vec<T>,
expected_set: std::collections::HashSet<T>,
actual_set: std::collections::HashSet<T>,
) -> String {
let mut diff = String::new();
for entry in expected {
if actual_set.contains(&entry) {
diff += &format!("\t{:?}\t{:?}\n", entry, entry);
} else {
diff += &format!("\t{:?}\n", entry);
}
}
for entry in actual {
if expected_set.contains(&entry) {
continue;
}
let blank = format!("{:?}", entry)
.chars()
.map(|_| ' ')
.collect::<String>();
diff += &format!("\t{}\t{:?}\n", blank, entry);
}
diff
}
+13 -1
View File
@@ -1,6 +1,6 @@
use std::fmt::Debug;
use crate::piece::ColoredPiece;
use crate::piece::{Color, ColoredPiece};
/// A tile on the board.
///
@@ -41,6 +41,18 @@ impl Tile {
const STACK_OFFSET: u8 = 16;
const STACK_MASK: u128 = !0xFFFFu128;
pub fn new(top_piece: ColoredPiece, stack: &[Color]) -> Self {
let mut tile = Tile::EMPTY;
tile.set_top_piece(top_piece);
tile.set_stack_len(stack.len() as u8);
let mut stack_bits = 0u128;
for c in stack.iter().rev() {
stack_bits = (stack_bits << 1) | (*c) as u128
}
tile.set_stack(stack_bits);
tile
}
/// Gets the top piece.
pub fn top_piece(&self) -> ColoredPiece {
ColoredPiece::from_u8((self.0 & 0b111) as u8)