From 40086a21f4a60c7405856140e9d86161435d1235 Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Fri, 19 Jan 2024 22:21:02 +0100 Subject: [PATCH] Refactor iterators (#13) * Do not serialize private game data * Put movement logic in chusst-gen::game::play * Allow to backup GameState mobility data * Allow chaining iterators more ergonomically * Fix iterators * Fix take_while_empty_or_enemy() * Fix take_while_empty_until_enemy() --- Cargo.lock | 2 + chusst-gen/Cargo.toml | 2 + chusst-gen/src/board.rs | 37 +- chusst-gen/src/board/bitboards.rs | 5 +- chusst-gen/src/board/compact.rs | 6 +- chusst-gen/src/board/iter.rs | 738 ++++++++++++++++++++++++++ chusst-gen/src/board/simple.rs | 6 +- chusst-gen/src/eval.rs | 80 ++- chusst-gen/src/eval/check.rs | 85 ++- chusst-gen/src/eval/conditions.rs | 93 +--- chusst-gen/src/eval/iter.rs | 312 +++-------- chusst-gen/src/eval/play.rs | 207 +------- chusst-gen/src/eval/tests.rs | 104 ++-- chusst-gen/src/game.rs | 107 +++- chusst-gen/src/game/play.rs | 148 ++++++ chusst-uci/src/main.rs | 2 +- pgn2yaml/src/converter/interpreter.rs | 7 +- src/Board.tsx | 2 - 18 files changed, 1239 insertions(+), 704 deletions(-) create mode 100644 chusst-gen/src/board/iter.rs create mode 100644 chusst-gen/src/game/play.rs diff --git a/Cargo.lock b/Cargo.lock index 2d977c0..062cfff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,8 @@ dependencies = [ "atty", "bencher", "colored", + "lazy_static", + "rand 0.8.5", "serde", ] diff --git a/chusst-gen/Cargo.toml b/chusst-gen/Cargo.toml index 57662da..bebc242 100644 --- a/chusst-gen/Cargo.toml +++ b/chusst-gen/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" anyhow = "1.0.79" atty = "0.2.14" colored = "2.1.0" +lazy_static = "1.4.0" +rand = "0.8.5" serde = { version = "1.0.195", features = ["derive"] } [dev-dependencies] diff --git a/chusst-gen/src/board.rs b/chusst-gen/src/board.rs index 0a911ec..0522d28 100644 --- a/chusst-gen/src/board.rs +++ b/chusst-gen/src/board.rs @@ -1,3 +1,5 @@ +mod iter; + // Board representations #[cfg(feature = "bitboards")] mod bitboards; @@ -13,6 +15,10 @@ pub(crate) use bitboards::PlayerBitboards; pub use compact::CompactBoard; pub use simple::SimpleBoard; +pub use self::iter::{BoardIter, Direction, PositionIter, PositionIterator}; + +use self::iter::{try_move, DirectionIter}; + use atty; use colored::Colorize; use serde::{ser::SerializeMap, ser::SerializeSeq, Serialize}; @@ -408,14 +414,43 @@ macro_rules! pos { pub type Files = [T; 8]; pub type Ranks = [Files; 8]; -pub trait ModifiableBoard { +pub trait ModifiableBoard +where + Self: Sized, +{ fn at(&self, pos: &K) -> V; fn update(&mut self, pos: &K, value: V); fn move_piece(&mut self, source: &K, target: &K); } +pub trait IterableBoard +where + Self: Sized, +{ + fn try_move(&self, position: &Position, direction: &Direction) -> PositionIter { + PositionIter::new(self, try_move(position, direction)) + } + + fn position_iter(&self, position: &Position) -> PositionIter { + PositionIter::new(self, Some(*position)) + } + + fn board_iter(&self) -> BoardIter { + BoardIter::new(self) + } + + fn direction_iterator( + &self, + position: &Position, + direction: &Direction, + ) -> DirectionIter { + DirectionIter::new(self, Some(*position), *direction) + } +} + pub trait Board: ModifiableBoard> + + IterableBoard + Clone + Default + fmt::Debug diff --git a/chusst-gen/src/board/bitboards.rs b/chusst-gen/src/board/bitboards.rs index 3293514..c71a8ba 100644 --- a/chusst-gen/src/board/bitboards.rs +++ b/chusst-gen/src/board/bitboards.rs @@ -4,7 +4,8 @@ mod in_between; use serde::Serialize; use super::{ - format_board, serialize_board, Board, ModifiableBoard, Piece, PieceType, Player, Position, + format_board, serialize_board, Board, IterableBoard, ModifiableBoard, Piece, PieceType, Player, + Position, }; use std::fmt; @@ -378,6 +379,8 @@ impl ModifiableBoard> for Bitboards { } } +impl IterableBoard for Bitboards {} + impl Board for Bitboards { const NEW_BOARD: Self = Bitboards::new(); } diff --git a/chusst-gen/src/board/compact.rs b/chusst-gen/src/board/compact.rs index 4298b33..0e8a368 100644 --- a/chusst-gen/src/board/compact.rs +++ b/chusst-gen/src/board/compact.rs @@ -3,8 +3,8 @@ use std::fmt; use serde::Serialize; use super::{ - format_board, serialize_board, Board, ModifiableBoard, Piece, PieceType, Player, Position, - Ranks, + format_board, serialize_board, Board, IterableBoard, ModifiableBoard, Piece, PieceType, Player, + Position, Ranks, }; use crate::p; @@ -128,6 +128,8 @@ impl ModifiableBoard> for CompactBoard { } } +impl IterableBoard for CompactBoard {} + impl Board for CompactBoard { const NEW_BOARD: Self = CompactBoard::new(); } diff --git a/chusst-gen/src/board/iter.rs b/chusst-gen/src/board/iter.rs new file mode 100644 index 0000000..a6aff03 --- /dev/null +++ b/chusst-gen/src/board/iter.rs @@ -0,0 +1,738 @@ +use super::{ModifiableBoard, Piece, PieceType, Player, Position}; +use crate::pos; +use std::marker::PhantomData; + +#[derive(Copy, Clone)] +pub struct Direction { + pub row_inc: i8, + pub col_inc: i8, +} + +pub fn try_move(position: &Position, direction: &Direction) -> Option { + let row = i8::try_from(position.rank).unwrap() + direction.row_inc; + let col = i8::try_from(position.file).unwrap() + direction.col_inc; + + if !(0..8).contains(&row) { + return None; + } + + if !(0..8).contains(&col) { + return None; + } + + match (usize::try_from(row), usize::try_from(col)) { + (Ok(urow), Ok(ucol)) => Some(pos!(urow, ucol)), + _ => None, + } +} + +pub trait PositionIterator<'a, R>: Iterator + Clone + Sized { + fn representation(&self) -> &R; + + fn only_empty(&self) -> IterateEmpty<'a, Self, R> { + IterateEmpty::new(self.clone()) + } + + fn only_player(&self, player: Player) -> IterateByPlayer<'a, Self, R> { + IterateByPlayer::new(self.clone(), player) + } + + fn only_enemy(&self, player: Player) -> IterateByPlayer<'a, Self, R> { + IterateByPlayer::new(self.clone(), !player) + } + + fn only_piece(&self, piece: PieceType) -> IterateByPiece<'a, Self, R> { + IterateByPiece::new(self.clone(), piece) + } + + fn only_enemy_piece( + &self, + player: Player, + piece: PieceType, + ) -> IterateByPlayerPiece<'a, Self, R> { + IterateByPlayerPiece::new(self.clone(), !player, piece) + } + + fn only_empty_or_enemy(&self, player: Player) -> IteratePlayerOrEmpty<'a, Self, R> { + IteratePlayerOrEmpty::new(self.clone(), !player) + } + + fn take_while_empty(&self) -> IterateWhileEmpty<'a, Self, R> { + IterateWhileEmpty::new(self.clone()) + } + + fn take_while_empty_until_enemy( + &self, + player: Player, + ) -> IterateWhileEmptyUntilPlayer<'a, Self, R> { + IterateWhileEmptyUntilPlayer::new(self.clone(), !player) + } + + fn first_non_empty(&self) -> IterateFirstNonEmpty<'a, Self, R> { + IterateFirstNonEmpty::new(self.clone()) + } +} + +// Generators: + +// PositionIter: iterator over a single position + +pub struct PositionIter<'a, R> { + representation: &'a R, + position: Option, +} + +impl<'a, R> PositionIterator<'a, R> for PositionIter<'a, R> { + fn representation(&self) -> &R { + self.representation + } +} + +impl<'a, R> Clone for PositionIter<'a, R> { + fn clone(&self) -> Self { + Self { + representation: self.representation, + position: self.position, + } + } +} + +impl<'a, R> Iterator for PositionIter<'a, R> { + type Item = Position; + + fn next(&mut self) -> Option { + let current_position = self.position; + self.position = None; + current_position + } +} + +impl<'a, R> PositionIter<'a, R> { + pub fn new(representation: &'a R, position: Option) -> Self { + PositionIter { + representation, + position, + } + } +} + +// DirectionIter: iterator over the positions in a direction + +pub struct DirectionIter<'a, R> { + representation: &'a R, + position: Option, + direction: Direction, +} + +impl<'a, R> PositionIterator<'a, R> for DirectionIter<'a, R> { + fn representation(&self) -> &R { + self.representation + } +} + +impl<'a, R> Clone for DirectionIter<'a, R> { + fn clone(&self) -> Self { + Self { + representation: self.representation, + position: self.position, + direction: self.direction, + } + } +} + +impl<'a, R> Iterator for DirectionIter<'a, R> { + type Item = Position; + + fn next(&mut self) -> Option { + self.position = self + .position + .and_then(|position| try_move(&position, &self.direction)); + self.position + } +} + +impl<'a, R> DirectionIter<'a, R> { + pub fn new(representation: &'a R, position: Option, direction: Direction) -> Self { + DirectionIter { + representation, + position, + direction, + } + } +} + +// BoardIter: iterator over all the positions of a board + +pub struct BoardIter<'a, R> { + representation: &'a R, + position: Position, +} + +impl<'a, R> PositionIterator<'a, R> for BoardIter<'a, R> { + fn representation(&self) -> &R { + self.representation + } +} + +impl<'a, R> Clone for BoardIter<'a, R> { + fn clone(&self) -> Self { + Self { + representation: self.representation, + position: self.position, + } + } +} + +impl<'a, R> Iterator for BoardIter<'a, R> { + type Item = Position; + + fn next(&mut self) -> Option { + if self.position.rank > 7 { + return None; + } + + let current_position = self.position; + + if self.position.file == 7 { + self.position.rank += 1; + self.position.file = 0; + } else { + self.position.file += 1; + } + + Some(current_position) + } +} + +impl<'a, R> BoardIter<'a, R> { + pub fn new(representation: &'a R) -> Self { + BoardIter { + representation, + position: Position { rank: 0, file: 0 }, + } + } +} + +// Filters: + +// IterateEmpty + +pub struct IterateEmpty<'a, I, R> { + iter: I, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateEmpty<'a, I, R> +where + I: PositionIterator<'a, R> + Clone, +{ + pub fn new(iter: I) -> Self { + IterateEmpty { + iter, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + loop { + let position = self.iter.next()?; + if self.iter.representation().at(&position).is_none() { + return Some(position); + } + } + } +} + +// IterateByPlayer + +pub struct IterateByPlayer<'a, I, R> { + iter: I, + player: Player, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateByPlayer<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I, player: Player) -> Self { + IterateByPlayer { + iter, + player, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateByPlayer<'a, I, R> +where + R: ModifiableBoard> + 'a, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateByPlayer<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + player: self.player, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateByPlayer<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + loop { + let position = self.iter.next()?; + match self.iter.representation().at(&position) { + Some(piece) if piece.player == self.player => return Some(position), + _ => continue, + } + } + } +} + +// IterateByPiece + +pub struct IterateByPiece<'a, I, R> { + iter: I, + piece: PieceType, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateByPiece<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I, piece: PieceType) -> Self { + IterateByPiece { + iter, + piece, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateByPiece<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateByPiece<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + piece: self.piece, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateByPiece<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + loop { + let position = self.iter.next()?; + match self.iter.representation().at(&position) { + Some(piece) if piece.piece == self.piece => return Some(position), + _ => continue, + } + } + } +} + +// IterateByPlayerPiece + +pub struct IterateByPlayerPiece<'a, I, R> { + iter: I, + player: Player, + piece: PieceType, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateByPlayerPiece<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I, player: Player, piece: PieceType) -> Self { + IterateByPlayerPiece { + iter, + player, + piece, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateByPlayerPiece<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateByPlayerPiece<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + player: self.player, + piece: self.piece, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateByPlayerPiece<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + loop { + let position = self.iter.next()?; + match self.iter.representation().at(&position) { + Some(piece) if piece.piece == self.piece && piece.player == self.player => { + return Some(position) + } + _ => continue, + } + } + } +} + +// IteratePlayerOrEmpty + +pub struct IteratePlayerOrEmpty<'a, I, R> { + iter: I, + player: Player, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IteratePlayerOrEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I, player: Player) -> Self { + IteratePlayerOrEmpty { + iter, + player, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IteratePlayerOrEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IteratePlayerOrEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + player: self.player, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IteratePlayerOrEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + loop { + let position = self.iter.next()?; + match self.iter.representation().at(&position) { + Some(piece) if piece.player == self.player => return Some(position), + None => return Some(position), + _ => continue, + } + } + } +} + +// IterateWhileEmpty + +pub struct IterateWhileEmpty<'a, I, R> { + iter: I, + stop: bool, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateWhileEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I) -> Self { + IterateWhileEmpty { + iter, + stop: false, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateWhileEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateWhileEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + stop: self.stop, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateWhileEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + if self.stop { + return None; + } + + if let Some(position) = self.iter.next() { + if self.iter.representation().at(&position).is_none() { + return Some(position); + } + } + + self.stop = true; + None + } +} + +// IterateWhileEmptyUntilPlayer + +pub struct IterateWhileEmptyUntilPlayer<'a, I, R> { + iter: I, + player: Player, + stop: bool, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateWhileEmptyUntilPlayer<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I, player: Player) -> Self { + IterateWhileEmptyUntilPlayer { + iter, + player, + stop: false, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateWhileEmptyUntilPlayer<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateWhileEmptyUntilPlayer<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + player: self.player, + stop: self.stop, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateWhileEmptyUntilPlayer<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + if self.stop { + return None; + } + + if let Some(position) = self.iter.next() { + match self.iter.representation().at(&position) { + Some(piece) if piece.player == self.player => { + self.stop = true; + return Some(position); + } + None => return Some(position), + _ => { + self.stop = true; + return None; + } + } + } + + self.stop = true; + None + } +} + +// IterateSkipEmpty + +pub struct IterateFirstNonEmpty<'a, I, R> { + iter: I, + stop: bool, + _unused: PhantomData<&'a R>, +} + +impl<'a, I, R> IterateFirstNonEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + pub fn new(iter: I) -> Self { + IterateFirstNonEmpty { + iter, + stop: false, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> PositionIterator<'a, R> for IterateFirstNonEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + fn representation(&self) -> &R { + self.iter.representation() + } +} + +impl<'a, I, R> Clone for IterateFirstNonEmpty<'a, I, R> +where + I: PositionIterator<'a, R>, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + stop: self.stop, + _unused: PhantomData, + } + } +} + +impl<'a, I, R> Iterator for IterateFirstNonEmpty<'a, I, R> +where + R: ModifiableBoard>, + I: PositionIterator<'a, R>, +{ + type Item = Position; + + fn next(&mut self) -> Option { + if self.stop { + return None; + } + loop { + let position = self.iter.next()?; + if self.iter.representation().at(&position).is_some() { + self.stop = true; + return Some(position); + } + } + } +} diff --git a/chusst-gen/src/board/simple.rs b/chusst-gen/src/board/simple.rs index 361589e..6299c1f 100644 --- a/chusst-gen/src/board/simple.rs +++ b/chusst-gen/src/board/simple.rs @@ -3,8 +3,8 @@ use core::fmt; use serde::Serialize; use super::{ - format_board, serialize_board, Board, ModifiableBoard, Piece, PieceType, Player, Position, - Ranks, + format_board, serialize_board, Board, IterableBoard, ModifiableBoard, Piece, PieceType, Player, + Position, Ranks, }; use crate::p; @@ -81,6 +81,8 @@ impl ModifiableBoard> for SimpleBoard { } } +impl IterableBoard for SimpleBoard {} + impl Board for SimpleBoard { const NEW_BOARD: Self = SimpleBoard::new(); } diff --git a/chusst-gen/src/eval.rs b/chusst-gen/src/eval.rs index bdf9c2a..68ad38f 100644 --- a/chusst-gen/src/eval.rs +++ b/chusst-gen/src/eval.rs @@ -7,16 +7,17 @@ mod play; #[cfg(test)] mod tests; +pub use self::iter::dir; + use self::check::{only_empty_and_safe, SafetyChecks}; -use self::conditions::{try_move, Direction}; pub use self::feedback::{ EngineFeedback, EngineFeedbackMessage, EngineMessage, SilentSearchFeedback, StdoutFeedback, }; use self::feedback::{PeriodicalSearchFeedback, SearchFeedback}; -use self::iter::{dir, piece_into_iter, player_pieces_iter, BoardIter, PlayerPiecesIter}; +use self::iter::piece_into_iter; use self::play::PlayableGame; -use crate::board::{Board, Piece, PieceType, Player, Position, Ranks}; -use crate::game::{GameInfo, GameState, Move, MoveAction, MoveActionType, PromotionPieces}; +use crate::board::{Board, Direction, Piece, PieceType, Position, PositionIterator, Ranks}; +use crate::game::{GameState, ModifiableGame, Move, MoveAction, MoveActionType, PromotionPieces}; use crate::{mv, mva, pos}; use core::panic; @@ -177,19 +178,11 @@ impl PlayableGame for GameState { } fn do_move_no_checks(&mut self, move_action: &MoveAction) -> anyhow::Result<()> { - self.do_move_no_checks_internal(move_action) + ModifiableGame::do_move_no_checks(self, move_action) } } -trait GamePrivate: PlayableGame { - fn board(&self) -> &B; - fn board_mut(&mut self) -> &mut B; - - fn player(&self) -> Player; - fn update_player(&mut self, player: Player); - - fn info(&self) -> &GameInfo; - +trait GamePrivate: PlayableGame + ModifiableGame { // Only for moves from the move generator, not from unknown sources fn clone_and_move_with_checks( &self, @@ -203,8 +196,12 @@ trait GamePrivate: PlayableGame { // Before moving, check if it is a castling and it is valid if is_king && mv.source.file.abs_diff(mv.target.file) == 2 { let is_valid_castling_square = |direction: &Direction| { - only_empty_and_safe(self.board(), try_move(&mv.source, direction), &player) - .is_some() + only_empty_and_safe( + self.board(), + self.try_move(&mv.source, direction).next(), + &player, + ) + .is_some() }; let can_castle = match mv.target.file { // Queenside @@ -339,8 +336,10 @@ trait GamePrivate: PlayableGame { let board = self.board(); let player = self.player(); - let pieces_iter = - player_pieces_iter!(board: board, player: &player).collect::>(); + let pieces_iter = self + .board_iter() + .only_player(player) + .collect::>(); let mut best_move: Option = None; @@ -414,7 +413,7 @@ trait GamePrivate: PlayableGame { mv, branch.score, local_alpha, - beta, + scores.beta, if !is_leaf_node { " {" } else { "" }, ); } @@ -497,7 +496,7 @@ trait GamePrivate: PlayableGame { "{}β cutoff: {} >= {}", indent(current_depth), branch.score, - beta + scores.beta ); } @@ -612,7 +611,7 @@ pub trait Game: GamePrivate { fn get_all_possible_moves(&self) -> Vec { let mut moves: Vec = vec![]; - for piece_position in player_pieces_iter!(board: self.board(), player: &self.player()) { + for piece_position in self.board_iter().only_player(self.player()) { moves.extend(self.get_possible_moves_from_game(piece_position)); } @@ -648,7 +647,7 @@ pub trait Game: GamePrivate { _ => None, }; let tgt_piece_opt = board.at(&mv.target); - let pieces_iter = player_pieces_iter!(board: board, player: player); + let pieces_iter = self.board_iter().only_player(*player); if let Some(piece_char_value) = piece_char(&src_piece.piece) { name.push(piece_char_value); @@ -760,10 +759,9 @@ pub trait Game: GamePrivate { } fn get_possible_captures(&self) -> BoardCaptures { - let board_iter: BoardIter = Default::default(); let mut board_captures: BoardCaptures = Default::default(); - for source_position in board_iter.into_iter() { + for source_position in self.board_iter() { for capture in self.get_possible_captures_of_position(&source_position) { board_captures[capture.rank][capture.file].push(source_position); } @@ -860,10 +858,12 @@ pub trait Game: GamePrivate { } fn do_move(&mut self, mv: &MoveAction) -> Option> { - let enemy_player = &!self.player(); + let enemy_player = !self.player(); let mut enemy_army: HashMap = HashMap::new(); - for piece in player_pieces_iter!(board: self.board(), player: &enemy_player) + for piece in self + .board_iter() + .only_player(enemy_player) .map(|position| self.board().at(&position).unwrap().piece) { match enemy_army.get_mut(&piece) { @@ -879,7 +879,9 @@ pub trait Game: GamePrivate { let result = PlayableGame::do_move_with_checks(self.as_mut(), mv); if result { - for piece in player_pieces_iter!(board: self.board(), player: &enemy_player) + for piece in self + .board_iter() + .only_player(enemy_player) .map(|position| self.board().at(&position).unwrap().piece) { match enemy_army.get_mut(&piece) { @@ -901,7 +903,7 @@ pub trait Game: GamePrivate { .keys() .map(|piece| Piece { piece: *piece, - player: *enemy_player, + player: enemy_player, }) .collect(), ) @@ -911,26 +913,6 @@ pub trait Game: GamePrivate { } } -impl GamePrivate for GameState { - fn board(&self) -> &B { - &self.board - } - - fn board_mut(&mut self) -> &mut B { - &mut self.board - } - - fn player(&self) -> Player { - self.player - } - - fn update_player(&mut self, player: Player) { - self.player = player; - } - - fn info(&self) -> &GameInfo { - &self.info - } -} +impl GamePrivate for GameState {} impl Game for GameState {} diff --git a/chusst-gen/src/eval/check.rs b/chusst-gen/src/eval/check.rs index f1bf89f..2f8272c 100644 --- a/chusst-gen/src/eval/check.rs +++ b/chusst-gen/src/eval/check.rs @@ -2,9 +2,10 @@ use crate::board::CompactBoard; #[cfg(feature = "bitboards")] use crate::board::{Bitboards, ModifiableBoard, PlayerBitboards}; -use crate::board::{Board, Piece, PieceType, Player, Position, SimpleBoard}; -use crate::eval::conditions::{only_empty, try_move, Direction}; -use crate::eval::iter::{dir, into_rolling_board_iterator}; +use crate::board::{ + Board, Direction, Piece, PieceType, Player, Position, PositionIterator, SimpleBoard, +}; +use crate::eval::iter::dir; fn find_king(board: &impl Board, player: &Player) -> Position { match board.iter().find(|position| { @@ -24,42 +25,40 @@ fn is_position_unsafe( player: &Player, enemy_pawn_direction: i8, ) -> bool { - let enemy_player = !*player; - - let is_enemy_piece = |position: &Option, piece: &PieceType| match position { - Some(pos) => match board.at(pos) { - Some(square) => square.piece == *piece && square.player == enemy_player, - None => false, - }, - None => false, + let is_enemy_piece = |direction: Direction, piece: PieceType| { + board + .try_move(position, &direction) + .only_enemy_piece(*player, piece) + .next() + .is_some() }; let enemy_in_direction = |direction: &Direction| { - into_rolling_board_iterator(board, player, position, direction) - .find_map(|pos| board.at(&pos)) + board + .direction_iterator(position, direction) + .first_non_empty() + .only_enemy(*player) + .next() + .and_then(|pos| board.at(&pos)) .map(|piece| piece.piece) }; // 1. Pawns - if is_enemy_piece( - &try_move(position, &dir!(enemy_pawn_direction, -1)), - &PieceType::Pawn, - ) || is_enemy_piece( - &try_move(position, &dir!(enemy_pawn_direction, 1)), - &PieceType::Pawn, - ) { + if is_enemy_piece(dir!(enemy_pawn_direction, -1), PieceType::Pawn) + || is_enemy_piece(dir!(enemy_pawn_direction, 1), PieceType::Pawn) + { return true; } // 2. Knights - if is_enemy_piece(&try_move(position, &dir!(-1, -2)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(-1, 2)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(-2, -1)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(-2, 1)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(2, -1)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(2, 1)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(1, -2)), &PieceType::Knight) - || is_enemy_piece(&try_move(position, &dir!(1, 2)), &PieceType::Knight) + if is_enemy_piece(dir!(-1, -2), PieceType::Knight) + || is_enemy_piece(dir!(-1, 2), PieceType::Knight) + || is_enemy_piece(dir!(-2, -1), PieceType::Knight) + || is_enemy_piece(dir!(-2, 1), PieceType::Knight) + || is_enemy_piece(dir!(2, -1), PieceType::Knight) + || is_enemy_piece(dir!(2, 1), PieceType::Knight) + || is_enemy_piece(dir!(1, -2), PieceType::Knight) + || is_enemy_piece(dir!(1, 2), PieceType::Knight) { return true; } @@ -97,14 +96,14 @@ fn is_position_unsafe( } // 6. King - if is_enemy_piece(&try_move(position, &dir!(-1, -1)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(-1, 0)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(-1, 1)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(0, -1)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(0, 1)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(1, -1)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(1, 0)), &PieceType::King) - || is_enemy_piece(&try_move(position, &dir!(1, 1)), &PieceType::King) + if is_enemy_piece(dir!(-1, -1), PieceType::King) + || is_enemy_piece(dir!(-1, 0), PieceType::King) + || is_enemy_piece(dir!(-1, 1), PieceType::King) + || is_enemy_piece(dir!(0, -1), PieceType::King) + || is_enemy_piece(dir!(0, 1), PieceType::King) + || is_enemy_piece(dir!(1, -1), PieceType::King) + || is_enemy_piece(dir!(1, 0), PieceType::King) + || is_enemy_piece(dir!(1, 1), PieceType::King) { return true; } @@ -130,15 +129,11 @@ pub fn only_empty_and_safe( position: Option, player: &Player, ) -> Option { - match &only_empty(board, position) { - Some(position_value) => { - if is_position_unsafe_generic(board, position_value, player) { - None - } else { - position - } - } - None => None, + let position_value = board.position_iter(&position?).only_empty().next()?; + if is_position_unsafe_generic(board, &position_value, player) { + None + } else { + position } } diff --git a/chusst-gen/src/eval/conditions.rs b/chusst-gen/src/eval/conditions.rs index 7aad30a..20c9ff5 100644 --- a/chusst-gen/src/eval/conditions.rs +++ b/chusst-gen/src/eval/conditions.rs @@ -1,91 +1,14 @@ -use crate::board::{Board, Player, Position}; -use crate::game::{MoveExtraInfo, MoveInfo}; -use crate::pos; +use crate::board::{Board, Direction, IterableBoard, Player, Position, PositionIterator}; +use crate::game::{GameState, MoveExtraInfo, MoveInfo}; -#[derive(Copy, Clone)] -pub struct Direction { - pub row_inc: i8, - pub col_inc: i8, -} - -pub fn try_move(position: &Position, direction: &Direction) -> Option { - let row = i8::try_from(position.rank).unwrap() + direction.row_inc; - let col = i8::try_from(position.file).unwrap() + direction.col_inc; - - if !(0..8).contains(&row) { - return None; - } - - if !(0..8).contains(&col) { - return None; - } - - match (usize::try_from(row), usize::try_from(col)) { - (Ok(urow), Ok(ucol)) => Some(pos!(urow, ucol)), - _ => None, - } -} - -pub fn only_empty(board: &impl Board, position: Option) -> Option { - match board.at(&position?) { - Some(_) => None, - None => position, - } -} - -fn only_player(board: &impl Board, position: Option, player: Player) -> Option { - match board.at(&position?) { - Some(square) => { - if square.player == player { - position - } else { - None - } - } - None => None, - } -} - -pub fn only_enemy( - board: &impl Board, - position: Option, - player: &Player, -) -> Option { - only_player(board, position, !*player) -} - -pub fn only_empty_or_enemy( - board: &impl Board, +pub fn only_en_passant( + game: &GameState, position: Option, + direction: i8, player: &Player, -) -> Option { - match position { - Some(position_value) => match board.at(&position_value) { - // Occupied square - Some(piece) => { - if piece.player != *player { - // Enemy piece - Some(position_value) - } else { - // Friend piece - None - } - } - // Empty square - None => Some(position_value), - }, - None => None, - } -} - -pub fn only_en_passant( - board: &impl Board, last_move: &Option, - position: Option, - player: &Player, - direction: i8, ) -> Option { - match only_empty(board, position) { + match game.position_iter(&position?).only_empty().next() { Some(position_value) => { let reverse_direction = Direction { row_inc: -direction, @@ -94,7 +17,9 @@ pub fn only_en_passant( match ( last_move, - only_enemy(board, try_move(&position_value, &reverse_direction), player), + game.try_move(&position_value, &reverse_direction) + .only_enemy(*player) + .next(), ) { (Some(last_move_info), Some(passed_position)) => { if passed_position == last_move_info.mv.target diff --git a/chusst-gen/src/eval/iter.rs b/chusst-gen/src/eval/iter.rs index cca90da..49c2a8b 100644 --- a/chusst-gen/src/eval/iter.rs +++ b/chusst-gen/src/eval/iter.rs @@ -1,75 +1,11 @@ -use crate::board::{Board, Piece, PieceType, Player, Position}; -use crate::eval::conditions::{ - only_empty, only_empty_or_enemy, only_en_passant, only_enemy, try_move, Direction, +use crate::board::{ + Board, Direction, IterableBoard, ModifiableBoard, Piece, PieceType, Player, Position, + PositionIterator, }; -use crate::game::GameState; -use crate::pos; - -pub struct BoardIter { - position: Position, -} - -impl Default for BoardIter { - fn default() -> Self { - BoardIter { - position: pos!(0, 0), - } - } -} - -impl Iterator for BoardIter { - type Item = Position; - - fn next(&mut self) -> Option { - if self.position.rank > 7 { - return None; - } - - let current_position = self.position; - - if self.position.file == 7 { - self.position.rank += 1; - self.position.file = 0; - } else { - self.position.file += 1; - } - - Some(current_position) - } -} - -pub struct PlayerPiecesIter<'a, B: Board> { - pub(super) board: &'a B, - pub(super) player: &'a Player, - pub(super) board_iter: BoardIter, -} - -macro_rules! player_pieces_iter { - (board: $board:expr, player: $player:expr) => { - PlayerPiecesIter { - board: $board, - player: $player, - board_iter: Default::default(), - } - }; -} -pub(crate) use player_pieces_iter; - -impl<'a, B: Board> Iterator for PlayerPiecesIter<'a, B> { - type Item = Position; - - fn next(&mut self) -> Option { - for position in self.board_iter.by_ref() { - if let Some(Piece { piece: _, player }) = self.board.at(&position) { - if player == *self.player { - return Some(position); - } - } - } - None - } -} +use crate::eval::conditions::only_en_passant; +use crate::game::{CastlingRights, GameState, ModifiableGame}; +#[macro_export] macro_rules! dir { ($row:expr, $col:expr) => { Direction { @@ -78,74 +14,7 @@ macro_rules! dir { } }; } -pub(crate) use dir; - -struct WalkPath { - position: Position, - direction: Direction, - stop_walking: bool, -} - -trait BoardIterator { - fn walk(position: Position, direction: Direction) -> WalkPath; - fn next(&mut self, board: &impl Board, player: &Player) -> Option; -} - -impl BoardIterator for WalkPath { - fn walk(position: Position, direction: Direction) -> WalkPath { - WalkPath { - position, - direction, - stop_walking: false, - } - } - - fn next(&mut self, board: &impl Board, player: &Player) -> Option { - if self.stop_walking { - return None; - } - match only_empty_or_enemy(board, try_move(&self.position, &self.direction), player) { - Some(new_position) => { - self.position = new_position; - match only_empty(board, Some(new_position)) { - Some(_) => Some(new_position), - None => { - self.stop_walking = true; - Some(new_position) - } - } - } - None => None, - } - } -} - -pub struct BoardIteratorAdapter<'a, B: Board> { - board: &'a B, - player: Player, - walker: WalkPath, -} - -impl<'a, B: Board> Iterator for BoardIteratorAdapter<'a, B> { - type Item = Position; - - fn next(&mut self) -> Option { - self.walker.next(self.board, &self.player) - } -} - -pub fn into_rolling_board_iterator<'a>( - board: &'a impl Board, - player: &Player, - position: &Position, - direction: &Direction, -) -> impl Iterator + 'a { - BoardIteratorAdapter { - board, - player: *player, - walker: ::walk(*position, *direction), - } -} +pub use dir; struct PieceIterGameState<'a, B: Board> { game: &'a GameState, @@ -225,17 +94,7 @@ pub struct RollingPieceIter<'a, B: Board, PieceStateEnum> { state: PieceStateEnum, game_state: PieceIterGameState<'a, B>, player: Player, - walker: Option, -} - -impl<'a, B: Board, P> RollingPieceIter<'a, B, P> { - fn walk(&'a self, direction: Direction) -> WalkPath { - WalkPath { - position: self.game_state.position, - direction, - stop_walking: false, - } - } + iter: Option + 'a>>, } type PawnIter<'a, B> = PositionalPieceIter<'a, B, PawnIterStates>; @@ -259,7 +118,7 @@ pub fn piece_into_iter( game: &GameState, position: Position, ) -> impl Iterator + '_ { - let Some(Piece { piece, player }) = game.board.at(&position) else { + let Some(Piece { piece, player }) = game.at(&position) else { // println!("Square {} is empty", position); return PieceIter::EmptySquare(std::iter::empty()); }; @@ -279,19 +138,19 @@ pub fn piece_into_iter( game_state, state: BishopIterStates::BishopIter0, player, - walker: None, + iter: None, }), PieceType::Rook => PieceIter::Rook(RookIter { game_state, state: RookIterStates::RookIter0, player, - walker: None, + iter: None, }), PieceType::Queen => PieceIter::Queen(QueenIter { game_state, state: QueenIterStates::QueenIter0, player, - walker: None, + iter: None, }), PieceType::King => PieceIter::King(KingIter { game_state, @@ -320,7 +179,7 @@ impl<'a, B: Board> Iterator for PawnIter<'a, B> { type Item = Position; fn next(&mut self) -> Option { - let square = self.game_state.game.board.at(&self.game_state.position); + let square = self.game_state.game.at(&self.game_state.position); let player = &square.unwrap().player; let direction = B::pawn_progress_direction(player); let can_pass = match player { @@ -328,65 +187,62 @@ impl<'a, B: Board> Iterator for PawnIter<'a, B> { Player::Black => self.game_state.position.rank == 6, }; loop { + let game = self.game_state.game; let result = match self.state { PawnIterStates::Normal => { self.state = PawnIterStates::Pass; - only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(direction, 0)), - ) + game.try_move(&self.game_state.position, &dir!(direction, 0)) + .only_empty() + .next() } PawnIterStates::Pass => { self.state = PawnIterStates::CaptureLeft; if can_pass - && only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(direction, 0)), - ) - .is_some() + && game + .try_move(&self.game_state.position, &dir!(direction, 0)) + .only_empty() + .next() + .is_some() { - only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(direction * 2, 0)), - ) + game.try_move(&self.game_state.position, &dir!(direction * 2, 0)) + .only_empty() + .next() } else { None } } PawnIterStates::CaptureLeft => { self.state = PawnIterStates::CaptureRight; - only_enemy( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(direction, -1)), - player, - ) + game.try_move(&self.game_state.position, &dir!(direction, -1)) + .only_enemy(*player) + .next() } PawnIterStates::CaptureRight => { self.state = PawnIterStates::CaptureEnPassantLeft; - only_enemy( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(direction, 1)), - player, - ) + game.try_move(&self.game_state.position, &dir!(direction, 1)) + .only_enemy(*player) + .next() } PawnIterStates::CaptureEnPassantLeft => { self.state = PawnIterStates::CaptureEnPassantRight; only_en_passant( - &self.game_state.game.board, - &self.game_state.game.last_move, - try_move(&self.game_state.position, &dir!(direction, -1)), - player, + game, + game.try_move(&self.game_state.position, &dir!(direction, -1)) + .next(), direction, + player, + self.game_state.game.last_move(), ) } PawnIterStates::CaptureEnPassantRight => { self.state = PawnIterStates::End; only_en_passant( - &self.game_state.game.board, - &self.game_state.game.last_move, - try_move(&self.game_state.position, &dir!(direction, 1)), - player, + game, + game.try_move(&self.game_state.position, &dir!(direction, 1)) + .next(), direction, + player, + self.game_state.game.last_move(), ) } PawnIterStates::End => return None, @@ -402,30 +258,35 @@ impl<'a, B: Board> Iterator for PawnIter<'a, B> { macro_rules! positional_state { ($self:ident, $player:expr, $dir:expr => $next_state:expr) => {{ $self.state = $next_state; - only_empty_or_enemy( - &$self.game_state.game.board, - try_move(&$self.game_state.position, &$dir), - $player, - ) + $self + .game_state + .game + .try_move(&$self.game_state.position, &$dir) + .only_empty_or_enemy(*$player) + .next() }}; } macro_rules! walk_state { ($self:ident, $dir:expr => $next_state:expr) => {{ - if $self.walker.is_none() { - $self.walker = Some($self.walk($dir)); - } + let iter = if $self.iter.is_none() { + $self.iter = Some(Box::new( + $self + .game_state + .game + .direction_iterator(&$self.game_state.position, &$dir) + .take_while_empty_until_enemy($self.player), + )); + $self.iter.as_mut().unwrap() + } else { + $self.iter.as_mut().unwrap() + }; - match $self - .walker - .as_mut() - .unwrap() - .next(&$self.game_state.game.board, &$self.player) - { + match iter.next() { Some(position) => Some(position), None => { $self.state = $next_state; - $self.walker = None; + $self.iter = None; None } } @@ -436,7 +297,7 @@ impl<'a, B: Board> Iterator for KnightIter<'a, B> { type Item = Position; fn next(&mut self) -> Option { - let square = self.game_state.game.board.at(&self.game_state.position); + let square = self.game_state.game.at(&self.game_state.position); let player = &square.unwrap().player; loop { let result = match self.state { @@ -574,8 +435,9 @@ impl<'a, B: Board> Iterator for KingIter<'a, B> { type Item = Position; fn next(&mut self) -> Option { - let square = self.game_state.game.board.at(&self.game_state.position); + let square = self.game_state.game.at(&self.game_state.position); let player = &square.unwrap().player; + let game = self.game_state.game; loop { let result = match self.state { KingIterStates::KingIter0 => { @@ -604,41 +466,39 @@ impl<'a, B: Board> Iterator for KingIter<'a, B> { } KingIterStates::KingIterKingsideCastle => { self.state = KingIterStates::KingIterQueensideCastle; - if !self.game_state.game.info.can_castle_kingside(player) { + if !self.game_state.game.can_castle_kingside(*player) { None - } else if only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(0, 1)), - ) - .is_some() + } else if game + .try_move(&self.game_state.position, &dir!(0, 1)) + .only_empty() + .next() + .is_some() { - only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(0, 2)), - ) + game.try_move(&self.game_state.position, &dir!(0, 2)) + .only_empty() + .next() } else { None } } KingIterStates::KingIterQueensideCastle => { self.state = KingIterStates::KingIterEnd; - if !self.game_state.game.info.can_castle_queenside(player) { + if !self.game_state.game.can_castle_queenside(*player) { None - } else if only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(0, -1)), - ) - .is_some() - && only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(0, -3)), - ) + } else if game + .try_move(&self.game_state.position, &dir!(0, -1)) + .only_empty() + .next() .is_some() + && game + .try_move(&self.game_state.position, &dir!(0, -3)) + .only_empty() + .next() + .is_some() { - only_empty( - &self.game_state.game.board, - try_move(&self.game_state.position, &dir!(0, -2)), - ) + game.try_move(&self.game_state.position, &dir!(0, -2)) + .only_empty() + .next() } else { None } diff --git a/chusst-gen/src/eval/play.rs b/chusst-gen/src/eval/play.rs index 3f73fc0..b432bde 100644 --- a/chusst-gen/src/eval/play.rs +++ b/chusst-gen/src/eval/play.rs @@ -1,20 +1,11 @@ -use crate::board::{Board, ModifiableBoard, Piece, PieceType, Position}; -use crate::eval::conditions::{only_enemy, try_move, Direction}; -// use crate::eval::get_possible_moves; -use crate::eval::iter::dir; +use crate::board::Board; use crate::eval::Game; -use crate::game::{GameInfo, GameState, Move, MoveAction, MoveActionType, MoveExtraInfo, MoveInfo}; -use crate::mv; -use anyhow::{bail, Result}; +use crate::game::{GameState, ModifiableGame, MoveAction}; +use anyhow::Result; use super::check::SafetyChecks; -pub struct ReversableMove { - mv: Move, - previous_piece: Option, -} - -pub trait PlayableGame: ModifiableBoard> +pub trait PlayableGame: ModifiableGame where B: SafetyChecks, { @@ -23,7 +14,7 @@ where fn clone_and_move(&self, mv: &MoveAction) -> Result> { let mut new_game = self.as_ref().clone(); - new_game.do_move_no_checks(mv)?; + PlayableGame::do_move_no_checks(&mut new_game, mv)?; Ok(new_game) } @@ -31,12 +22,11 @@ where // because here we don't know if the move is legal, so we check against // all possible legal moves. fn do_move_with_checks(&mut self, move_action: &MoveAction) -> bool { - let board = &self.as_ref().board; let mv = &move_action.mv; - match board.at(&mv.source) { + match self.at(&mv.source) { Some(piece) => { - if piece.player != self.as_ref().player { + if piece.player != self.player() { return false; } } @@ -54,189 +44,8 @@ where return false; } - self.do_move_no_checks(move_action).is_ok() + PlayableGame::do_move_no_checks(self, move_action).is_ok() } fn do_move_no_checks(&mut self, move_action: &MoveAction) -> Result<()>; - - fn do_move_no_checks_internal(&mut self, move_action: &MoveAction) -> Result<()> { - let mv = &move_action.mv; - - let Some(source_square) = self.as_ref().board.at(&mv.source) else { - bail!("Move {} from empty square:\n{}", mv, self.as_ref().board); - }; - - let player = source_square.player; - let moved_piece = source_square.piece; - let move_info = match moved_piece { - PieceType::Pawn => { - if mv.source.rank.abs_diff(mv.target.rank) == 2 { - MoveExtraInfo::Passed - } else if mv.source.file != mv.target.file - && self.as_ref().board.at(&mv.target).is_none() - { - MoveExtraInfo::EnPassant - } else if mv.target.rank == B::promotion_rank(&player) { - let promotion_piece = match move_action.move_type { - MoveActionType::Normal => bail!("Promotion piece not specified"), - MoveActionType::Promotion(piece) => piece, - }; - - MoveExtraInfo::Promotion(promotion_piece) - } else { - MoveExtraInfo::Other - } - } - PieceType::King => { - if mv.source.file.abs_diff(mv.target.file) == 2 { - match mv.target.file { - 2 => MoveExtraInfo::CastleQueenside, - 6 => MoveExtraInfo::CastleKingside, - _ => bail!("invalid castling {} in:\n{}", mv, self.as_ref().board), - } - } else { - MoveExtraInfo::Other - } - } - _ => MoveExtraInfo::Other, - }; - - self.move_piece(&mv.source, &mv.target); - - match move_info { - MoveExtraInfo::EnPassant => { - // Capture passed pawn - let direction = B::pawn_progress_direction(&player); - let passed = only_enemy( - &self.as_ref().board, - try_move(&mv.target, &dir!(-direction, 0)), - &player, - ) - .unwrap(); - self.update(&passed, None); - } - MoveExtraInfo::Promotion(promotion_piece) => { - self.update( - &mv.target, - Some(Piece { - piece: promotion_piece.into(), - player, - }), - ); - } - MoveExtraInfo::CastleKingside => { - let rook_source = try_move(&mv.source, &dir!(0, 3)).unwrap(); - let rook_target = try_move(&mv.source, &dir!(0, 1)).unwrap(); - self.move_piece(&rook_source, &rook_target); - } - MoveExtraInfo::CastleQueenside => { - let rook_source = try_move(&mv.source, &dir!(0, -4)).unwrap(); - let rook_target = try_move(&mv.source, &dir!(0, -1)).unwrap(); - self.move_piece(&rook_source, &rook_target); - } - _ => (), - } - - if moved_piece == PieceType::King { - self.as_mut().info.disable_castle_kingside(&player); - self.as_mut().info.disable_castle_queenside(&player); - } else if moved_piece == PieceType::Rook && mv.source.rank == B::home_rank(&player) { - match mv.source.file { - 0 => self.as_mut().info.disable_castle_queenside(&player), - 7 => self.as_mut().info.disable_castle_kingside(&player), - _ => (), - } - } - - self.as_mut().player = !self.as_ref().player; - self.as_mut().last_move = Some(MoveInfo { - mv: *mv, - info: move_info, - }); - - Ok(()) - } -} - -pub struct ReversableGame<'a, B: Board> { - game: &'a mut GameState, - moves: Vec, - last_move: Option, - info: Option, -} - -impl<'a, B: Board + SafetyChecks> PlayableGame for ReversableGame<'a, B> { - fn as_ref(&self) -> &GameState { - self.game - } - - fn as_mut(&mut self) -> &mut GameState { - self.game - } - - fn do_move_no_checks(&mut self, mv: &MoveAction) -> Result<()> { - let mut moves: Vec = Vec::new(); - PlayableGame::do_move_no_checks_internal(self, mv)?; - self.moves.append(&mut moves); - Ok(()) - } -} - -impl<'a, B: Board + SafetyChecks> ModifiableBoard> - for ReversableGame<'a, B> -{ - fn at(&self, pos: &Position) -> Option { - self.game.board.at(pos) - } - - fn move_piece(&mut self, source: &Position, target: &Position) { - let mv = mv!(*source, *target); - self.moves.push(ReversableMove { - mv, - previous_piece: self.as_ref().board.at(&mv.target), - }); - self.as_mut().board.move_piece(&mv.source, &mv.target); - } - - fn update(&mut self, pos: &Position, value: Option) { - self.moves.push(ReversableMove { - mv: mv!(*pos, *pos), - previous_piece: self.as_ref().board.at(pos), - }); - self.as_mut().board.update(pos, value); - } -} - -impl<'a, B: Board> ReversableGame<'a, B> { - #[allow(dead_code)] - pub fn from(game: &'a mut GameState) -> Self { - let last_move = game.last_move; - let game_info = game.info; - ReversableGame { - game, - moves: vec![], - last_move, - info: Some(game_info), - } - } - - // undo() only used in tests - #[allow(dead_code)] - pub fn undo(&mut self) { - assert!(!self.moves.is_empty()); - - for rev_move in self.moves.iter().rev() { - let mv = &rev_move.mv; - - self.game.board.move_piece(&mv.target, &mv.source); - self.game.board.update(&mv.target, rev_move.previous_piece); - } - - self.moves.clear(); - self.game.player = !self.game.player; - self.game.last_move = self.last_move; - self.game.info = self.info.unwrap(); - self.last_move = None; - self.info = None; - } } diff --git a/chusst-gen/src/eval/tests.rs b/chusst-gen/src/eval/tests.rs index d918be2..98bad3d 100644 --- a/chusst-gen/src/eval/tests.rs +++ b/chusst-gen/src/eval/tests.rs @@ -1,8 +1,11 @@ -use super::play::{PlayableGame, ReversableGame}; +use super::play::PlayableGame; use crate::board::{Board, ModifiableBoard, Piece, PieceType, Player, Position}; use crate::eval::check::SafetyChecks; use crate::eval::Game; -use crate::game::{Move, MoveAction, MoveActionType, PromotionPieces, SimpleGame}; +use crate::game::{ + CastlingRights, GameState, ModifiableGame, Move, MoveAction, MoveActionType, PromotionPieces, + SimpleGame, +}; use crate::{mva, p, pos}; struct PiecePosition { @@ -34,8 +37,8 @@ struct TestBoard<'a> { type TestGame = SimpleGame; -fn custom_board(board_opt: &Option<&str>) -> B { - match board_opt { +fn custom_game(board_opt: &Option<&str>, player: Player) -> GameState { + let mut game = match board_opt { Some(board_str) => { let mut board = B::default(); @@ -78,10 +81,14 @@ fn custom_board(board_opt: &Option<&str>) -> B { None => continue, } } - board + GameState::from(board) } - None => B::NEW_BOARD, - } + None => GameState::from(B::NEW_BOARD), + }; + + game.update_player(player); + + game } #[test] @@ -218,12 +225,7 @@ fn move_reversable() { for test_board in &test_boards { // Prepare board - let mut game = TestGame { - board: custom_board(&test_board.board), - player: Player::White, - last_move: None, - info: Default::default(), - }; + let mut game: TestGame = custom_game(&test_board.board, Player::White); // Do setup moves for mv in &test_board.initial_moves { @@ -231,46 +233,33 @@ fn move_reversable() { game.do_move(mv).is_some(), "move {} failed:\n{}", mv.mv, - game.board + game.board() ); } - let original_board = game.board.clone(); - - let mut rev_game = ReversableGame::from(&mut game); - // Do move assert!( - rev_game.do_move_with_checks(&test_board.mv), + game.do_move_with_checks(&test_board.mv), "failed to make legal move {} in:\n{}", test_board.mv.mv, - game.board + game.board() ); for check in &test_board.checks { assert_eq!( - rev_game.as_ref().at(&check.position), + game.as_ref().at(&check.position), check.piece, "expected {} in {}, found {}:\n{}", check .piece .map_or("nothing".to_string(), |piece| format!("{}", piece.piece)), check.position, - rev_game - .as_ref() + game.as_ref() .at(&check.position) .map_or("nothing".to_string(), |piece| format!("{}", piece.piece)), - rev_game.as_ref().board, + game.board(), ); } - - rev_game.undo(); - - assert_eq!( - game.board, original_board, - "after move {},\nmodified board:\n{}\noriginal board:\n{}", - test_board.mv.mv, game.board, original_board - ); } } @@ -363,17 +352,12 @@ fn check_mate() { for test_board in test_boards { // Prepare board - let mut game = TestGame { - board: custom_board(&test_board.board), - player: Player::Black, - last_move: None, - info: Default::default(), - }; + let mut game: TestGame = custom_game(&test_board.board, Player::Black); - game.info.disable_castle_kingside(&Player::White); - game.info.disable_castle_kingside(&Player::Black); - game.info.disable_castle_queenside(&Player::White); - game.info.disable_castle_queenside(&Player::Black); + game.disable_castle_kingside(Player::White); + game.disable_castle_kingside(Player::Black); + game.disable_castle_queenside(Player::White); + game.disable_castle_queenside(Player::Black); // Do setup moves for mv in &test_board.initial_moves { @@ -381,7 +365,7 @@ fn check_mate() { game.do_move(mv).is_some(), "move {} failed:\n{}", mv.mv, - game.board + game.board() ); } @@ -391,27 +375,26 @@ fn check_mate() { "notation `{}` for move {} doesn't show checkmate sign # in:\n{}", name, test_board.mv.mv, - game.board + game.board() ); // Do move - let mut rev_game = ReversableGame::from(&mut game); assert!( - rev_game.do_move_with_checks(&test_board.mv), + game.do_move_with_checks(&test_board.mv), "invalid move {}:\n{}", test_board.mv.mv, - rev_game.as_ref().board + game.board() ); let possible_moves = game.get_possible_moves(pos!(a1)); - let in_check = game.board.is_piece_unsafe(&pos!(a1)); - assert!(in_check, "king should be in check:\n{}", game.board); + let in_check = game.board().is_piece_unsafe(&pos!(a1)); + assert!(in_check, "king should be in check:\n{}", game.board()); assert!( possible_moves.is_empty(), "unexpected possible move {} in check mate:\n{}", possible_moves.first().unwrap().mv, - game.board + game.board() ); } } @@ -427,7 +410,7 @@ fn fen_parsing() { ); assert!(parsed_game.is_some(), "Failed to parse FEN string"); let game = parsed_game.unwrap(); - assert_eq!(game, TestGame::new(), "\n{}", game.board); + assert_eq!(game, TestGame::new(), "\n{}", game.board()); } // Template to quickly test a specific board/move @@ -455,15 +438,10 @@ fn quick_test() { for test_board in test_boards { // Prepare board - let mut game = TestGame { - board: custom_board(&test_board.board), - player: Player::White, - last_move: None, - info: Default::default(), - }; + let mut game: TestGame = custom_game(&test_board.board, Player::White); - game.info.disable_castle_kingside(&Player::White); - game.info.disable_castle_kingside(&Player::Black); + game.disable_castle_kingside(Player::White); + game.disable_castle_kingside(Player::Black); // Do setup moves for mv in &test_board.initial_moves { @@ -471,18 +449,16 @@ fn quick_test() { game.do_move(mv).is_some(), "move {} failed:\n{}", mv.mv, - game.board + game.board() ); } // Do move - let mut rev_game = ReversableGame::from(&mut game); - assert!( - rev_game.do_move_with_checks(&test_board.mv), + game.do_move_with_checks(&test_board.mv), "invalid move {}:\n{}", test_board.mv.mv, - rev_game.as_ref().board + game.as_ref().board() ); } } diff --git a/chusst-gen/src/game.rs b/chusst-gen/src/game.rs index 67cc2c6..4ff68f9 100644 --- a/chusst-gen/src/game.rs +++ b/chusst-gen/src/game.rs @@ -1,9 +1,14 @@ +mod play; + use crate::board::{Board, ModifiableBoard, Piece, PieceType, Player, Position, SimpleBoard}; use crate::{mv, pos}; - +use anyhow::Result; +use serde::ser::SerializeMap; use serde::Serialize; use std::fmt; +pub use play::ModifiableGame; + #[derive(Copy, Clone, Debug, PartialEq, Serialize)] pub struct Move { pub source: Position, @@ -167,6 +172,13 @@ pub struct MoveInfo { pub info: MoveExtraInfo, } +pub trait CastlingRights { + fn can_castle_kingside(&self, player: Player) -> bool; + fn can_castle_queenside(&self, player: Player) -> bool; + fn disable_castle_kingside(&mut self, player: Player); + fn disable_castle_queenside(&mut self, player: Player); +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize)] pub struct GameInfo { white_kingside_castling_allowed: bool, @@ -184,29 +196,31 @@ impl GameInfo { black_queenside_castling_allowed: true, } } +} - pub fn can_castle_kingside(&self, player: &Player) -> bool { +impl CastlingRights for GameInfo { + fn can_castle_kingside(&self, player: Player) -> bool { match player { Player::White => self.white_kingside_castling_allowed, Player::Black => self.black_kingside_castling_allowed, } } - pub fn can_castle_queenside(&self, player: &Player) -> bool { + fn can_castle_queenside(&self, player: Player) -> bool { match player { Player::White => self.white_queenside_castling_allowed, Player::Black => self.black_queenside_castling_allowed, } } - pub fn disable_castle_kingside(&mut self, player: &Player) { + fn disable_castle_kingside(&mut self, player: Player) { match player { Player::White => self.white_kingside_castling_allowed = false, Player::Black => self.black_kingside_castling_allowed = false, } } - pub fn disable_castle_queenside(&mut self, player: &Player) { + fn disable_castle_queenside(&mut self, player: Player) { match player { Player::White => self.white_queenside_castling_allowed = false, Player::Black => self.black_queenside_castling_allowed = false, @@ -233,21 +247,42 @@ impl Default for GameInfo { } } -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq)] +pub struct GameMobilityData { + player: Player, + last_move: Option, + info: GameInfo, +} + +#[derive(Clone, Debug, PartialEq)] pub struct GameState { - pub(crate) board: B, - pub(crate) player: Player, - pub(crate) last_move: Option, - pub(crate) info: GameInfo, + board: B, + data: GameMobilityData, +} + +impl Serialize for GameState { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + + map.serialize_entry("board", &self.board)?; + map.serialize_entry("player", &self.data.player)?; + + map.end() + } } impl From for GameState { fn from(value: B) -> Self { GameState { board: value, - player: Player::White, - last_move: None, - info: GameInfo::new(), + data: GameMobilityData { + player: Player::White, + last_move: None, + info: GameInfo::new(), + }, } } } @@ -256,18 +291,20 @@ impl GameState { pub const fn new() -> GameState { GameState { board: B::NEW_BOARD, - player: Player::White, - last_move: None, - info: GameInfo::new(), + data: GameMobilityData { + player: Player::White, + last_move: None, + info: GameInfo::new(), + }, } } - pub fn player(&self) -> Player { - self.player + pub fn data(&self) -> &GameMobilityData { + &self.data } - pub fn board(&self) -> &B { - &self.board + pub fn set_data(&mut self, data: &GameMobilityData) { + self.data = data.clone(); } pub fn try_from_fen(fen: &[&str]) -> Option { @@ -332,9 +369,11 @@ impl GameState { Some(GameState { board, - player, - last_move, - info, + data: GameMobilityData { + player, + last_move, + info, + }, }) } } @@ -345,11 +384,29 @@ impl ModifiableBoard> for GameState { } fn update(&mut self, pos: &Position, value: Option) { - self.board.update(pos, value) + self.board.update(pos, value); } fn move_piece(&mut self, source: &Position, target: &Position) { - self.board.move_piece(source, target) + self.board.move_piece(source, target); + } +} + +impl CastlingRights for GameState { + fn can_castle_kingside(&self, player: Player) -> bool { + self.data.info.can_castle_kingside(player) + } + + fn can_castle_queenside(&self, player: Player) -> bool { + self.data.info.can_castle_queenside(player) + } + + fn disable_castle_kingside(&mut self, player: Player) { + self.data.info.disable_castle_kingside(player); + } + + fn disable_castle_queenside(&mut self, player: Player) { + self.data.info.disable_castle_queenside(player); } } diff --git a/chusst-gen/src/game/play.rs b/chusst-gen/src/game/play.rs new file mode 100644 index 0000000..3de0511 --- /dev/null +++ b/chusst-gen/src/game/play.rs @@ -0,0 +1,148 @@ +use super::{ + CastlingRights, GameInfo, GameState, MoveAction, MoveActionType, MoveExtraInfo, MoveInfo, +}; +use crate::board::{ + Board, Direction, IterableBoard, ModifiableBoard, Piece, PieceType, Player, Position, + PositionIterator, +}; +use crate::dir; +use anyhow::{bail, Result}; + +pub trait ModifiableGame: + ModifiableBoard> + CastlingRights + IterableBoard +{ + fn board(&self) -> &B; + fn board_mut(&mut self) -> &mut B; + + fn player(&self) -> Player; + fn update_player(&mut self, player: Player); + + fn info(&self) -> &GameInfo; + + fn last_move(&self) -> &Option; + + fn do_move_no_checks(&mut self, mv: &MoveAction) -> Result<()>; +} + +impl ModifiableGame for GameState { + fn board(&self) -> &B { + &self.board + } + + fn board_mut(&mut self) -> &mut B { + &mut self.board + } + + fn player(&self) -> Player { + self.data.player + } + + fn update_player(&mut self, player: Player) { + self.data.player = player; + } + + fn info(&self) -> &GameInfo { + &self.data.info + } + + fn last_move(&self) -> &Option { + &self.data.last_move + } + + fn do_move_no_checks(&mut self, move_action: &MoveAction) -> Result<()> { + let mv = &move_action.mv; + + let Some(source_square) = self.board.at(&mv.source) else { + bail!("Move {} from empty square:\n{}", mv, self.board); + }; + + let player = source_square.player; + let moved_piece = source_square.piece; + let move_info = match moved_piece { + PieceType::Pawn => { + if mv.source.rank.abs_diff(mv.target.rank) == 2 { + MoveExtraInfo::Passed + } else if mv.source.file != mv.target.file && self.board.at(&mv.target).is_none() { + MoveExtraInfo::EnPassant + } else if mv.target.rank == B::promotion_rank(&player) { + let promotion_piece = match move_action.move_type { + MoveActionType::Normal => bail!("Promotion piece not specified"), + MoveActionType::Promotion(piece) => piece, + }; + + MoveExtraInfo::Promotion(promotion_piece) + } else { + MoveExtraInfo::Other + } + } + PieceType::King => { + if mv.source.file.abs_diff(mv.target.file) == 2 { + match mv.target.file { + 2 => MoveExtraInfo::CastleQueenside, + 6 => MoveExtraInfo::CastleKingside, + _ => bail!("invalid castling {} in:\n{}", mv, self.board), + } + } else { + MoveExtraInfo::Other + } + } + _ => MoveExtraInfo::Other, + }; + + self.move_piece(&mv.source, &mv.target); + + match move_info { + MoveExtraInfo::EnPassant => { + // Capture passed pawn + let direction = B::pawn_progress_direction(&player); + let passed = self + .try_move(&mv.target, &dir!(-direction, 0)) + .only_enemy(player) + .next() + .unwrap(); + self.update(&passed, None); + } + MoveExtraInfo::Promotion(promotion_piece) => { + self.update( + &mv.target, + Some(Piece { + piece: promotion_piece.into(), + player, + }), + ); + } + MoveExtraInfo::CastleKingside => { + let rook_source = self.try_move(&mv.source, &dir!(0, 3)).next().unwrap(); + let rook_target = self.try_move(&mv.source, &dir!(0, 1)).next().unwrap(); + self.move_piece(&rook_source, &rook_target); + } + MoveExtraInfo::CastleQueenside => { + let rook_source = self.try_move(&mv.source, &dir!(0, -4)).next().unwrap(); + let rook_target = self.try_move(&mv.source, &dir!(0, -1)).next().unwrap(); + self.move_piece(&rook_source, &rook_target); + } + _ => (), + } + + if moved_piece == PieceType::King { + self.disable_castle_kingside(player); + self.disable_castle_queenside(player); + } else if moved_piece == PieceType::Rook && mv.source.rank == B::home_rank(&player) { + match mv.source.file { + 0 => self.disable_castle_queenside(player), + 7 => self.disable_castle_kingside(player), + _ => (), + } + } + + self.data.player = !self.data.player; + self.data.last_move = Some(MoveInfo { + mv: *mv, + info: move_info, + }); + + Ok(()) + } +} + +impl IterableBoard for GameState {} diff --git a/chusst-uci/src/main.rs b/chusst-uci/src/main.rs index 35902b0..95d54ff 100644 --- a/chusst-uci/src/main.rs +++ b/chusst-uci/src/main.rs @@ -3,7 +3,7 @@ mod engine; mod stdin; use chusst_gen::eval::GameMove; -use chusst_gen::game::{BitboardGame, MoveAction}; +use chusst_gen::game::{BitboardGame, ModifiableGame, MoveAction}; use engine::{create_engine_thread, EngineCommand, EngineResponse, GoCommand, NewGameCommand}; use stdin::{create_stdin_thread, StdinResponse}; diff --git a/pgn2yaml/src/converter/interpreter.rs b/pgn2yaml/src/converter/interpreter.rs index 854ba2c..1eecdf7 100644 --- a/pgn2yaml/src/converter/interpreter.rs +++ b/pgn2yaml/src/converter/interpreter.rs @@ -1,8 +1,9 @@ -use crate::reader::{Tag, Pgn}; +use crate::reader::{Pgn, Tag}; use anyhow::{bail, Result}; use chusst_gen::{ - board::{Piece, PieceType, ModifiableBoard}, - game::{SimpleGame, MoveAction}, eval::Game, + board::{ModifiableBoard, Piece, PieceType}, + eval::Game, + game::{ModifiableGame, MoveAction, SimpleGame}, }; #[derive(PartialEq)] diff --git a/src/Board.tsx b/src/Board.tsx index ac413c7..a69f704 100644 --- a/src/Board.tsx +++ b/src/Board.tsx @@ -30,8 +30,6 @@ type Move = { type Game = { board: {ranks: SquareType[][]}; player: string; - turn: number; - last_move: Position | null; }; export enum MateType {