tak_rs/tak_lib/src/engine.rs
2025-12-22 11:32:20 -06:00

300 lines
7.6 KiB
Rust

use crate::{
Board, Move,
piece::{ColoredPiece, Piece},
};
impl<const N: usize> Board<N> {
pub fn moves(&self) -> impl Iterator<Item = Move<N>> {
let to_move = self.to_move;
let tiles = &self.tiles;
let can_place_caps = if Self::N_CAPS == 0 {
false
} else {
let caps_on_board = self
.tiles
.iter()
.flat_map(|col| col.iter())
.filter(|t| {
let piece = t.top_piece();
piece.color_opt() == Some(to_move) && piece.piece() == Piece::Caps
})
.count() as u8;
caps_on_board < Self::N_CAPS
};
let chords = (0..(N as u8)).flat_map(|x| (0..(N as u8)).map(move |y| (x, y)));
chords.flat_map(move |(x, y)| {
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)
} else if top_piece.color() == to_move {
get_movement_moves(x, y, tiles[x as usize][y as usize].stack_len())
} else {
Vec::with_capacity(0)
}
})
}
}
fn insert_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,
at: (x, y),
});
moves.push(Move::Place {
piece: Piece::Wall,
at: (x, y),
});
if can_place_caps {
moves.push(Move::Place {
piece: Piece::Caps,
at: (x, y),
});
}
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);
// 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!()
}
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 {
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;
}
#[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]]);
}