use crate::core::{Move10, MovePlanes};
use crate::board::{PieceMapping, Occupied, CapturedBits, init_chess_positions};
use crate::engine::engine_trait::{ChessEngine, MoveError};
use crate::rules::castling::CastlingLogic;
use crate::rules::en_passant::EnPassantLogic;
use crate::rules::checkmate::{CheckmateLogic, Color, GameResult};
use crate::engine::move_generator::MoveGenerator;
pub struct GameCore {
pub planes: MovePlanes,
pub ply: usize,
pub captured_bits: CapturedBits,
pub occupied: Occupied,
pub mapping: PieceMapping,
pub en_passant_target: Option<u8>,
}
impl Default for GameCore {
fn default() -> Self {
let mut mapping = PieceMapping::new_empty();
let mut occupied: u64 = 0;
let mut captured_bits: u32 = 0;
for &(pid, sq) in &init_chess_positions() {
mapping.place_piece(pid, sq);
occupied |= 1u64 << (sq as u64);
}
GameCore {
planes: MovePlanes::new(),
ply: 0,
captured_bits,
occupied,
mapping,
en_passant_target: None,
}
}
}
impl ChessEngine for GameCore {
fn current_mapping(&self) -> &PieceMapping {
&self.mapping
}
fn current_occupied(&self) -> Occupied {
self.occupied
}
fn current_captured_bits(&self) -> CapturedBits {
self.captured_bits
}
fn side_to_move(&self) -> Color {
if self.ply % 2 == 0 {
Color::White
} else {
Color::Black
}
}
fn en_passant_target(&self) -> Option<u8> {
self.en_passant_target
}
fn ply(&self) -> usize {
self.ply
}
fn game_result(&self) -> GameResult {
CheckmateLogic::game_result(
&self.mapping,
self.occupied,
self.side_to_move(),
&self.planes,
self.ply,
self.en_passant_target,
)
}
fn read_ply(&self, i: usize) -> Move10 {
self.planes.read_ply(i)
}
fn generate_pseudo_legal_moves(&self) -> Vec<Move10> {
let side = if self.side_to_move() == Color::White { 0 } else { 1 };
MoveGenerator::generate(
&self.mapping,
self.occupied,
self.captured_bits,
self.en_passant_target,
side,
)
}
fn push_move(&mut self, mv: Move10) -> Result<(), MoveError> {
let side = if self.side_to_move() == Color::White { 0 } else { 1 };
let pid_within = mv.piece_id();
let global_pid = 16 * side + pid_within;
let saved_mapping = self.mapping.clone();
let saved_occupied = self.occupied;
let saved_captured = self.captured_bits;
let saved_ep = self.en_passant_target;
{
let mut temp_mapping = saved_mapping.clone();
let mut temp_occupied = saved_occupied;
let mut temp_captured = saved_captured;
let mut temp_ep = saved_ep;
let src_sq = temp_mapping.piece_square[global_pid as usize].ok_or(MoveError::IllegalMove)? as usize;
let dst_sq = mv.dest() as usize;
let is_castle = pid_within == 15
&& ((side == 0 && ((src_sq == 4 && dst_sq == 6) || (src_sq == 4 && dst_sq == 2)))
|| (side == 1 && ((src_sq == 60 && dst_sq == 62) || (src_sq == 60 && dst_sq == 58))));
if is_castle {
if side == 0 && dst_sq == 6 {
CastlingLogic::do_white_kingside(&mut temp_mapping, &mut temp_occupied);
temp_ep = None;
} else if side == 0 && dst_sq == 2 {
CastlingLogic::do_white_queenside(&mut temp_mapping, &mut temp_occupied);
temp_ep = None;
} else if side == 1 && dst_sq == 62 {
CastlingLogic::do_black_kingside(&mut temp_mapping, &mut temp_occupied);
temp_ep = None;
} else if side == 1 && dst_sq == 58 {
CastlingLogic::do_black_queenside(&mut temp_mapping, &mut temp_occupied);
temp_ep = None;
}
} else {
let is_pawn = pid_within < 8;
if is_pawn {
let sr = (src_sq >> 3) as i8;
let sf = (src_sq & 7) as i8;
let dr = (dst_sq >> 3) as i8;
let df = (dst_sq & 7) as i8;
if (df - sf).abs() == 1 && (dr - sr).abs() == 1 {
if let Some(ep) = temp_ep {
if ep as usize == dst_sq {
let cap_sq = if side == 0 { dst_sq - 8 } else { dst_sq + 8 };
if let Some(opp) = temp_mapping.who_on_square(cap_sq as u8) {
temp_mapping.remove_piece(opp);
temp_occupied &= !(1u64 << cap_sq);
temp_captured |= 1u32 << (opp as u32);
}
}
}
}
}
if let Some(opp) = temp_mapping.who_on_square(dst_sq as u8) {
temp_mapping.remove_piece(opp);
temp_occupied &= !(1u64 << dst_sq);
temp_captured |= 1u32 << (opp as u32);
}
temp_mapping.move_piece(global_pid as u8, dst_sq as u8);
temp_occupied &= !(1u64 << (src_sq as u64));
temp_occupied |= 1u64 << dst_sq;
if pid_within < 8 {
if let Some(new_ep) =
EnPassantLogic::compute_ep_target(src_sq as u8, dst_sq as u8, side)
{
temp_ep = Some(new_ep);
} else {
temp_ep = None;
}
} else {
temp_ep = None;
}
}
let king_pid = if side == 0 { 15 } else { 31 };
if CheckmateLogic::is_in_check(&temp_mapping, temp_occupied, king_pid) {
return Err(MoveError::KingInCheckAfterMove);
}
}
let src_sq = self.mapping.piece_square[global_pid as usize].ok_or(MoveError::IllegalMove)? as usize;
let dst_sq = mv.dest() as usize;
let is_castle = pid_within == 15
&& ((side == 0 && ((src_sq == 4 && dst_sq == 6) || (src_sq == 4 && dst_sq == 2)))
|| (side == 1 && ((src_sq == 60 && dst_sq == 62) || (src_sq == 60 && dst_sq == 58))));
if is_castle {
if side == 0 && dst_sq == 6 {
CastlingLogic::do_white_kingside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
} else if side == 0 && dst_sq == 2 {
CastlingLogic::do_white_queenside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
} else if side == 1 && dst_sq == 62 {
CastlingLogic::do_black_kingside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
} else if side == 1 && dst_sq == 58 {
CastlingLogic::do_black_queenside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
}
} else {
let is_pawn = pid_within < 8;
if is_pawn {
let sr = (src_sq >> 3) as i8;
let sf = (src_sq & 7) as i8;
let dr = (dst_sq >> 3) as i8;
let df = (dst_sq & 7) as i8;
if (df - sf).abs() == 1 && (dr - sr).abs() == 1 {
if let Some(ep) = self.en_passant_target {
if ep as usize == dst_sq {
let cap_sq = if side == 0 { dst_sq - 8 } else { dst_sq + 8 };
if let Some(opp) = self.mapping.who_on_square(cap_sq as u8) {
self.mapping.remove_piece(opp);
self.occupied &= !(1u64 << cap_sq);
self.captured_bits |= 1u32 << (opp as u32);
}
}
}
}
}
if let Some(opp) = self.mapping.who_on_square(dst_sq as u8) {
self.captured_bits |= 1u32 << (opp as u32);
self.mapping.remove_piece(opp);
self.occupied &= !(1u64 << dst_sq);
}
self.mapping.move_piece(global_pid as u8, dst_sq as u8);
self.occupied &= !(1u64 << (src_sq as u64));
self.occupied |= 1u64 << dst_sq;
if is_pawn {
if let Some(new_ep) =
EnPassantLogic::compute_ep_target(src_sq as u8, dst_sq as u8, side)
{
self.en_passant_target = Some(new_ep);
} else {
self.en_passant_target = None;
}
} else {
self.en_passant_target = None;
}
}
self.planes.write_ply(self.ply, Some(mv));
self.ply += 1;
Ok(())
}
fn pop_move(&mut self) {
if self.ply == 0 {
return;
}
self.ply -= 1;
let new_k = self.ply;
self.planes.write_ply(new_k, None);
self.captured_bits = 0;
self.occupied = 0;
self.mapping = PieceMapping::new_empty();
self.en_passant_target = None;
for &(pid, sq) in &init_chess_positions() {
self.mapping.place_piece(pid, sq);
self.occupied |= 1u64 << (sq as u64);
}
for i in 0..new_k {
let mv = self.planes.read_ply(i);
let side = (i % 2) as u8;
let pid_within = mv.piece_id();
let global_pid = 16 * side + pid_within;
let src = self.mapping.piece_square[global_pid as usize].unwrap() as usize;
let dest = mv.dest() as usize;
let is_castle = pid_within == 15
&& ((side == 0 && ((src == 4 && dest == 6) || (src == 4 && dest == 2)))
|| (side == 1 && ((src == 60 && dest == 62) || (src == 60 && dest == 58))));
if is_castle {
if side == 0 && dest == 6 {
CastlingLogic::do_white_kingside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
continue;
} else if side == 0 && dest == 2 {
CastlingLogic::do_white_queenside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
continue;
} else if side == 1 && dest == 62 {
CastlingLogic::do_black_kingside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
continue;
} else if side == 1 && dest == 58 {
CastlingLogic::do_black_queenside(&mut self.mapping, &mut self.occupied);
self.en_passant_target = None;
continue;
}
}
let is_pawn = pid_within < 8;
if is_pawn {
let src_rank = (src >> 3) as i8;
let src_file = (src & 7) as i8;
let dst_rank = (dest >> 3) as i8;
let dst_file = (dest & 7) as i8;
if (dst_file - src_file).abs() == 1 && (dst_rank - src_rank).abs() == 1 {
if let Some(ep_sq) = self.en_passant_target {
if ep_sq as usize == dest {
let captured_sq =
if side == 0 { (dest as i8 - 8) as u8 } else { (dest as i8 + 8) as u8 };
if let Some(opp_pid) = self.mapping.who_on_square(captured_sq) {
self.captured_bits |= 1u32 << (opp_pid as u32);
self.mapping.remove_piece(opp_pid);
self.occupied &= !(1u64 << (captured_sq as u64));
}
}
}
}
}
if let Some(opp_pid) = self.mapping.who_on_square(dest as u8) {
self.captured_bits |= 1u32 << (opp_pid as u32);
self.mapping.remove_piece(opp_pid);
self.occupied &= !(1u64 << (dest as u64));
}
self.mapping.move_piece(global_pid as u8, dest as u8);
self.occupied &= !(1u64 << (src as u64));
self.occupied |= 1u64 << (dest as u64);
if is_pawn {
if let Some(ep_sq) = EnPassantLogic::compute_ep_target(src as u8, dest as u8, side) {
self.en_passant_target = Some(ep_sq);
} else {
self.en_passant_target = None;
}
} else {
self.en_passant_target = None;
}
}
}
fn reset(&mut self) {
*self = GameCore::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::board::{init_chess_positions, encode_piece, encode_square};
#[test]
fn starting_position_correct() {
let engine = GameCore::default();
for &(pid, sq) in &init_chess_positions() {
assert_eq!(engine.mapping.piece_square[pid as usize], Some(sq));
assert_eq!((engine.occupied >> (sq as u64)) & 1u64, 1u64);
}
assert_eq!(engine.ply(), 0);
assert_eq!(engine.en_passant_target, None);
assert_eq!(engine.game_result(), GameResult::Ongoing);
}
}