Compare commits
6
Commits
2e344fed0a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4510141ce5 | ||
|
|
047c5fc02e | ||
|
|
96fb485c87 | ||
|
|
13aa9ffffe | ||
|
|
c9ceb5b2ab | ||
|
|
fdec2ae764 |
+19
@@ -0,0 +1,19 @@
|
|||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
.vscode/
|
||||||
|
.zed/
|
||||||
|
.helix/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
Generated
+47
@@ -0,0 +1,47 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# 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"
|
||||||
|
dependencies = [
|
||||||
|
"tak_lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tak_lib"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pretty_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tak_server"
|
||||||
|
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"
|
||||||
@@ -2,5 +2,7 @@
|
|||||||
name = "tak_bot"
|
name = "tak_bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
default-run = "tak_bot"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
tak_lib = { path = "../tak_lib" }
|
||||||
|
|||||||
+12
-1
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
+136
-10
@@ -1,14 +1,140 @@
|
|||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub mod engine;
|
||||||
left + right
|
pub mod piece;
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod test_engine;
|
||||||
use super::*;
|
pub mod tile;
|
||||||
|
|
||||||
#[test]
|
use crate::{
|
||||||
fn it_works() {
|
piece::{Color, Piece},
|
||||||
let result = add(2, 2);
|
tile::Tile,
|
||||||
assert_eq!(result, 4);
|
};
|
||||||
|
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> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Color {
|
||||||
|
White = 0,
|
||||||
|
Black = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Piece {
|
||||||
|
None = 0,
|
||||||
|
Flat = 1,
|
||||||
|
Wall = 2,
|
||||||
|
Caps = 3,
|
||||||
|
}
|
||||||
|
impl Debug for Piece {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Piece::None => write!(f, "----"),
|
||||||
|
Piece::Flat => write!(f, "Flat"),
|
||||||
|
Piece::Wall => write!(f, "Wall"),
|
||||||
|
Piece::Caps => write!(f, "Caps"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ColoredPiece {
|
||||||
|
None = 0,
|
||||||
|
WhiteFlat = 1,
|
||||||
|
WhiteWall = 2,
|
||||||
|
WhiteCaps = 3,
|
||||||
|
|
||||||
|
BlackFlat = 5,
|
||||||
|
BlackWall = 6,
|
||||||
|
BlackCaps = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColoredPiece {
|
||||||
|
/// Constructs a new `ColoredPiece`.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// safety: value will always be in range
|
||||||
|
unsafe { std::mem::transmute(piece_val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new `ColoredPiece` from the given raw value.
|
||||||
|
pub fn from_u8_opt(val: u8) -> Option<Self> {
|
||||||
|
if val <= 7 && val != 4 {
|
||||||
|
// safety: value is checked to be in range
|
||||||
|
Some(unsafe { std::mem::transmute(val) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new `ColoredPiece` from the given raw value.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
/// This panics if `val` is outside the range `0..=7` or is `4`.
|
||||||
|
pub fn from_u8(val: u8) -> Self {
|
||||||
|
if val <= 7 && val != 4 {
|
||||||
|
// safety: value is checked to be in range
|
||||||
|
unsafe { std::mem::transmute(val) }
|
||||||
|
} else {
|
||||||
|
panic!("`val` ({}) was not in range for `ColoredPiece`!", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the color of the piece,
|
||||||
|
/// returning `Color::White` if `self` is `None`.
|
||||||
|
pub fn color(&self) -> Color {
|
||||||
|
if (*self as u8 >> 2) == 1 {
|
||||||
|
Color::Black
|
||||||
|
} else {
|
||||||
|
Color::White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the color of the piece,
|
||||||
|
/// returning `None` if `self` is `None`.
|
||||||
|
pub fn color_opt(&self) -> Option<Color> {
|
||||||
|
let val = *self as u8;
|
||||||
|
if val == 0 {
|
||||||
|
return None;
|
||||||
|
} else if (val >> 2) == 1 {
|
||||||
|
Some(Color::Black)
|
||||||
|
} else {
|
||||||
|
Some(Color::White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the piece type of this piece.
|
||||||
|
pub fn piece(&self) -> Piece {
|
||||||
|
let piece_val = *self as u8 & 0b11;
|
||||||
|
// safety: value will always be in range
|
||||||
|
unsafe { std::mem::transmute(piece_val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::piece::{Color, ColoredPiece};
|
||||||
|
|
||||||
|
/// A tile on the board.
|
||||||
|
///
|
||||||
|
/// bits [2:0] represent the top piece (if any).
|
||||||
|
/// - bit [2] being the color.
|
||||||
|
/// - bits [1:0] being the piece type (if any).
|
||||||
|
/// bits [15:8] represent the number of pieces underneath the top piece.
|
||||||
|
/// bits [127:16] are the colors of the stacked pieces.
|
||||||
|
/// - bit [16] is the top-most piece, and bit [127] is the bottom-most piece.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Tile(u128);
|
||||||
|
|
||||||
|
impl Debug for Tile {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if self.stack_len() == 0 {
|
||||||
|
self.top_piece().fmt(f)
|
||||||
|
} else {
|
||||||
|
let stack = self.stack();
|
||||||
|
let stack: String = (0..self.stack_len())
|
||||||
|
.rev()
|
||||||
|
.map(|i| if (stack >> i) == 1 { 'b' } else { 'w' })
|
||||||
|
.collect();
|
||||||
|
write!(f, "{}:{:?}", stack, self.top_piece())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tile {
|
||||||
|
/// An empty tile.
|
||||||
|
pub const EMPTY: Tile = Tile(0);
|
||||||
|
|
||||||
|
const PIECE_OFFSET: u8 = 0;
|
||||||
|
const PIECE_MASK: u128 = 0b111u128;
|
||||||
|
|
||||||
|
const LEN_OFFSET: u8 = 8;
|
||||||
|
const LEN_MASK: u128 = 0xFF00u128;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the top piece.
|
||||||
|
pub fn set_top_piece(&mut self, piece: ColoredPiece) {
|
||||||
|
// clear top_piece
|
||||||
|
self.0 &= !Self::PIECE_MASK;
|
||||||
|
// set top_piece
|
||||||
|
self.0 |= (piece as u128) << Self::PIECE_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the length of the stack.
|
||||||
|
pub fn stack_len(&self) -> u8 {
|
||||||
|
(self.0 >> 8) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the stack length.
|
||||||
|
pub fn set_stack_len(&mut self, len: u8) {
|
||||||
|
// clear stack_len
|
||||||
|
self.0 &= !Self::LEN_MASK;
|
||||||
|
// set stack_len
|
||||||
|
self.0 |= (len as u128) << Self::LEN_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the stacked pieces as a bit array.
|
||||||
|
///
|
||||||
|
/// The LSB is the top-most piece, whereas the MSB is the bottom-most piece.
|
||||||
|
pub fn stack(&self) -> u128 {
|
||||||
|
self.0 >> Self::STACK_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the stacked pieces.
|
||||||
|
pub fn set_stack(&mut self, stack: u128) {
|
||||||
|
// clear stack
|
||||||
|
self.0 &= !Self::STACK_MASK;
|
||||||
|
// set stack
|
||||||
|
self.0 |= (stack as u128) << Self::STACK_OFFSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user