use std::fmt;
use super::{Action, GameStatus, Square, State, Team};
use crate::state::{
MASK_COL_A, MASK_COL_B, MASK_COL_G, MASK_COL_H, MASK_ROW_1, MASK_ROW_2, MASK_ROW_7, MASK_ROW_8,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct Board {
pub turn: Team,
pub state: State,
}
impl Board {
#[must_use]
pub const fn new(turn: Team, state: State) -> Self {
Self { turn, state }
}
#[must_use]
pub const fn new_default() -> Self {
Self {
turn: Team::White,
state: State::default(),
}
}
#[must_use]
pub const fn from_squares(
turn: Team,
white_squares: &[Square],
black_squares: &[Square],
king_squares: &[Square],
) -> Self {
let mut whites = 0u64;
let mut i = 0;
while i < white_squares.len() {
let square = white_squares[i];
whites |= square.to_mask();
i += 1;
}
let mut blacks = 0u64;
let mut i = 0;
while i < black_squares.len() {
let square = black_squares[i];
blacks |= square.to_mask();
i += 1;
}
let mut kings = 0u64;
let mut i = 0;
while i < king_squares.len() {
let square = king_squares[i];
kings |= square.to_mask();
i += 1;
}
Self {
turn,
state: State {
pieces: [whites, blacks],
kings,
},
}
}
#[inline(always)]
#[must_use]
pub const fn friendly_pieces(&self) -> u64 {
self.state.pieces[self.turn as usize]
}
#[inline(always)]
#[must_use]
pub const fn hostile_pieces(&self) -> u64 {
self.state.pieces[self.turn.opponent() as usize]
}
#[inline(always)]
pub const fn swap_turn_(&mut self) {
self.turn = self.turn.opponent();
}
#[inline]
#[must_use]
pub fn swap_turn(&self) -> Self {
let mut new_board = *self;
new_board.swap_turn_();
new_board
}
#[inline(always)]
pub fn apply_(&mut self, action: &Action) {
self.state.apply_(&action.delta);
#[cfg(debug_assertions)]
self.state.validate();
}
#[must_use]
pub fn apply(&self, action: &Action) -> Self {
let mut new_board = *self;
new_board.apply_(action);
new_board
}
pub fn rotate_(&mut self) {
self.state.rotate_();
#[cfg(debug_assertions)]
self.state.validate();
}
#[must_use]
pub fn rotate(&self) -> Self {
let mut new_board = *self;
new_board.rotate_();
new_board
}
#[must_use]
pub fn status(&self) -> GameStatus {
let friendly_pieces = self.friendly_pieces();
let hostile_pieces = self.hostile_pieces();
if friendly_pieces == 0 {
return GameStatus::Won(self.turn.opponent());
}
if hostile_pieces == 0 {
return GameStatus::Won(self.turn);
}
if friendly_pieces.is_power_of_two() && hostile_pieces.is_power_of_two() {
return GameStatus::Draw;
}
if self.is_blocked() {
return GameStatus::Won(self.turn.opponent());
}
if self.rotate().is_blocked() {
return GameStatus::Won(self.turn);
}
GameStatus::InProgress
}
const fn is_blocked(&self) -> bool {
let friendly_pieces = self.friendly_pieces();
let hostile_pieces = self.hostile_pieces();
let friendly_kings = friendly_pieces & self.state.kings;
let empty = self.state.empty();
if self.can_any_piece_move(friendly_pieces, friendly_kings, empty) {
return false;
}
if self.can_any_piece_capture_simple(friendly_pieces, hostile_pieces, empty) {
return false;
}
true
}
const fn can_any_piece_move(
&self,
friendly_pieces: u64,
friendly_kings: u64,
empty: u64,
) -> bool {
if ((friendly_pieces & !MASK_COL_A) >> 1) & empty != 0 {
return true;
}
if ((friendly_pieces & !MASK_COL_H) << 1) & empty != 0 {
return true;
}
let vertical_pawn_moves = match self.turn {
Team::White => ((friendly_pieces & !MASK_ROW_8) << 8) & empty,
Team::Black => ((friendly_pieces & !MASK_ROW_1) >> 8) & empty,
};
if vertical_pawn_moves != 0 {
return true;
}
let backward_king_moves = match self.turn {
Team::White => ((friendly_kings & !MASK_ROW_1) >> 8) & empty,
Team::Black => ((friendly_kings & !MASK_ROW_8) << 8) & empty,
};
backward_king_moves != 0
}
const fn can_any_piece_capture_simple(
&self,
friendly_pieces: u64,
hostile_pieces: u64,
empty: u64,
) -> bool {
let left_captures =
(((friendly_pieces & !(MASK_COL_A | MASK_COL_B)) >> 1) & hostile_pieces) >> 1 & empty;
if left_captures != 0 {
return true;
}
let right_captures =
(((friendly_pieces & !(MASK_COL_G | MASK_COL_H)) << 1) & hostile_pieces) << 1 & empty;
if right_captures != 0 {
return true;
}
let vertical_captures = match self.turn {
Team::White => {
(((friendly_pieces & !(MASK_ROW_7 | MASK_ROW_8)) << 8) & hostile_pieces) << 8
& empty
}
Team::Black => {
(((friendly_pieces & !(MASK_ROW_1 | MASK_ROW_2)) >> 8) & hostile_pieces) >> 8
& empty
}
};
vertical_captures != 0
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Status: {}\nTurn: {}\n{}",
self.status(),
self.turn,
self.state
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{MASK_ROW_2, MASK_ROW_3, MASK_ROW_6};
#[test]
fn new() {
let state = State::new(
[Square::A4.to_mask(), Square::H5.to_mask()],
Square::H5.to_mask(),
);
let board = Board::new(Team::Black, state);
assert_eq!(board.turn, Team::Black);
assert_eq!(board.state, state);
}
#[test]
fn new_default() {
let board = Board::new_default();
assert_eq!(board.turn, Team::White);
assert_eq!(board.state, State::default());
}
#[test]
fn from_squares() {
let friendly_squares = [Square::A1, Square::B2];
let hostile_squares = [Square::C3, Square::D4];
let king_squares = [Square::A1, Square::D4];
let board = Board::from_squares(
Team::Black,
&friendly_squares,
&hostile_squares,
&king_squares,
);
assert_eq!(board.turn, Team::Black);
assert_eq!(
board.friendly_pieces(),
Square::C3.to_mask() | Square::D4.to_mask()
);
assert_eq!(
board.hostile_pieces(),
Square::A1.to_mask() | Square::B2.to_mask()
);
assert_eq!(
board.state.kings,
Square::A1.to_mask() | Square::D4.to_mask()
);
}
#[test]
fn apply() {
let board = Board::from_squares(
Team::White,
&[Square::A2, Square::B2],
&[Square::A3, Square::D4],
&[],
);
let action = Action::new(
Team::White,
Square::A2,
Square::A4,
&[Square::A3],
board.state.kings,
);
let new_board = board.apply(&action);
let expected =
Board::from_squares(Team::White, &[Square::A4, Square::B2], &[Square::D4], &[]);
assert_eq!(new_board, expected);
let mut new_board_ = board;
new_board_.apply_(&action);
assert_eq!(new_board_, new_board);
}
#[test]
fn rotate() {
let board = Board::new(
Team::White,
State::new(
[
MASK_ROW_2 | Square::B3.to_mask(),
MASK_ROW_6 | Square::F5.to_mask(),
],
Square::B3.to_mask() | Square::F5.to_mask(),
),
);
let expected = Board::new(
Team::White,
State::new(
[
MASK_ROW_3 | Square::C4.to_mask(),
MASK_ROW_7 | Square::G6.to_mask(),
],
Square::G6.to_mask() | Square::C4.to_mask(),
),
);
let new_board = board.rotate();
assert_eq!(new_board, expected);
let mut new_board_ = board;
new_board_.rotate_();
assert_eq!(new_board_, new_board);
}
#[test]
fn status_no_friendly_pieces() {
let board = Board::from_squares(
Team::White,
&[],
&[Square::A1, Square::B2, Square::C3, Square::D4],
&[Square::A1],
);
assert_eq!(board.status(), GameStatus::Won(Team::Black));
}
#[test]
fn status_no_hostile_pieces() {
let board = Board::from_squares(
Team::White,
&[Square::A1, Square::B2, Square::C3, Square::D4],
&[],
&[Square::A1],
);
assert_eq!(board.status(), GameStatus::Won(Team::White));
}
#[test]
fn status_draw() {
let board = Board::from_squares(Team::White, &[Square::A1], &[Square::B2], &[Square::A1]);
assert_eq!(board.status(), GameStatus::Draw);
}
#[test]
fn status_in_progress() {
let board = Board::from_squares(
Team::White,
&[Square::A2, Square::B2, Square::C3, Square::D4],
&[Square::E5, Square::F6, Square::G7, Square::H8],
&[Square::C3],
);
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn status_friendly_blocked() {
let board = Board::from_squares(
Team::White,
&[Square::A2, Square::A3],
&[
Square::A4,
Square::A5,
Square::B2,
Square::B3,
Square::C2,
Square::C3,
],
&[],
);
assert_eq!(board.status(), GameStatus::Won(Team::Black));
}
#[test]
fn status_hostile_blocked() {
let board = Board::from_squares(
Team::White,
&[
Square::A2,
Square::A3,
Square::B4,
Square::B5,
Square::C4,
Square::C5,
],
&[Square::A4, Square::A5],
&[],
);
assert_eq!(board.status(), GameStatus::Won(Team::White));
}
#[test]
fn status_king_can_escape_via_flying_capture() {
let board = Board::from_squares(
Team::White,
&[Square::A1],
&[Square::A2, Square::B1, Square::D1],
&[Square::A1], );
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn status_king_blocked_no_flying_capture_possible() {
let board = Board::from_squares(
Team::White,
&[Square::A1],
&[Square::A2, Square::A3, Square::B1, Square::C1],
&[Square::A1], );
assert_eq!(board.status(), GameStatus::Won(Team::Black));
}
#[test]
fn swap_turn_returns_new_board() {
let board = Board::new_default();
let swapped = board.swap_turn();
assert_eq!(swapped.turn, Team::Black);
assert_eq!(board.turn, Team::White); }
#[test]
fn display_format() {
let board = Board::from_squares(Team::White, &[Square::A1], &[Square::H8], &[]);
let display = format!("{}", board);
assert!(display.contains("Turn: White"));
assert!(display.contains("Status:"));
}
#[test]
fn default_board() {
let board = Board::default();
assert_eq!(board, Board::new_default());
}
#[test]
fn status_black_pawn_blocked_can_capture_backward() {
let board = Board::from_squares(Team::Black, &[Square::A7], &[Square::A6, Square::B7], &[]);
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn status_king_can_move_backward() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::C4, Square::E4, Square::D5],
&[Square::D4],
);
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn is_blocked_black_king_only_backward_move() {
let board = Board::from_squares(
Team::Black,
&[Square::A2],
&[Square::A1, Square::B2],
&[Square::A2],
);
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn is_blocked_black_vertical_capture_only() {
let board = Board::from_squares(
Team::Black,
&[Square::A3],
&[Square::A2, Square::B3, Square::C3], &[],
);
assert_eq!(board.status(), GameStatus::InProgress);
}
#[test]
fn is_blocked_left_capture_only() {
let board = Board::from_squares(
Team::White,
&[Square::C3],
&[Square::B3, Square::C4, Square::D3, Square::E3], &[],
);
assert_eq!(board.status(), GameStatus::InProgress);
}
}