use crate::board::legal_actions::{BitLegalMoveIter, BitLegalPutIter};
use crate::error::BitBoardError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BitBoard {
bits: u32,
}
impl std::default::Default for BitBoard {
fn default() -> Self {
Self::new(0x7777_7777) }
}
impl std::fmt::Display for BitBoard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"BitBoard({:04b}_{:04b}_{:04b}_{:04b}_{:04b}_{:04b}_{:04b}_{:04b})",
(self.bits >> 28) & 0xF,
(self.bits >> 24) & 0xF,
(self.bits >> 20) & 0xF,
(self.bits >> 16) & 0xF,
(self.bits >> 12) & 0xF,
(self.bits >> 8) & 0xF,
(self.bits >> 4) & 0xF,
self.bits & 0xF
)
}
}
impl BitBoard {
pub fn new(bits: u32) -> Self {
Self { bits }
}
pub fn bits(&self) -> u32 {
self.bits
}
pub fn has_valid_bits(&self) -> bool {
fn _contains_4_consecutive_hot_bits(mut bits: u32) -> bool {
bits = bits & (bits >> 1);
bits = bits & (bits >> 2);
bits != 0
}
!_contains_4_consecutive_hot_bits(self.bits & 0xF0F0_F0F0) && !_contains_4_consecutive_hot_bits(self.bits & 0x0F0F0F0F) }
pub fn swap_colors(&mut self) {
self.bits = !self.bits - 0x1111_1111;
}
pub fn flip(&mut self) {
self.bits = self.bits.swap_bytes();
self.bits = ((self.bits & 0xF0F0_F0F0) >> 4) | ((self.bits & 0x0F0F_0F0F) << 4);
}
pub fn rotate_clockwise(&mut self, n: u32) {
self.bits = self.bits.rotate_left(4 * n);
}
pub fn rotate_counterclockwise(&mut self, n: u32) {
self.bits = self.bits.rotate_right(4 * n);
}
pub fn count_pieces(&self, color: bool) -> u32 {
if color {
(self.bits + 0x1111_1111).count_ones() - 8
} else {
self.bits.count_zeros() - 8
}
}
pub fn get_pieces_at(&self, pos: u32) -> Vec<bool> {
let mut pieces = Vec::new();
let mut cursor = pos >> 3;
let mut is_piece = false;
for _ in 0..4 {
if is_piece {
pieces.push(self.bits & cursor != 0);
}
is_piece |= self.bits & cursor == 0;
cursor <<= 1;
}
pieces
}
pub fn lines_up(&self, color: bool) -> bool {
let bits = if color {
self.bits
} else {
!self.bits - 0x1111_1111
};
let top3 = bits & 0xEEEE_EEEE; if top3 & (top3 >> 1) & (top3 >> 2) != 0 {
return true;
}
let top1 = bits & 0x8888_8888; if top1 & top1.rotate_left(4) & top1.rotate_left(8) & top1.rotate_left(12) != 0 {
return true;
}
false
}
pub fn put(&mut self, pos: u32, color: bool) -> Result<(), BitBoardError> {
if self.bits & (pos >> 3) == 0 {
return Err(BitBoardError::PositionOccupied(pos));
}
self.put_unchecked(pos, color);
Ok(())
}
pub fn put_unchecked(&mut self, pos: u32, color: bool) {
let mask_stack = pos | (pos >> 1) | (pos >> 2);
let mask_others = !(mask_stack | (pos >> 3));
self.bits = ((self.bits & mask_stack) >> 1) | (self.bits & mask_others);
if color {
self.bits |= pos;
}
}
pub fn remove(&mut self, pos: u32) -> Result<bool, BitBoardError> {
if pos & (self.bits << 1) & (self.bits << 2) & (self.bits << 3) != 0 {
return Err(BitBoardError::EmptyPosition(pos));
}
let removed = self.remove_unchecked(pos);
Ok(removed)
}
pub fn remove_unchecked(&mut self, pos: u32) -> bool {
let mask_stack = (pos >> 1) | (pos >> 2) | (pos >> 3);
let mask_others = !(mask_stack | pos);
let removed = self.bits & pos;
self.bits = ((self.bits & mask_stack) << 1) | (pos >> 3) | self.bits & mask_others;
removed != 0
}
pub fn move_(&mut self, from: u32, to: u32) -> Result<(), BitBoardError> {
let removed = self
.remove(from)
.map_err(|_| BitBoardError::InvalidMove { from, to })?;
self.put(to, removed)
.map_err(|_| BitBoardError::InvalidMove { from, to })
}
pub fn move_unchecked(&mut self, from: u32, to: u32) {
let removed = self.remove_unchecked(from);
self.put_unchecked(to, removed);
}
pub fn legal_put_iter(&self, color: bool) -> BitLegalPutIter {
BitLegalPutIter::new(*self, color)
}
pub fn legal_move_iter(&self, color: bool) -> BitLegalMoveIter {
BitLegalMoveIter::new(*self, color)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::prelude::*;
macro_rules! test_board {
($(
$pos:ident: $($color:expr),*
);* $(;)?) => {{
let mut board = BitBoard::default();
$(
$(
board.put_unchecked(Position::$pos as u32, $color);
)*
)*
board
}};
}
#[test]
fn test_new() {
let board = BitBoard::new(0x1234_5678);
assert_eq!(board.bits(), 0x1234_5678);
let default_board = BitBoard::default();
assert_eq!(default_board.bits(), 0x7777_7777);
}
#[test]
fn test_bits() {
let board = BitBoard::new(0x1234_5678);
assert_eq!(board.bits(), 0x1234_5678);
let mut board = BitBoard::default();
board.put_unchecked(Position::N as u32, true);
assert_ne!(board.bits(), 0x7777_7777); }
#[test]
fn test_has_valid_bits() {
let board = BitBoard::default();
assert!(
board.has_valid_bits(),
"Default bit pattern should be valid"
);
let valid_board = BitBoard::new(0x1111_1111);
assert!(
valid_board.has_valid_bits(),
"0x1111_1111 is a valid pattern"
);
let invalid_board = BitBoard::new(0x0000_000F); assert!(
!invalid_board.has_valid_bits(),
"0x0000_000F should be an invalid pattern"
);
}
#[test]
fn test_swap_colors() {
let mut board = test_board! {
N: true;
E: false;
S: true
};
let original_board = board;
let original_black_count = board.count_pieces(true);
let original_white_count = board.count_pieces(false);
board.swap_colors();
assert_eq!(board.count_pieces(true), original_white_count);
assert_eq!(board.count_pieces(false), original_black_count);
board.swap_colors();
assert_eq!(board.count_pieces(true), original_black_count);
assert_eq!(board.count_pieces(false), original_white_count);
assert_eq!(board, original_board);
}
#[test]
fn test_count_pieces() {
let board = BitBoard::default();
assert_eq!(
board.count_pieces(true),
0,
"Empty board should have no black pieces"
);
assert_eq!(
board.count_pieces(false),
0,
"Empty board should have no white pieces"
);
let board = test_board! {
N: true;
E: false;
S: true
};
assert_eq!(board.count_pieces(true), 2, "Should have 2 black pieces");
assert_eq!(board.count_pieces(false), 1, "Should have 1 white piece");
}
#[test]
fn test_get_pieces_at() {
let board = BitBoard::default();
assert!(
board.get_pieces_at(Position::N as u32).is_empty(),
"Empty position should have no pieces"
);
let board = test_board! {
N: true, true, false
};
let pieces = board.get_pieces_at(Position::N as u32);
assert_eq!(
pieces,
vec![true, true, false],
"Position N should have pieces in order [true, true, false] from bottom"
);
}
#[test]
fn test_lines_up() {
let board = test_board! {
N: true, true, true
};
assert!(
board.lines_up(true),
"Three vertical black pieces should satisfy win condition"
);
assert!(
!board.lines_up(false),
"White pieces should not satisfy win condition"
);
let board = test_board! {
N: true;
NE: true;
E: true;
SE: true
};
assert!(
board.lines_up(true),
"Four consecutive black pieces should satisfy win condition"
);
let board = test_board! {
N: true;
S: true
};
assert!(
!board.lines_up(true),
"Non-consecutive pieces should not satisfy win condition"
);
}
#[test]
fn test_put() {
let mut board = BitBoard::default();
assert!(board.put(Position::N as u32, true).is_ok());
assert_eq!(board.get_pieces_at(Position::N as u32), vec![true]);
let mut board = test_board! {
N: true, true, true
};
assert!(board.put(Position::N as u32, false).is_err());
}
#[test]
fn test_put_unchecked() {
let mut board = BitBoard::default();
board.put_unchecked(Position::N as u32, true);
assert_eq!(board.get_pieces_at(Position::N as u32), vec![true]);
let old_bits = board.bits();
board.put_unchecked(Position::E as u32, false);
assert_ne!(board.bits(), old_bits);
}
#[test]
fn test_remove() {
let mut board = test_board! {
N: true
};
let result = board.remove(Position::N as u32);
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
assert!(board.get_pieces_at(Position::N as u32).is_empty());
assert!(board.remove(Position::N as u32).is_err());
}
#[test]
fn test_remove_unchecked() {
let mut board = test_board! {
N: true
};
let removed = board.remove_unchecked(Position::N as u32);
assert_eq!(removed, true);
assert!(board.get_pieces_at(Position::N as u32).is_empty());
}
#[test]
fn test_move() {
let mut board = test_board! {
N: true
};
assert!(board.move_(Position::N as u32, Position::E as u32).is_ok());
assert!(board.get_pieces_at(Position::N as u32).is_empty());
assert_eq!(board.get_pieces_at(Position::E as u32), vec![true]);
assert!(board.move_(Position::N as u32, Position::S as u32).is_err()); }
#[test]
fn test_move_unchecked() {
let mut board = test_board! {
N: true
};
board.move_unchecked(Position::N as u32, Position::E as u32);
assert!(board.get_pieces_at(Position::N as u32).is_empty());
assert_eq!(board.get_pieces_at(Position::E as u32), vec![true]);
let expected = test_board! {
E: true
};
assert_eq!(board.bits(), expected.bits());
}
#[test]
fn test_rotate_clockwise() {
let mut board = test_board! {
N: true;
E: false;
S: true
};
let original = board;
for _ in 0..8 {
board.rotate_clockwise(1);
}
assert_eq!(
original, board,
"Rotating 8 times should return to the original board state"
);
}
#[test]
fn test_rotate_counterclockwise() {
let mut board = test_board! {
N: true;
E: false;
S: true
};
let original = board;
for _ in 0..8 {
board.rotate_counterclockwise(1);
}
assert_eq!(
original, board,
"Rotating 8 times counterclockwise should return to the original board state"
);
}
#[test]
fn test_flip() {
let mut board = test_board! {
N: true;
E: false;
S: true
};
let original = board;
board.flip();
board.flip();
assert_eq!(
original, board,
"Flipping twice should return to the original board state"
);
}
}