added a bunch of test cases for move generation
This commit is contained in:
parent
1a8c339326
commit
99e619d733
2 changed files with 206 additions and 190 deletions
|
|
@ -44,8 +44,8 @@ const DIRECTION_MASKS: [Board; 8] = [
|
||||||
/// Represents the board state using two integers. This allows us to reduce the
|
/// Represents the board state using two integers. This allows us to reduce the
|
||||||
/// memory footprint by 75% when compared against using a byte[8][8] (64
|
/// memory footprint by 75% when compared against using a byte[8][8] (64
|
||||||
/// bytes). While digits in these boards do not have _place value_ per se, it
|
/// bytes). While digits in these boards do not have _place value_ per se, it
|
||||||
/// still may be helpful to think of the structure as being Little Endian and
|
/// still may be helpful to think of the structure as being Big Endian and
|
||||||
/// by that I mean to say that a0 is the leftmost bit and h8 is the rightmost.
|
/// by that I mean to say that h8 is the leftmost bit and a1 is the rightmost.
|
||||||
///
|
///
|
||||||
/// The values are to be mapped as follows where each number indicates the bit
|
/// The values are to be mapped as follows where each number indicates the bit
|
||||||
/// in the integer that corresponds to the position in the board:
|
/// in the integer that corresponds to the position in the board:
|
||||||
|
|
@ -70,6 +70,11 @@ const DIRECTION_MASKS: [Board; 8] = [
|
||||||
///
|
///
|
||||||
/// Note that I use the traditional nomenclature of ranks and files which
|
/// Note that I use the traditional nomenclature of ranks and files which
|
||||||
/// correspond to rows and columns respectively.
|
/// correspond to rows and columns respectively.
|
||||||
|
///
|
||||||
|
/// Like I note later on, there is some variation on how precisely ranks
|
||||||
|
/// and files are to be rendered or oriented due to the symmetrical nature
|
||||||
|
/// of Othello, but I stick to rigid Chess-esque conventions for the sake of my
|
||||||
|
/// sanity.
|
||||||
pub struct BitBoard {
|
pub struct BitBoard {
|
||||||
/// Contains all boards for game. In this case, there are only two.
|
/// Contains all boards for game. In this case, there are only two.
|
||||||
/// The first is black. The second is white.
|
/// The first is black. The second is white.
|
||||||
|
|
@ -78,9 +83,10 @@ pub struct BitBoard {
|
||||||
|
|
||||||
impl Default for BitBoard {
|
impl Default for BitBoard {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
use squares::*;
|
||||||
// Create board in standard starting structure
|
// Create board in standard starting structure
|
||||||
Self {
|
Self {
|
||||||
boards: [(1 << 36) + (1 << 27), (1 << 35) + (1 << 28)],
|
boards: [D5 | E4, E5 | D4],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +102,19 @@ impl Debug for BitBoard {
|
||||||
|
|
||||||
impl Display for BitBoard {
|
impl Display for BitBoard {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Render the board in a chess style. There seems to be differing opinions
|
||||||
|
// on how ranks and files should be displayed in Othello (mirroring vs rotation),
|
||||||
|
// so I just print the board in the Chess style since it makes more sense to me.
|
||||||
|
//
|
||||||
|
// For the sake of testing, it's also super convenient to not have the board
|
||||||
|
// flipping and rotating in the output. It makes it way easier to compare two
|
||||||
|
// boards side by side.
|
||||||
|
|
||||||
|
// for each rank _r_ in reverse since our structure is Big Endian and we want
|
||||||
|
// to print the 8 rank first (chess style in white perspective).
|
||||||
for r in (0..8).rev() {
|
for r in (0..8).rev() {
|
||||||
let start_i = r * 8;
|
let start_i = r * 8;
|
||||||
|
write!(f, "{}: ", r + 1).unwrap();
|
||||||
for i in start_i..(start_i + 8) {
|
for i in start_i..(start_i + 8) {
|
||||||
// rshift to cut off bits on rhs, then AND on 1 to get just the last bit.
|
// rshift to cut off bits on rhs, then AND on 1 to get just the last bit.
|
||||||
write!(
|
write!(
|
||||||
|
|
@ -117,6 +134,7 @@ impl Display for BitBoard {
|
||||||
write!(f, "\n").unwrap();
|
write!(f, "\n").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
write!(f, "\n abcdefgh").unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +194,7 @@ impl BitBoard {
|
||||||
let mut moves = 0;
|
let mut moves = 0;
|
||||||
let (team, opponent) = (
|
let (team, opponent) = (
|
||||||
self.boards[current_team as usize],
|
self.boards[current_team as usize],
|
||||||
self.boards[(current_team as usize + 1) % 2],
|
self.boards[current_team.next() as usize],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Instead of looping through a structure and checking adjacent
|
// Instead of looping through a structure and checking adjacent
|
||||||
|
|
@ -222,15 +240,17 @@ impl BitBoard {
|
||||||
//
|
//
|
||||||
// Certain shifts are associated with particular masks by matching
|
// Certain shifts are associated with particular masks by matching
|
||||||
// indices. So for example, when we're looking to the right, we want to
|
// indices. So for example, when we're looking to the right, we want to
|
||||||
// ignore the _h_ file. This is because there's no way of placing a piece
|
// ignore the _a_ file. This is because there's no way of placing a disc
|
||||||
// in an orientation such that a piece in the _h_ file is flanked on its
|
// in an orientation such that a disc in the _a_ file is flanked on its
|
||||||
// right side since the board ends. We do the same for when looking
|
// right side. We do the same for when looking left with the _h_ file.
|
||||||
// left with the _a_ file.
|
|
||||||
|
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
let shift = DIRECTIONS[i];
|
let shift = DIRECTIONS[i];
|
||||||
let mask = DIRECTION_MASKS[i];
|
let mask = DIRECTION_MASKS[i];
|
||||||
// begin performing masks based on where opponent positions are
|
// begin performing masks based on where opponent positions are
|
||||||
|
// since we can flank multiple pieces in a single direction, we
|
||||||
|
// apply this logic seven times as this would account for the
|
||||||
|
// longest possible flank.
|
||||||
let mut sub_move = shift_in_direction(shift, team & mask) & opponent;
|
let mut sub_move = shift_in_direction(shift, team & mask) & opponent;
|
||||||
sub_move |= shift_in_direction(shift, sub_move & mask) & opponent;
|
sub_move |= shift_in_direction(shift, sub_move & mask) & opponent;
|
||||||
sub_move |= shift_in_direction(shift, sub_move & mask) & opponent;
|
sub_move |= shift_in_direction(shift, sub_move & mask) & opponent;
|
||||||
|
|
@ -327,224 +347,209 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_masking_logic() {
|
fn available_works() {
|
||||||
// validating the tests used in the comment explaining the way we're
|
|
||||||
// generating moves.
|
|
||||||
let bb = BitBoard::default();
|
let bb = BitBoard::default();
|
||||||
let blk_shl = bb.boards[Team::Black as usize] << 1;
|
assert_eq!(bb.available(Team::Black), E6 | F5 | C4 | D3);
|
||||||
// validate that they are shifted to the right
|
|
||||||
assert_eq!(blk_shl, (1 << 37) + (1 << 28));
|
|
||||||
// validate that they are shifted to the right visually
|
|
||||||
let expected_output = r"--------
|
|
||||||
--------
|
|
||||||
--------
|
|
||||||
-----B--
|
|
||||||
----B---
|
|
||||||
--------
|
|
||||||
--------
|
|
||||||
--------";
|
|
||||||
assert_eq!(
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
BitBoard {
|
|
||||||
boards: [blk_shl, 0]
|
|
||||||
}
|
|
||||||
),
|
|
||||||
expected_output
|
|
||||||
);
|
|
||||||
|
|
||||||
// test that we can mask with white squares.
|
|
||||||
let matched = blk_shl & bb.boards[Team::White as usize];
|
|
||||||
// validate that they are matching only
|
|
||||||
assert_eq!(matched, !Board::MAX + (1 << 28));
|
|
||||||
// validate that they match visually
|
|
||||||
let expected_output = r"--------
|
|
||||||
--------
|
|
||||||
--------
|
|
||||||
--------
|
|
||||||
----B---
|
|
||||||
--------
|
|
||||||
--------
|
|
||||||
--------";
|
|
||||||
assert_eq!(
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
BitBoard {
|
|
||||||
boards: [matched, 0]
|
|
||||||
}
|
|
||||||
),
|
|
||||||
expected_output
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn avaliable_works() {
|
|
||||||
let bb = BitBoard::default();
|
|
||||||
assert_eq!(bb.available(Team::Black), D6 + C5 + F4 + E3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_works() {
|
fn display_works() {
|
||||||
let bb = BitBoard::default();
|
let bb = BitBoard::default();
|
||||||
let expected_output = r"--------
|
let expected_output = r"8: --------
|
||||||
--------
|
7: --------
|
||||||
--------
|
6: --------
|
||||||
---WB---
|
5: ---BW---
|
||||||
---BW---
|
4: ---WB---
|
||||||
--------
|
3: --------
|
||||||
--------
|
2: --------
|
||||||
--------";
|
1: --------
|
||||||
|
abcdefgh";
|
||||||
assert_eq!(format!("{}", bb), expected_output);
|
assert_eq!(format!("{}", bb), expected_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jon_works() {
|
fn jon_works() {
|
||||||
let bb = BitBoard::from_jon("///3wb/3bw///").expect("Starting board should be valid");
|
let bb = BitBoard::from_jon("///3bw/3wb///").expect("Starting board should be valid");
|
||||||
println!("{}", bb);
|
println!("{}", bb);
|
||||||
assert_eq!(bb.boards, BitBoard::default().boards);
|
assert_eq!(bb.boards, BitBoard::default().boards);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_starting_position_black() {
|
fn test_available_starting_position_black() {
|
||||||
// Starting position
|
let bb = BitBoard::default();
|
||||||
let bb = BitBoard::from_jon("///3wb/3bw///").expect("Valid board");
|
assert_eq!(bb.available(Team::Black), E6 | F5 | C4 | D3);
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can move to: d3, c4, f5, e6
|
|
||||||
let expected = BitBoard::from_jon("//4b/5b/2b/3b//").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_starting_position_white() {
|
fn test_available_starting_position_white() {
|
||||||
// Starting position
|
let bb = BitBoard::default();
|
||||||
let bb = BitBoard::from_jon("///3wb/3bw///").expect("Valid board");
|
assert_eq!(bb.available(Team::White), D6 | E3 | C5 | F4);
|
||||||
let available = bb.available(Team::White);
|
|
||||||
|
|
||||||
// White can move to: c5, d6, f4, e3
|
|
||||||
let expected = BitBoard::from_jon("//3w/5w/2w/4w//").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::White as usize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_empty_board() {
|
fn test_available_empty_board() {
|
||||||
// Empty board - no pieces
|
let bb = BitBoard::from_jon("///////").expect("Valid board");
|
||||||
let bb = BitBoard::from_jon("////////").expect("Valid board");
|
assert_eq!(bb.available(Team::Black), 0);
|
||||||
let available_black = bb.available(Team::Black);
|
assert_eq!(bb.available(Team::White), 0);
|
||||||
let available_white = bb.available(Team::White);
|
|
||||||
|
|
||||||
// No moves available on empty board
|
|
||||||
assert_eq!(available_black, 0);
|
|
||||||
assert_eq!(available_white, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_single_piece_no_moves() {
|
fn test_available_single_piece_no_moves() {
|
||||||
// Single white piece, no black pieces
|
// Only white at d4, no black pieces
|
||||||
let bb = BitBoard::from_jon("///3w////").expect("Valid board");
|
let bb = BitBoard::from_jon("///3w///").expect("Valid board");
|
||||||
let available = bb.available(Team::Black);
|
assert_eq!(bb.available(Team::Black), 0);
|
||||||
|
assert_eq!(bb.available(Team::White), 0);
|
||||||
// Black has no valid moves (needs opponent pieces to capture)
|
|
||||||
assert_eq!(available, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_corner_move() {
|
fn test_available_horizontal_line() {
|
||||||
// Board with pieces set up for a corner capture
|
// Black at a4, white at b4-f4, empty g4, black at h4
|
||||||
// Black at b8, white at a8, black can play at a7 to capture
|
let bb = BitBoard::from_jon("////bwwwww1b///").expect("Valid board");
|
||||||
let bb = BitBoard::from_jon("wb//////").expect("Valid board");
|
// Black can play at g4 to capture white pieces
|
||||||
println!("{}", bb);
|
assert_eq!(bb.available(Team::Black), G4);
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can move to a7 to capture white at a8
|
|
||||||
let expected = BitBoard::from_jon("/b//////").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_edge_captures() {
|
|
||||||
// Black pieces on edges with white pieces that can be captured
|
|
||||||
let bb = BitBoard::from_jon("b6w/8/8/8/8/8/8/8").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can capture by playing in between
|
|
||||||
let expected = BitBoard::from_jon("1bbbbbb/").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_multiple_direction_capture() {
|
|
||||||
// White surrounded by black - black can capture in multiple directions
|
|
||||||
let bb = BitBoard::from_jon("///2bbb/2bwb/2bbb///").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can play at d4 (where white is) - but this is invalid, let me reconsider
|
|
||||||
// Actually, moves must be on empty squares
|
|
||||||
// Let's set up where black can capture in multiple directions
|
|
||||||
let bb = BitBoard::from_jon("///2b1b/2bwb/2bbb///").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can move to d5 to capture white
|
|
||||||
let expected = BitBoard::from_jon("///3b////").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_no_valid_moves() {
|
|
||||||
// Position where current team has no valid moves
|
|
||||||
// Black pieces isolated with no white pieces to capture
|
|
||||||
let bb = BitBoard::from_jon("b///////w").expect("Valid board");
|
|
||||||
let available_black = bb.available(Team::Black);
|
|
||||||
let available_white = bb.available(Team::White);
|
|
||||||
|
|
||||||
// Neither player can capture anything
|
|
||||||
assert_eq!(available_black, 0);
|
|
||||||
assert_eq!(available_white, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_full_row_capture() {
|
|
||||||
// Black can capture an entire row of white pieces
|
|
||||||
let bb = BitBoard::from_jon("///////bwwwwwwb").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can play anywhere between the two black pieces on row 1
|
|
||||||
let expected = BitBoard::from_jon("///////1bbbbbb").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_diagonal_capture() {
|
|
||||||
// Test diagonal captures
|
|
||||||
let bb = BitBoard::from_jon("b/1w/2w/3w////").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black can capture diagonally
|
|
||||||
let expected = BitBoard::from_jon("////4b///").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_available_midgame_position() {
|
|
||||||
// More complex mid-game position
|
|
||||||
let bb = BitBoard::from_jon("//2bwb/2www/2bwb///").expect("Valid board");
|
|
||||||
let available = bb.available(Team::Black);
|
|
||||||
|
|
||||||
// Black should have several valid moves
|
|
||||||
// This would need to be calculated based on actual game rules
|
|
||||||
// Adding moves that would flip white pieces
|
|
||||||
let expected = BitBoard::from_jon("/3b/b1b1b/b3b/b1b1b/3b//").expect("Valid board");
|
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_available_vertical_capture() {
|
fn test_available_vertical_capture() {
|
||||||
// Test vertical captures
|
// Black at a8, white at a7, a6, a5, empty a4, black at a3
|
||||||
let bb = BitBoard::from_jon("b/w/w/w////").expect("Valid board");
|
let bb = BitBoard::from_jon("b/w/w/w//b//").expect("Valid board");
|
||||||
let available = bb.available(Team::Black);
|
// Black can play at a4 to capture white pieces between a3 and a8
|
||||||
|
assert_eq!(bb.available(Team::Black), A4);
|
||||||
|
}
|
||||||
|
|
||||||
// Black can capture vertically downward
|
#[test]
|
||||||
let expected = BitBoard::from_jon("////b///").expect("Valid board");
|
fn test_available_diagonal_capture_tr() {
|
||||||
assert_eq!(available, expected.boards[Team::Black as usize]);
|
// Black at a1, white at b2, c3, empty d4
|
||||||
|
let bb = BitBoard::from_jon("/////2w/1w/b").expect("Valid board");
|
||||||
|
// Black can play at d4 to reverse on top-right diagonal
|
||||||
|
assert_eq!(bb.available(Team::Black), D4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_diagonal_capture_tl() {
|
||||||
|
// Black at h1, white at g2, f3, empty e4
|
||||||
|
let bb = BitBoard::from_jon("/////5w/6w/7b").expect("Valid board");
|
||||||
|
// Black can play at e4 to reverse on top-left-diagonal
|
||||||
|
assert_eq!(bb.available(Team::Black), E4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_diagonal_capture_br() {
|
||||||
|
// Black at a1, white at b2, c3, empty d4
|
||||||
|
let bb = BitBoard::from_jon("w/1b/2b").expect("Valid board");
|
||||||
|
// White can play at d5 to reverse on bottom-right diagonal
|
||||||
|
assert_eq!(bb.available(Team::White), D5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_diagonal_capture_bl() {
|
||||||
|
// Black at h1, white at g2, f3, empty e4
|
||||||
|
let bb = BitBoard::from_jon("7w/6b/5b").expect("Valid board");
|
||||||
|
// White can play at e5 to reverse on bottom-left diagonal
|
||||||
|
assert_eq!(bb.available(Team::White), E5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_corner_a1() {
|
||||||
|
// Setup where black can capture into corner a1
|
||||||
|
// White at b1, black at c1, empty a1
|
||||||
|
let bb = BitBoard::from_jon("///////wb").expect("Valid board");
|
||||||
|
// Black can play at a1 to capture b1
|
||||||
|
assert_eq!(bb.available(Team::Black), 0);
|
||||||
|
assert_eq!(bb.available(Team::White), C1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_corner_h8() {
|
||||||
|
// Setup where black can capture into corner h8
|
||||||
|
// Black at f8, white at g8, empty h8
|
||||||
|
let bb = BitBoard::from_jon("5bw//////").expect("Valid board");
|
||||||
|
// Black can play at h8 to capture g8
|
||||||
|
assert_eq!(bb.available(Team::Black), H8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_multiple_directions() {
|
||||||
|
// White at d4, black at d3, d5, c4, e4 (surrounding white)
|
||||||
|
let bb = BitBoard::from_jon("///3b/2bwb/3b//").expect("Valid board");
|
||||||
|
// White can play at d2, d6, b4, or f4 to capture black
|
||||||
|
assert_eq!(bb.available(Team::White), D2 | D6 | B4 | F4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_no_moves_surrounded() {
|
||||||
|
// Black piece at b7 completely surrounded by white
|
||||||
|
let bb = BitBoard::from_jon("www/wbw/www////").expect("Valid board");
|
||||||
|
println!("dis:\n{}", bb);
|
||||||
|
// Black should have three moves in this position: d7, b5, and d5
|
||||||
|
assert_eq!(bb.available(Team::Black), D7 | B5 | D5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_long_capture() {
|
||||||
|
// Black at a4, white at b4-f4, empty g4, black at h4
|
||||||
|
let bb = BitBoard::from_jon("////bwwwww1b///").expect("Valid board");
|
||||||
|
println!("dis:\n{}", bb);
|
||||||
|
// Black can play at g4 to capture entire row of white pieces
|
||||||
|
assert_eq!(bb.available(Team::Black), G4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_edge_moves() {
|
||||||
|
// Black at a1, white at b1, c1, empty d1
|
||||||
|
let bb = BitBoard::from_jon("///////bww").expect("Valid board");
|
||||||
|
// Black can play at d1 to capture b1 and c1
|
||||||
|
assert_eq!(bb.available(Team::Black), D1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_after_first_move() {
|
||||||
|
// After black plays d3 from starting position
|
||||||
|
let bb = BitBoard::from_jon("///3bw/2bbw/3b//").expect("Valid board");
|
||||||
|
// White should be able to play at several positions
|
||||||
|
let available = bb.available(Team::White);
|
||||||
|
// White can at minimum play c3, e3, c5
|
||||||
|
assert_ne!(available, 0);
|
||||||
|
assert_eq!(available & C3, C3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_complex_midgame() {
|
||||||
|
// A complex position with multiple pieces
|
||||||
|
let bb = BitBoard::from_jon("//1b2w/1bwwww/1bwbww/1bwwww/1b3/").expect("Valid board");
|
||||||
|
let available = bb.available(Team::Black);
|
||||||
|
// Black should have at least some moves available
|
||||||
|
assert_ne!(available, 0);
|
||||||
|
// In fact, it should have the following options available:
|
||||||
|
assert_eq!(available, F7 | D6 | F6 | G4 | F2 | E2 | D2 | G5 | G3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_no_opponent_pieces() {
|
||||||
|
// Board with only white pieces (two rows)
|
||||||
|
let bb = BitBoard::from_jon("wwwwwwww/wwwwwwww/////").expect("Valid board");
|
||||||
|
// Black has no pieces, so no moves
|
||||||
|
assert_eq!(bb.available(Team::Black), 0);
|
||||||
|
assert_eq!(bb.available(Team::White), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_available_all_four_corners() {
|
||||||
|
// Test capture opportunities in all four corners
|
||||||
|
// a1 corner: white at b1, black at c1
|
||||||
|
let bb = BitBoard::from_jon("///////wb").expect("Valid board");
|
||||||
|
assert_eq!(bb.available(Team::White), C1);
|
||||||
|
|
||||||
|
// h1 corner: black at f1, white at g1
|
||||||
|
let bb = BitBoard::from_jon("///////6bw").expect("Valid board");
|
||||||
|
assert_eq!(bb.available(Team::White), F1);
|
||||||
|
|
||||||
|
// a8 corner: white at b8, black at c8
|
||||||
|
let bb = BitBoard::from_jon("wb").expect("Valid board");
|
||||||
|
assert_eq!(bb.available(Team::White), C8);
|
||||||
|
|
||||||
|
// h8 corner: white at b8, black at c8
|
||||||
|
let bb = BitBoard::from_jon("6wb").expect("Valid board");
|
||||||
|
assert_eq!(bb.available(Team::Black), F8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,14 @@ pub enum Team {
|
||||||
Black,
|
Black,
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Team {
|
||||||
|
/// Just return the other team or the next team.
|
||||||
|
/// This is useful for modeling state transfer
|
||||||
|
pub fn next(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Team::Black => Team::White,
|
||||||
|
Team::White => Team::Black,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue