move engine work

This commit is contained in:
Mitchell Marino 2025-12-24 10:59:43 -06:00
parent 96fb485c87
commit 047c5fc02e
6 changed files with 736 additions and 211 deletions

25
Cargo.lock generated
View File

@ -2,6 +2,22 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "tak_bot" name = "tak_bot"
version = "0.1.0" version = "0.1.0"
@ -12,6 +28,9 @@ dependencies = [
[[package]] [[package]]
name = "tak_lib" name = "tak_lib"
version = "0.1.0" version = "0.1.0"
dependencies = [
"pretty_assertions",
]
[[package]] [[package]]
name = "tak_server" name = "tak_server"
@ -20,3 +39,9 @@ version = "0.1.0"
[[package]] [[package]]
name = "tak_visualizer" name = "tak_visualizer"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View File

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

View File

@ -1,9 +1,12 @@
use crate::{ use crate::{
Board, Move, Board, Direction, Move,
piece::{ColoredPiece, Piece}, piece::{ColoredPiece, Piece},
tile::Tile,
}; };
impl<const N: usize> Board<N> { impl<const N: usize> Board<N> {
/// Gets an iterator for the moves that can be made by
/// the active player on this board.
pub fn moves(&self) -> impl Iterator<Item = Move<N>> { pub fn moves(&self) -> impl Iterator<Item = Move<N>> {
let to_move = self.to_move; let to_move = self.to_move;
let tiles = &self.tiles; let tiles = &self.tiles;
@ -28,9 +31,9 @@ impl<const N: usize> Board<N> {
let top_piece = tiles[x as usize][y as usize].top_piece(); let top_piece = tiles[x as usize][y as usize].top_piece();
if top_piece == ColoredPiece::None { if top_piece == ColoredPiece::None {
// possible moves are place moves // possible moves are place moves
insert_placement_moves(x, y, can_place_caps) get_placement_moves(x, y, can_place_caps)
} else if top_piece.color() == to_move { } else if top_piece.color() == to_move {
get_movement_moves(x, y, tiles[x as usize][y as usize].stack_len()) get_movement_moves(x, y, tiles)
} else { } else {
Vec::with_capacity(0) Vec::with_capacity(0)
} }
@ -38,7 +41,7 @@ impl<const N: usize> Board<N> {
} }
} }
fn insert_placement_moves<const N: usize>(x: u8, y: u8, can_place_caps: bool) -> Vec<Move<N>> { fn get_placement_moves<const N: usize>(x: u8, y: u8, can_place_caps: bool) -> Vec<Move<N>> {
let mut moves = Vec::with_capacity(2 + can_place_caps as usize); let mut moves = Vec::with_capacity(2 + can_place_caps as usize);
moves.push(Move::Place { moves.push(Move::Place {
piece: Piece::Flat, piece: Piece::Flat,
@ -57,21 +60,92 @@ fn insert_placement_moves<const N: usize>(x: u8, y: u8, can_place_caps: bool) ->
moves moves
} }
fn get_movement_moves<const N: usize>(x: u8, y: u8, stack_len: u8) -> Vec<Move<N>> { pub(crate) fn get_movement_moves<const N: usize>(
let max_grab = u8::min(N as u8, stack_len + 1); x: u8,
y: u8,
tiles: &[[Tile; N]; N],
) -> Vec<Move<N>> {
let mut moves = Vec::new();
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);
// East for dir in Direction::ALL {
let max_dist_east = max_grab.min(N as u8 - x - 1); get_moves_in_dir(x, y, dir, tiles, &mut moves, is_caps, max_grab);
// North
let max_dist_north = max_grab.min(N as u8 - y - 1);
// West
let max_dist_west = max_grab.min(x);
// South
let max_dist_south = max_grab.min(y);
todo!()
} }
fn get_drop_amounts<const N: usize>( moves
}
/// 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 // shared state
buf: &mut [u8; N], buf: &mut [u8; N],
i: usize, i: usize,
@ -94,9 +168,17 @@ fn get_drop_amounts<const N: usize>(
return; return;
} }
if len == 1 { if len == 1 {
if end_w_capstone {
if pieces == 1 {
buf[i] = 1;
insert_fn(buf.clone());
buf[i] = 0;
}
} else {
buf[i] = pieces; buf[i] = pieces;
insert_fn(buf.clone()); insert_fn(buf.clone());
buf[i] = 0; buf[i] = 0;
}
return; return;
} }
@ -107,193 +189,3 @@ fn get_drop_amounts<const N: usize>(
} }
buf[i] = 0; buf[i] = 0;
} }
#[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 std::collections::HashSet;
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);
let expected_drop_set: HashSet<_> = expected_drop_list.iter().cloned().collect();
let drop_set: HashSet<_> = drop_list.iter().cloned().collect();
if drop_set.len() != drop_list.len() {
panic!("drop list had duplicate values");
}
if expected_drop_set != drop_set {
let mut err_str = String::new();
for entry in expected_drop_list {
if drop_set.contains(&entry) {
err_str += &format!("\t{:?} {:?}\n", entry, entry);
} else {
err_str += &format!("\t{:?}\n", entry);
}
}
for entry in drop_list {
if expected_drop_set.contains(&entry) {
continue;
}
let blank = format!("{:?}", entry)
.chars()
.map(|_| ' ')
.collect::<String>();
err_str += &format!("\t{} {:?}\n", blank, entry);
}
panic!("incorrect drop list!\n{}", err_str);
}
}
#[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]]);
}

View File

@ -1,5 +1,7 @@
pub mod engine; pub mod engine;
pub mod piece; pub mod piece;
#[cfg(test)]
mod test_engine;
pub mod tile; pub mod tile;
use crate::{ use crate::{
@ -26,12 +28,31 @@ impl Display for Direction {
} }
} }
impl Direction { impl Direction {
const ALL: [Direction; 4] = [ pub const ALL: [Direction; 4] = [
Direction::East, Direction::East,
Direction::North, Direction::North,
Direction::West, Direction::West,
Direction::South, 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)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
@ -39,6 +60,11 @@ pub struct Board<const N: usize> {
to_move: Color, to_move: Color,
tiles: [[Tile; N]; N], 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> { impl<const N: usize> Board<N> {
/// The number of capstones in each players' pouches. /// The number of capstones in each players' pouches.
@ -68,6 +94,7 @@ impl<const N: usize> Board<N> {
} }
} }
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Move<const N: usize> { pub enum Move<const N: usize> {
Place { Place {
piece: Piece, piece: Piece,

566
tak_lib/src/test_engine.rs Normal file
View File

@ -0,0 +1,566 @@
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 actual = get_movement_moves(from.0, from.1, &tiles);
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 actual = get_movement_moves(from.0, from.1, &tiles);
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
}

View File

@ -1,6 +1,6 @@
use std::fmt::Debug; use std::fmt::Debug;
use crate::piece::ColoredPiece; use crate::piece::{Color, ColoredPiece};
/// A tile on the board. /// A tile on the board.
/// ///
@ -41,6 +41,18 @@ impl Tile {
const STACK_OFFSET: u8 = 16; const STACK_OFFSET: u8 = 16;
const STACK_MASK: u128 = !0xFFFFu128; 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. /// Gets the top piece.
pub fn top_piece(&self) -> ColoredPiece { pub fn top_piece(&self) -> ColoredPiece {
ColoredPiece::from_u8((self.0 & 0b111) as u8) ColoredPiece::from_u8((self.0 & 0b111) as u8)