use crate::board::bitboard::BitBoard;
use crate::board::legal_actions::{LegalActionIter, LegalMoveIter, LegalPutIter};
use crate::board::types::{Action, Color, Position};
use crate::error::BoardError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Board {
inner: BitBoard,
}
impl Board {
pub fn inner(&self) -> &BitBoard {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut BitBoard {
&mut self.inner
}
pub fn from_inner(inner: BitBoard) -> Self {
Self { inner }
}
}
impl Board {
pub fn is_valid(&self) -> bool {
self.inner.has_valid_bits()
}
pub fn count_pieces(&self, color: Color) -> u32 {
self.inner.count_pieces(color.into())
}
pub fn get_pieces_at(&self, pos: Position) -> Vec<Color> {
let pieces = self.inner.get_pieces_at(pos.into());
pieces.into_iter().map(Color::from).collect()
}
pub fn lines_up(&self, color: Color) -> bool {
self.inner.lines_up(color.into())
}
pub fn apply(&mut self, action: Action) -> Result<(), BoardError> {
use Action::*;
match action {
Put(pos, color) => self.put(pos, color),
Move(from, to) => self.move_(from, to),
}
}
pub fn apply_unchecked(&mut self, action: Action) {
use Action::*;
match action {
Put(pos, color) => self.put_unchecked(pos, color),
Move(from, to) => self.move_unchecked(from, to),
}
}
pub fn put(&mut self, pos: Position, color: Color) -> Result<(), BoardError> {
self.inner
.put(pos.into(), color.into())
.map_err(|_| BoardError::PositionOccupied(pos))
}
pub fn put_unchecked(&mut self, pos: Position, color: Color) {
self.inner.put_unchecked(pos.into(), color.into());
}
pub fn remove(&mut self, pos: Position) -> Result<Color, BoardError> {
let color = self
.inner
.remove(pos.into())
.map_err(|_| BoardError::EmptyPosition(pos))?;
Ok(color.into())
}
pub fn remove_unchecked(&mut self, pos: Position) -> Color {
self.inner.remove_unchecked(pos.into()).into()
}
pub fn move_(&mut self, from: Position, to: Position) -> Result<(), BoardError> {
self.inner
.move_(from.into(), to.into())
.map_err(|_| BoardError::InvalidMove { from, to })
}
pub fn move_unchecked(&mut self, from: Position, to: Position) {
self.inner.move_unchecked(from.into(), to.into());
}
pub fn legal_action_iter(&self, color: Color) -> LegalActionIter {
if self.count_pieces(color) < 8 {
let bit_iter = self.inner.legal_put_iter(color.into());
LegalActionIter::with_put_iter(LegalPutIter::new(bit_iter))
} else {
let bit_iter = self.inner.legal_move_iter(color.into());
LegalActionIter::with_move_iter(LegalMoveIter::new(bit_iter))
}
}
}
impl std::fmt::Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn _pos_to_str(pos: Position) -> &'static str {
use Position::*;
match pos {
N => "N ",
NE => "NE",
E => "E ",
SE => "SE",
S => "S ",
SW => "SW",
W => "W ",
NW => "NW",
}
}
fn _color_to_str(color: Color) -> &'static str {
use Color::*;
match color {
B => "B",
W => "W",
}
}
if !self.is_valid() {
return write!(f, "Invalid internal state");
}
for pos in Position::iter() {
let pieces = self.get_pieces_at(pos);
let pieces_str: String = pieces.into_iter().map(_color_to_str).collect();
writeln!(f, "{}: {}", _pos_to_str(pos), pieces_str)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! test_board {
($(
$pos:ident: $($color:ident),*
);* $(;)?) => {{
let mut board = Board::default();
$(
$(
board.put_unchecked(Position::$pos, Color::$color);
)*
)*
board
}};
}
#[test]
fn test_is_valid() {
let board = Board::default();
assert!(board.is_valid(), "Default board should be valid");
let invalid_board = Board {
inner: BitBoard::new(0xFFFF_FFFF), };
assert!(
!invalid_board.is_valid(),
"Bit pattern 0xFFFF_FFFF should be invalid"
);
}
#[test]
fn test_count_pieces() {
let board = Board::default();
assert_eq!(
board.count_pieces(Color::B),
0,
"Empty board should have no black pieces"
);
assert_eq!(
board.count_pieces(Color::W),
0,
"Empty board should have no white pieces"
);
let board = test_board! {
N: B;
E: W;
S: B
};
assert_eq!(
board.count_pieces(Color::B),
2,
"Should have 2 black pieces"
);
assert_eq!(board.count_pieces(Color::W), 1, "Should have 1 white piece");
}
#[test]
fn test_get_pieces_at() {
let board = Board::default();
assert!(
board.get_pieces_at(Position::N).is_empty(),
"Empty position should have no pieces"
);
let board = test_board! {
N: B
};
assert_eq!(
board.get_pieces_at(Position::N),
vec![Color::B],
"Position N should have one black piece"
);
let board = test_board! {
E: W, B
};
assert_eq!(
board.get_pieces_at(Position::E),
vec![Color::W, Color::B],
"Position E should have pieces in order [white, black] from bottom"
);
}
#[test]
fn test_lines_up() {
let board = test_board! {
N: B, B, B
};
assert!(
board.lines_up(Color::B),
"Three vertical black pieces should satisfy win condition"
);
assert!(
!board.lines_up(Color::W),
"White pieces should not satisfy win condition"
);
let board = test_board! {
N: B;
NE: B;
E: B;
SE: B
};
assert!(
board.lines_up(Color::B),
"Four adjacent black pieces should satisfy win condition"
);
let board = test_board! {
N: B;
S: B
};
assert!(
!board.lines_up(Color::B),
"Non-consecutive pieces should not satisfy win condition"
);
}
#[test]
fn test_apply() {
let mut board = Board::default();
assert!(board.apply(Action::Put(Position::N, Color::B)).is_ok());
assert_eq!(board.get_pieces_at(Position::N), vec![Color::B]);
assert!(board.apply(Action::Move(Position::N, Position::E)).is_ok());
assert!(board.get_pieces_at(Position::N).is_empty());
assert_eq!(board.get_pieces_at(Position::E), vec![Color::B]);
let result = board.apply(Action::Move(Position::N, Position::S)); assert!(result.is_err());
}
#[test]
fn test_apply_unchecked() {
let mut board = Board::default();
board.apply_unchecked(Action::Put(Position::N, Color::B));
assert_eq!(board.get_pieces_at(Position::N), vec![Color::B]);
board.apply_unchecked(Action::Move(Position::N, Position::E));
assert!(board.get_pieces_at(Position::N).is_empty());
assert_eq!(board.get_pieces_at(Position::E), vec![Color::B]);
}
#[test]
fn test_put() {
let mut board = Board::default();
assert!(board.put(Position::N, Color::B).is_ok());
assert_eq!(board.get_pieces_at(Position::N), vec![Color::B]);
let mut board = test_board! {
N: B, B, B
};
assert!(board.put(Position::N, Color::W).is_err());
}
#[test]
fn test_put_unchecked() {
let mut board = Board::default();
board.put_unchecked(Position::N, Color::B);
assert_eq!(board.get_pieces_at(Position::N), vec![Color::B]);
}
#[test]
fn test_remove() {
let mut board = test_board! {
N: B
};
let removed = board.remove(Position::N);
assert!(removed.is_ok());
assert_eq!(removed.unwrap(), Color::B);
assert!(board.get_pieces_at(Position::N).is_empty());
assert!(board.remove(Position::N).is_err());
}
#[test]
fn test_remove_unchecked() {
let mut board = test_board! {
N: B
};
let removed = board.remove_unchecked(Position::N);
assert_eq!(removed, Color::B);
assert!(board.get_pieces_at(Position::N).is_empty());
}
#[test]
fn test_move() {
let mut board = test_board! {
N: B
};
assert!(board.move_(Position::N, Position::E).is_ok());
assert!(board.get_pieces_at(Position::N).is_empty());
assert_eq!(board.get_pieces_at(Position::E), vec![Color::B]);
assert!(board.move_(Position::N, Position::S).is_err()); }
#[test]
fn test_move_unchecked() {
let mut board = test_board! {
N: B
};
board.move_unchecked(Position::N, Position::E);
assert!(board.get_pieces_at(Position::N).is_empty());
assert_eq!(board.get_pieces_at(Position::E), vec![Color::B]);
}
#[test]
fn test_from_inner() {
let bit_board = BitBoard::default();
let board = Board::from_inner(bit_board);
assert!(board.is_valid(), "Board created from default BitBoard should be valid");
assert_eq!(board.count_pieces(Color::B), 0, "Board should have no black pieces");
assert_eq!(board.count_pieces(Color::W), 0, "Board should have no white pieces");
let custom_bit_board = BitBoard::new(0x123); let board = Board::from_inner(custom_bit_board);
assert_eq!(board.inner(), &custom_bit_board, "Inner BitBoard should match");
}
}