use std::fmt;
use crate::Team;
pub(crate) const MASK_ROW_1: u64 = 0x0000_0000_0000_00FF;
pub(crate) const MASK_ROW_2: u64 = 0x0000_0000_0000_FF00;
#[allow(dead_code)]
pub(crate) const MASK_ROW_3: u64 = 0x0000_0000_00FF_0000;
#[allow(dead_code)]
pub(crate) const MASK_ROW_4: u64 = 0x0000_0000_FF00_0000;
#[allow(dead_code)]
pub(crate) const MASK_ROW_5: u64 = 0x0000_00FF_0000_0000;
#[allow(dead_code)]
pub(crate) const MASK_ROW_6: u64 = 0x0000_FF00_0000_0000;
pub(crate) const MASK_ROW_7: u64 = 0x00FF_0000_0000_0000;
pub(crate) const MASK_ROW_8: u64 = 0xFF00_0000_0000_0000;
pub(crate) const MASK_COL_A: u64 = 0x0101_0101_0101_0101;
pub(crate) const MASK_COL_B: u64 = 0x0202_0202_0202_0202;
#[allow(dead_code)]
pub(crate) const MASK_COL_C: u64 = 0x0404_0404_0404_0404;
#[allow(dead_code)]
pub(crate) const MASK_COL_D: u64 = 0x0808_0808_0808_0808;
#[allow(dead_code)]
pub(crate) const MASK_COL_E: u64 = 0x1010_1010_1010_1010;
#[allow(dead_code)]
pub(crate) const MASK_COL_F: u64 = 0x2020_2020_2020_2020;
pub(crate) const MASK_COL_G: u64 = 0x4040_4040_4040_4040;
pub(crate) const MASK_COL_H: u64 = 0x8080_8080_8080_8080;
pub(crate) const MASK_ROW_PROMOTIONS: [u64; 2] = [MASK_ROW_8, MASK_ROW_1];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct State {
pub pieces: [u64; 2],
pub kings: u64,
}
impl State {
#[must_use]
pub const fn new(pieces: [u64; 2], kings: u64) -> Self {
Self { pieces, kings }
}
#[must_use]
pub const fn default() -> Self {
let whites = MASK_ROW_2 | MASK_ROW_3;
let blacks = MASK_ROW_6 | MASK_ROW_7;
Self {
pieces: [whites, blacks],
kings: 0u64,
}
}
#[must_use]
pub const fn zeros() -> Self {
Self {
pieces: [0u64, 0u64],
kings: 0u64,
}
}
}
impl State {
pub fn validate(&self) {
debug_assert_eq!(
self.pieces[0] & self.pieces[1],
0,
"a single square is occupied by both teams"
);
debug_assert_eq!(
self.kings,
self.kings & (self.pieces[0] | self.pieces[1]),
"an empty square is marked as king"
);
for team_index in 0..=1 {
let team = Team::from_usize(team_index);
debug_assert!(
self.pieces[team_index].count_ones() <= 16,
"more than 16 pieces for {team}",
);
}
}
#[inline(always)]
#[must_use]
pub const fn empty(&self) -> u64 {
!(self.pieces[0] | self.pieces[1])
}
#[inline(always)]
pub const fn apply_(&mut self, other: &Self) {
self.pieces[0] ^= other.pieces[0];
self.pieces[1] ^= other.pieces[1];
self.kings ^= other.kings;
}
#[inline(always)]
#[must_use]
pub fn apply(&self, other: &Self) -> Self {
let mut new_state = *self;
new_state.apply_(other);
new_state
}
#[inline]
pub const fn rotate_(&mut self) {
let tmp_pieces = self.pieces[0];
self.pieces[0] = self.pieces[1].reverse_bits();
self.pieces[1] = tmp_pieces.reverse_bits();
self.kings = self.kings.reverse_bits();
}
#[inline]
#[must_use]
pub fn rotate(&self) -> Self {
let mut new_state = *self;
new_state.rotate_();
new_state
}
}
impl Default for State {
fn default() -> Self {
Self::default()
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut result = " A B C D E F G H\n".to_string();
for row in (0..8).rev() {
let mut line = format!("{} ", row + 1);
for col in 0..8 {
let index: u8 = row * 8 + col;
let mut ch = '.';
if (self.pieces[0] >> index) & 1u64 == 1u64 {
ch = 'w';
} else if (self.pieces[1] >> index) & 1u64 == 1u64 {
ch = 'b';
}
if (self.kings >> index) & 1u64 == 1u64 {
ch = ch.to_ascii_uppercase();
}
line += &format!(" {ch}");
}
line += &format!(" {}\n", row + 1);
result += &line;
}
result += " A B C D E F G H";
write!(f, "{result}")
}
}
#[cfg(test)]
mod tests {
use crate::Square;
use super::*;
#[test]
fn new() {
let whites = MASK_ROW_2;
let blacks = MASK_ROW_7;
let kings = MASK_ROW_2;
let state = State::new([whites, blacks], kings);
assert_eq!(state.pieces[Team::White.to_usize()], whites);
assert_eq!(state.pieces[Team::Black.to_usize()], blacks);
assert_eq!(state.kings, kings);
}
#[test]
fn default() {
let state = State::default();
assert_eq!(
state.pieces[Team::White.to_usize()],
MASK_ROW_2 | MASK_ROW_3
);
assert_eq!(
state.pieces[Team::Black.to_usize()],
MASK_ROW_6 | MASK_ROW_7
);
assert_eq!(state.kings, 0u64);
}
#[test]
fn default_trait() {
let state_inherent = State::default();
let state_trait: State = Default::default();
assert_eq!(state_inherent, state_trait);
}
#[test]
fn zeros() {
let state = State::zeros();
assert_eq!(state.pieces[Team::White.to_usize()], 0u64);
assert_eq!(state.pieces[Team::Black.to_usize()], 0u64);
assert_eq!(state.kings, 0u64);
}
#[test]
fn validate_default() {
let state = State::default();
state.validate();
}
#[test]
#[should_panic(expected = "a single square is occupied by both teams")]
fn invalid_validate_common_pieces() {
let whites = MASK_ROW_2 | MASK_ROW_3;
let blacks = MASK_ROW_3 | MASK_ROW_4;
let kings = 0u64;
let state = State::new([whites, blacks], kings);
state.validate();
}
#[test]
#[should_panic(expected = "an empty square is marked as king")]
fn invalid_validate_empty_square_as_king() {
let whites = MASK_ROW_2;
let blacks = MASK_ROW_3;
let kings = MASK_ROW_1 | MASK_ROW_2;
let state = State::new([whites, blacks], kings);
state.validate();
}
#[test]
#[should_panic(expected = "more than 16 pieces for White")]
fn invalid_validate_more_than_16_white_pieces() {
let whites = MASK_ROW_2 | MASK_ROW_3 | Square::A4.to_mask();
let blacks = MASK_ROW_7;
let kings = 0u64;
let state = State::new([whites, blacks], kings);
state.validate();
}
#[test]
#[should_panic(expected = "more than 16 pieces for Black")]
fn invalid_validate_more_than_16_black_pieces() {
let whites = MASK_ROW_2;
let blacks = MASK_ROW_6 | MASK_ROW_7 | Square::A5.to_mask();
let kings = 0u64;
let state = State::new([whites, blacks], kings);
state.validate();
}
#[test]
fn empty() {
let state = State::default();
assert_eq!(
state.empty(),
!(MASK_ROW_2 | MASK_ROW_3 | MASK_ROW_6 | MASK_ROW_7)
);
}
#[test]
fn apply() {
let state = State::default();
let transformation = State::new([MASK_ROW_2, MASK_ROW_7], MASK_ROW_3); let new_state = state.apply(&transformation);
assert_eq!(new_state.pieces[Team::White.to_usize()], MASK_ROW_3);
assert_eq!(new_state.pieces[Team::Black.to_usize()], MASK_ROW_6);
assert_eq!(new_state.kings, MASK_ROW_3);
let mut new_state_ = state;
new_state_.apply_(&transformation);
assert_eq!(new_state_, new_state);
}
#[test]
fn rotate() {
let state = 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 = 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_state = state.rotate();
assert_eq!(new_state, expected);
let mut new_state_ = state;
new_state_.rotate_();
assert_eq!(new_state_, new_state);
}
#[test]
fn fmt() {
let state = State::default();
let expected = " A B C D E F G H\n8 . . . . . . . . 8\n7 b b b b b b b b 7\n6 b b b b b b b b 6\n5 . . . . . . . . 5\n4 . . . . . . . . 4\n3 w w w w w w w w 3\n2 w w w w w w w w 2\n1 . . . . . . . . 1\n A B C D E F G H";
let result = format!("{state}");
assert_eq!(result, expected);
}
#[test]
fn fmt_with_kings() {
let state = State::new(
[Square::A1.to_mask(), Square::H8.to_mask()],
Square::A1.to_mask() | Square::H8.to_mask(),
);
let result = format!("{state}");
assert!(result.contains('W'), "White king should display as 'W'");
assert!(result.contains('B'), "Black king should display as 'B'");
}
}