use std::fmt;
use crate::physical::board::{self, Board, Coordinate, Piece};
use crate::bot::ToUCI;
pub trait Action: fmt::Debug + fmt::Display + ToUCI + Send {
fn perform_on(&mut self, game: &mut Board); fn undo_on(&self, game: &mut Board);
fn to_coordinate(&self) -> Option<Coordinate>;
fn is_illegal(&mut self, game: &mut Board) -> bool;
fn duplicate(&self) -> Box<dyn Action>;
fn evaluate(&self) -> f64;
}
#[derive(Copy, Clone)]
pub struct Move {
pub(crate) from: Coordinate,
pub(crate) to: Coordinate,
en_passant: Option<Coordinate>,
value: u32,
pub(crate) promotion: Option<board::PieceType>,
captured_type: Option<board::PieceType>,
piece_first_move: bool,
}
impl fmt::Display for Move {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Move from {} to {}",
self.from, self.to
)
}
}
impl fmt::Debug for Move {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Move from {:?} to {:?} with value {} [DEBUG]", self.from, self.to, self.value)
}
}
impl Action for Move {
fn to_coordinate(&self) -> Option<Coordinate> {
Some(self.to)
}
fn is_illegal(&mut self, game: &mut Board) -> bool {
let moving_piece = match game.square(&self.from).piece {
Some(piece) => piece,
None => {
eprintln!(
"Move::is_illegal(): Returning true because there is no piece where this move is from ({:?})",
self.from
);
return true;
}
};
self.perform_on(game);
let result = game.is_check(moving_piece.color);
self.undo_on(game);
result
}
fn perform_on(&mut self, game: &mut Board) {
let from_square = game.mut_square(&self.from);
let mut moving_piece = from_square.piece.unwrap_or_else(|| {
panic!("{self} isn't legal because it is from a square without a piece.")
});
moving_piece.has_moved = true;
if let Some(promo_type) = self.promotion {
moving_piece.ptype = promo_type;
}
let to_square = game.square(&self.to);
if let Some(captured_piece) = to_square.piece {
self.captured_type = Some(captured_piece.ptype);
}
game.turn = moving_piece.color.opposite();
if game.turn == board::Color::White {
game.round += 1;
}
if moving_piece.ptype.value() == 1 && self.is_two_square_pawn_move() {
moving_piece.ptype = board::PieceType::Pawn(game.round);
}
game.remove_on(&self.from);
game.put_piece_on(&self.to, moving_piece);
if let Some(ep_loc) = self.en_passant {
game.remove_on(&ep_loc);
}
game.update();
}
fn undo_on(&self, game: &mut Board) {
game.round -= 1;
let from_square = game.mut_square(&self.to);
let mut moving_piece = from_square.piece.unwrap_or_else(|| {
panic!("Undoing {self} isn't possible because there is no piece on the to square.")
});
if self.promotion.is_some() {
moving_piece.ptype = board::PieceType::Pawn(0); }
moving_piece.has_moved = !self.piece_first_move;
if moving_piece.ptype.value() == 1 && self.is_two_square_pawn_move() {
moving_piece.ptype = board::PieceType::Pawn(0);
}
game.turn = game.turn.opposite();
if game.turn == board::Color::Black {
game.round -= 1;
}
game.remove_on(&self.to);
game.put_piece_on(&self.from, moving_piece);
if let Some(ep_loc) = self.en_passant {
game.put_piece_on(
&ep_loc,
Piece {
color: if moving_piece.color == board::Color::White {
board::Color::Black
} else {
board::Color::White
},
ptype: board::PieceType::Pawn(game.round),
location: ep_loc,
has_moved: false,
},
);
}
if let Some(captured_type) = self.captured_type {
let restored_piece = Piece {
color: if moving_piece.color == board::Color::White {
board::Color::Black
} else {
board::Color::White
},
ptype: captured_type,
location: self.to,
has_moved: false,
};
game.put_piece_on(&self.to, restored_piece);
}
game.update();
}
fn duplicate(&self) -> Box<dyn Action> {
Box::new(Self {
..*self
})
}
fn evaluate(&self) -> f64 {
self.value as f64
}
}
impl PartialEq for Move {
fn eq(&self, other: &Self) -> bool {
self.from == other.from && self.to == other.to
}
}
impl Move {
pub fn new(
from: Coordinate,
to: board::Coordinate,
game: &Board,
promotion: Option<board::PieceType>,
en_passant: bool,
) -> Self {
let mut piece_first_move = false;
let mut moving_piece_color = None;
let moving_piece = game.square(&from).piece;
if let Some(piece) = moving_piece {
piece_first_move = !piece.has_moved;
moving_piece_color = Some(piece.color);
} else {
eprintln!("WARNING: Creating move from {from}, which doesn't have a piece.");
}
let en_passant_location = if en_passant {
let direction: i32 = if from.row < to.row { -1 } else { 1 };
if let Ok(loc) = to.with_offset(direction, 0) {
match game.square(&loc).piece {
Some(piece) => {
if piece.ptype == board::PieceType::Pawn(game.round) && Some(piece.color) != moving_piece_color {
Some(loc)
} else {
None
}
}
None => {
None
}
}
} else {
None
}
} else {
None
};
let value = if let Some(ptype) = promotion {
ptype.value() - 1
} else {
let to_square = game.square(&to);
if let Some(piece) = to_square.piece {
piece.ptype.value()
} else {
0
}
};
Move {
from,
to,
en_passant: en_passant_location,
value: value as u32,
promotion,
captured_type: None,
piece_first_move,
}
}
fn is_two_square_pawn_move(&self) -> bool {
let mut rise = self.to.row as i32 - self.from.row as i32;
rise = rise.abs();
rise == 2
}
}
#[derive(PartialEq, Copy, Clone)]
pub enum CastleSide {
KingSide,
QueenSide,
}
impl fmt::Display for CastleSide {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let phrase = match self {
Self::KingSide => "King Side",
Self::QueenSide => "Queen Side",
};
write!(f, "{phrase}")
}
}
impl CastleSide {
pub fn king_end_col(&self) -> usize {
match self {
Self::KingSide => 6,
Self::QueenSide => 2,
}
}
pub fn rook_start_col(&self) -> usize {
match self {
Self::KingSide => 7,
Self::QueenSide => 0,
}
}
fn rook_end_col(&self) -> usize {
match self {
Self::KingSide => 5,
Self::QueenSide => 3,
}
}
fn squares_that_must_be_empty(&self) -> Vec<usize> {
match self {
Self::KingSide => vec![6, 5],
Self::QueenSide => vec![1, 2, 3],
}
}
}
#[derive(PartialEq, Clone, Copy)]
pub struct Castle {
pub(crate) side: CastleSide,
pub(crate) player: board::Color,
}
impl fmt::Display for Castle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {} castles", self.player, self.side)
}
}
impl fmt::Debug for Castle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self} [DEBUG]")
}
}
impl Action for Castle {
fn to_coordinate(&self) -> Option<Coordinate> {
None
}
fn perform_on(&mut self, game: &mut Board) {
let row = self.row();
let king_pos = Coordinate::new(4, row);
let king_target = Coordinate::new(self.side.king_end_col(), row);
let rook_pos = Coordinate::new(self.side.rook_start_col(), row);
let rook_target = Coordinate::new(self.side.rook_end_col(), row);
game.move_from(&king_pos, &king_target);
game.move_from(&rook_pos, &rook_target);
game.turn = game.square(&rook_target).piece.unwrap().color.opposite();
if game.turn == board::Color::White {
game.round += 1;
}
game.update();
}
fn undo_on(&self, game: &mut Board) {
let row = self.row();
let king_target = Coordinate::new(4, row);
let king_pos = Coordinate::new(self.side.king_end_col(), row);
let rook_pos = Coordinate::new(self.side.rook_end_col(), row);
let rook_target = Coordinate::new(self.side.rook_start_col(), row);
game.move_from(&king_pos, &king_target);
game.move_from(&rook_pos, &rook_target);
game.turn = game.turn.opposite();
if game.turn == board::Color::Black {
game.round -= 1;
}
game.mut_square(&king_target).piece.unwrap().has_moved = false;
game.mut_square(&rook_target).piece.unwrap().has_moved = false;
game.update();
}
fn is_illegal(&mut self, game: &mut Board) -> bool {
let row = self.row();
let king_loc = Coordinate::new(4, row);
let expected_king = Piece {
color: self.player,
ptype: board::PieceType::King,
location: king_loc,
has_moved: false,
};
let king_square = game.square(&king_loc);
let king = match king_square.piece {
Some(king) => king,
None => return true,
};
if king != expected_king {
return true;
}
if game.is_check(king.color) {
return true;
}
let rook_loc = Coordinate::new(self.side.rook_start_col(), row);
let expected_rook = Piece {
color: self.player,
ptype: board::PieceType::Rook,
location: rook_loc,
has_moved: false,
};
let rook_square = game.square(&rook_loc);
let rook = match rook_square.piece {
Some(rook) => rook,
None => return true,
};
if rook != expected_rook {
return true;
}
for col in self.side.squares_that_must_be_empty() {
let coord = Coordinate::new(col, row);
let opponent_threats = match self.player {
board::Color::White => &game.move_info.black_threatened_squares,
board::Color::Black => &game.move_info.white_threatened_squares,
};
if opponent_threats.contains(&coord) {
return true;
}
let square = game.square(&coord);
if square.piece.is_some() {
return true;
}
}
false
}
fn duplicate(&self) -> Box<dyn Action> {
Box::new(Self {
..*self
})
}
fn evaluate(&self) -> f64 {
2.0
}
}
impl Castle {
pub fn new(side: CastleSide, player: board::Color) -> Self {
Castle { side, player }
}
fn row(&self) -> usize {
match self.player {
board::Color::White => 0,
board::Color::Black => 7,
}
}
}
#[derive(Debug)]
pub struct MoveInfo {
pub(crate) white_threatened_squares: Vec<Coordinate>,
pub(crate) black_threatened_squares: Vec<Coordinate>,
pub(crate) white_potential_moves: Vec<Box<dyn Action>>,
pub(crate) black_potential_moves: Vec<Box<dyn Action>>,
}
impl MoveInfo {
pub fn new() -> Self {
Self {
white_threatened_squares: Vec::new(),
black_threatened_squares: Vec::new(),
white_potential_moves: Vec::new(),
black_potential_moves: Vec::new(),
}
}
pub fn from(game: &Board) -> Self {
let mut info = Self::new();
info.update(game);
info
}
pub fn update(&mut self, game: &Board) {
self.white_potential_moves = game.white_potential_moves();
self.black_potential_moves = game.black_potential_moves();
self.white_threatened_squares = Vec::new();
for m in self.white_potential_moves.iter() {
if let Some(coord) = m.to_coordinate() {
self.white_threatened_squares.push(coord);
}
}
self.black_threatened_squares = Vec::new();
for m in self.black_potential_moves.iter() {
if let Some(coord) = m.to_coordinate() {
self.black_threatened_squares.push(coord);
}
}
}
}