use crate::othello::{
Bitboard, Position, Stone,
constants::{
BLACK_START_POS, FILE_A, FILE_H, RANK_1, RANK_8, SHIFT_DIRS, SHIFT_MASKS, SHIFT_RAYS,
WHITE_START_POS,
},
display::BoardDisplay,
};
use std::{error, fmt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "ShadowBoard"))]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Board {
black_stones: Bitboard,
white_stones: Bitboard,
}
impl Board {
#[must_use]
pub fn empty() -> Self {
Self {
black_stones: Bitboard::EMPTY,
white_stones: Bitboard::EMPTY,
}
}
#[must_use]
pub fn standard() -> Self {
Self {
black_stones: BLACK_START_POS.into(),
white_stones: WHITE_START_POS.into(),
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.black_stones & self.white_stones == 0
}
pub fn place_stone_unchecked(&mut self, stone: Stone, pos: Bitboard) {
match stone {
Stone::Black => self.black_stones |= pos,
Stone::White => self.white_stones |= pos,
}
}
pub fn remove_stone_unchecked(&mut self, stone: Stone, pos: Bitboard) {
match stone {
Stone::Black => self.black_stones &= !pos,
Stone::White => self.white_stones &= !pos,
}
}
pub fn play(&mut self, stone: Stone, pos: Position) {
let pos: Bitboard = pos.into();
let current_bits = self.bits_for(stone);
let opponent_bits = self.bits_for(stone.flip());
let mut mask = 0;
for (i, shift) in SHIFT_DIRS.iter().enumerate() {
let mut dir_mask = 0;
let shift_mask = SHIFT_MASKS[i] & SHIFT_RAYS[pos.raw().leading_zeros() as usize][i];
let opponent_bits = opponent_bits & shift_mask;
let mut current = pos;
let mut next = current;
while current != 0 {
dir_mask |= current;
next = dir_shift(current, *shift);
current = next & opponent_bits;
}
if next & current_bits != 0 {
mask |= dir_mask ^ pos;
}
}
match stone {
Stone::Black => {
self.black_stones |= mask | pos;
self.white_stones ^= mask;
}
Stone::White => {
self.white_stones |= mask | pos;
self.black_stones ^= mask;
}
}
}
#[must_use]
pub fn bits_for(&self, stone: Stone) -> Bitboard {
match stone {
Stone::Black => self.black_stones,
Stone::White => self.white_stones,
}
}
#[must_use]
pub fn is_legal_move(&self, stone: Stone, pos: Position) -> bool {
let pos = Bitboard::from(pos);
let current_bits = self.bits_for(stone);
let opponent_bits = self.bits_for(stone.flip());
if pos & (current_bits | opponent_bits) != 0 {
return false;
}
for (i, shift) in SHIFT_DIRS.iter().enumerate() {
let mut dir_mask = 0;
let shift_mask = SHIFT_MASKS[i] & SHIFT_RAYS[pos.raw().leading_zeros() as usize][i];
let opponent_bits = opponent_bits & shift_mask;
let mut current = pos;
let mut next = current;
while current != 0 {
dir_mask |= current;
next = dir_shift(current, *shift);
current = next & opponent_bits;
}
if next & current_bits != 0 && dir_mask ^ pos != 0 {
return true;
}
}
false
}
#[must_use]
pub fn moves_for(&self, stone: Stone) -> Bitboard {
let current_bits = self.bits_for(stone);
let opponent_bits = self.bits_for(stone.flip());
let empty_squares = self.empty_squares();
let move_in_dir = |mask: u64, shift: i8| {
let excluded: Bitboard = opponent_bits & mask;
let mut m: Bitboard = dir_shift(current_bits, shift) & excluded;
m |= dir_shift(m, shift) & excluded;
m |= dir_shift(m, shift) & excluded;
m |= dir_shift(m, shift) & excluded;
m |= dir_shift(m, shift) & excluded;
m |= dir_shift(m, shift) & excluded;
dir_shift(m, shift) & empty_squares
};
let exclude_top_bottom = !(RANK_1 | RANK_8);
let exclude_left_right = !(FILE_A | FILE_H);
let mut moves = move_in_dir(exclude_top_bottom, -8);
moves |= move_in_dir(exclude_left_right, -7);
moves |= move_in_dir(exclude_left_right, 1);
moves |= move_in_dir(exclude_left_right, 9);
moves |= move_in_dir(exclude_top_bottom, 8);
moves |= move_in_dir(exclude_left_right, 7);
moves |= move_in_dir(exclude_left_right, -1);
moves | move_in_dir(exclude_left_right, -9)
}
#[must_use]
pub fn empty_squares(&self) -> Bitboard {
!(self.black_stones | self.white_stones)
}
#[must_use]
pub fn stone_at(&self, pos: Position) -> Option<Stone> {
if self.black_stones & pos > 0 {
Some(Stone::Black)
} else if self.white_stones & pos > 0 {
Some(Stone::White)
} else {
None
}
}
#[must_use]
pub fn display(&'_ self) -> BoardDisplay<'_> {
BoardDisplay::new(self)
}
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[cfg(feature = "serde")]
struct ShadowBoard {
black_stones: u64,
white_stones: u64,
}
#[cfg(feature = "serde")]
impl std::convert::TryFrom<ShadowBoard> for Board {
type Error = BoardError;
fn try_from(unchecked: ShadowBoard) -> Result<Self, Self::Error> {
Board::try_from((unchecked.black_stones, unchecked.white_stones))
}
}
impl Default for Board {
fn default() -> Self {
Self::standard()
}
}
impl TryFrom<(u64, u64)> for Board {
type Error = BoardError;
fn try_from(stones: (u64, u64)) -> Result<Self, Self::Error> {
let (black_stones, white_stones) = stones;
if black_stones & white_stones != 0 {
return Err(BoardError::OverlappingPieces);
}
let board = Self {
black_stones: black_stones.into(),
white_stones: white_stones.into(),
};
Ok(board)
}
}
impl TryFrom<(Bitboard, Bitboard)> for Board {
type Error = BoardError;
fn try_from(stones: (Bitboard, Bitboard)) -> Result<Self, Self::Error> {
let (black_stones, white_stones) = stones;
Board::try_from((black_stones.raw(), white_stones.raw()))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum BoardError {
OverlappingPieces,
}
impl fmt::Display for BoardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OverlappingPieces => write!(f, "overlapping pieces"),
}
}
}
impl error::Error for BoardError {}
fn dir_shift(x: Bitboard, shift: i8) -> Bitboard {
if shift > 0 { x >> shift } else { x << -shift }
}