move engine work
This commit is contained in:
parent
96fb485c87
commit
047c5fc02e
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -4,3 +4,6 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
use crate::{
|
||||
Board, Move,
|
||||
Board, Direction, Move,
|
||||
piece::{ColoredPiece, Piece},
|
||||
tile::Tile,
|
||||
};
|
||||
|
||||
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>> {
|
||||
let to_move = self.to_move;
|
||||
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();
|
||||
if top_piece == ColoredPiece::None {
|
||||
// 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 {
|
||||
get_movement_moves(x, y, tiles[x as usize][y as usize].stack_len())
|
||||
get_movement_moves(x, y, tiles)
|
||||
} else {
|
||||
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);
|
||||
moves.push(Move::Place {
|
||||
piece: Piece::Flat,
|
||||
@ -57,21 +60,92 @@ fn insert_placement_moves<const N: usize>(x: u8, y: u8, can_place_caps: bool) ->
|
||||
moves
|
||||
}
|
||||
|
||||
fn get_movement_moves<const N: usize>(x: u8, y: u8, stack_len: u8) -> Vec<Move<N>> {
|
||||
let max_grab = u8::min(N as u8, stack_len + 1);
|
||||
pub(crate) fn get_movement_moves<const N: usize>(
|
||||
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
|
||||
let max_dist_east = max_grab.min(N as u8 - x - 1);
|
||||
// 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!()
|
||||
for dir in Direction::ALL {
|
||||
get_moves_in_dir(x, y, dir, tiles, &mut moves, is_caps, max_grab);
|
||||
}
|
||||
|
||||
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
|
||||
buf: &mut [u8; N],
|
||||
i: usize,
|
||||
@ -94,9 +168,17 @@ fn get_drop_amounts<const N: usize>(
|
||||
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;
|
||||
}
|
||||
|
||||
@ -107,193 +189,3 @@ fn get_drop_amounts<const N: usize>(
|
||||
}
|
||||
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]]);
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
pub mod engine;
|
||||
pub mod piece;
|
||||
#[cfg(test)]
|
||||
mod test_engine;
|
||||
pub mod tile;
|
||||
|
||||
use crate::{
|
||||
@ -26,12 +28,31 @@ impl Display for Direction {
|
||||
}
|
||||
}
|
||||
impl Direction {
|
||||
const ALL: [Direction; 4] = [
|
||||
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)]
|
||||
@ -39,6 +60,11 @@ pub struct Board<const N: usize> {
|
||||
to_move: Color,
|
||||
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.
|
||||
@ -68,6 +94,7 @@ impl<const N: usize> Board<N> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
pub enum Move<const N: usize> {
|
||||
Place {
|
||||
piece: Piece,
|
||||
|
||||
566
tak_lib/src/test_engine.rs
Normal file
566
tak_lib/src/test_engine.rs
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user