1use core::fmt;
2use std::rc::Rc;
3
4use ahash;
5use log;
6
7use crate::engine;
8use crate::errors::BoardStateError;
9use crate::errors::PGNParseError;
10use crate::fen::FEN;
11use crate::log_and_return_error;
12use crate::movegen::*;
13use crate::pgn;
14use crate::pgn::notation::Notation;
15use crate::pgn::tag::Tag;
16use crate::position::*;
17use crate::transposition;
18use crate::util;
19use crate::zobrist;
20use crate::zobrist::PositionHash;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum GameState {
24 Check,
25 Checkmate,
26 Stalemate,
27 Repetition,
28 FiftyMove,
29 InsufficientMaterial,
30 Active,
31}
32impl GameState {
33 #[inline]
35 pub fn is_draw(&self) -> bool {
36 matches!(
37 self,
38 Self::Stalemate | Self::FiftyMove | Self::Repetition | Self::InsufficientMaterial
39 )
40 }
41 #[inline]
43 pub fn is_win(&self) -> bool {
44 matches!(self, Self::Checkmate)
45 }
46 #[inline]
48 pub fn is_game_over(&self) -> bool {
49 self.is_win() || self.is_draw()
50 }
51}
52impl fmt::Display for GameState {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let state_str = match self {
56 Self::Check => "Check",
57 Self::Checkmate => "Checkmate",
58 Self::Stalemate => "Stalemate",
59 Self::Repetition => "Repetition",
60 Self::FiftyMove => "Fifty Move Draw",
61 Self::InsufficientMaterial => "Insufficient Material",
62 Self::Active => "",
63 };
64 write!(f, "{}", state_str)
65 }
66}
67
68#[derive(Debug, Clone)]
69pub struct BoardState {
70 pub side_to_move: PieceColour,
71 pub last_move: Move,
72 legal_moves: Vec<Move>,
73 pub board_hash: u64,
74 pub position_hash: u64,
75 position: Position,
76 move_count: u32,
77 halfmove_count: u32,
78 position_occurences: ahash::AHashMap<PositionHash, u8>,
79 lazy_legal_moves: bool,
80}
81
82impl PartialEq for BoardState {
83 fn eq(&self, other: &Self) -> bool {
84 self.board_hash == other.board_hash && self.position_hash == other.position_hash
85 }
86}
87
88impl From<FEN> for BoardState {
89 fn from(fen: FEN) -> Self {
90 let pos = Position::from(fen);
91 Self::from_parts(pos, fen.halfmove_count(), fen.move_count())
92 }
93}
94
95impl BoardState {
96 pub fn new_starting() -> Self {
97 let position = Position::new_starting();
98 log::info!("New starting Position created");
99 let position_hash: PositionHash = position.pos_hash();
100 let board_hash = zobrist::board_state_hash(position_hash, 1, 0);
101 let side_to_move = position.side;
102 let legal_moves = position.get_legal_moves().into_iter().cloned().collect();
104 log::trace!("Legal moves generated: {legal_moves:?}");
105 let mut position_occurences = ahash::AHashMap::default();
106 position_occurences.insert(position_hash, 1);
107 log::info!("New starting BoardState created");
108 BoardState {
109 position,
110 move_count: 1, halfmove_count: 0,
112 position_hash,
113 board_hash,
114 side_to_move,
115 last_move: NULL_MOVE,
116 legal_moves,
117 position_occurences,
118 lazy_legal_moves: false,
119 }
120 }
121
122 pub(crate) fn from_parts(position: Position, halfmove_count: u32, move_count: u32) -> Self {
123 let position_hash: PositionHash = position.pos_hash();
124 let board_hash = zobrist::board_state_hash(position_hash, 1, halfmove_count);
125 let side_to_move = position.side;
126 let legal_moves = position.get_legal_moves().into_iter().cloned().collect();
128 let mut position_occurences = ahash::AHashMap::default();
129 position_occurences.insert(position_hash, 1);
130 log::info!("New BoardState created from parts");
131 BoardState {
132 position,
133 move_count,
134 halfmove_count,
135 position_hash,
136 board_hash,
137 side_to_move,
138 last_move: NULL_MOVE,
139 legal_moves,
140 position_occurences,
141 lazy_legal_moves: false,
142 }
143 }
144
145 pub(crate) fn position(&self) -> &Position {
146 &self.position
147 }
148
149 pub fn halfmove_count(&self) -> u32 {
150 self.halfmove_count
151 }
152
153 pub fn move_count(&self) -> u32 {
154 self.move_count
155 }
156
157 pub fn get_pseudo_legal_moves(&self) -> &Vec<Move> {
158 self.position.get_pseudo_legal_moves()
159 }
160
161 pub fn is_move_legal_position(&self, mv: &Move) -> bool {
163 self.position.is_move_legal(mv)
164 }
165
166 pub fn lazy_get_legal_moves(&self) -> impl Iterator<Item = &Move> {
168 self.position
169 .get_pseudo_legal_moves()
170 .iter()
171 .filter(|mv| self.position.is_move_legal(mv))
172 }
173
174 pub fn next_state_unchecked(&self, mv: &Move) -> Self {
177 let position = self.position.new_position(mv);
178 log::trace!("New Position created from move: {:?}", mv);
179 let position_hash = zobrist::pos_next_hash(
180 &self.position.movegen_flags,
181 &position.movegen_flags,
182 self.position_hash,
183 mv,
184 );
185 log::trace!(
186 "New position hash generated: {}",
187 util::hash_to_string(position_hash)
188 );
189 let side_to_move = position.side;
190 let last_move = *mv;
191 let legal_moves = Vec::with_capacity(0); let move_count = if side_to_move == PieceColour::White {
195 self.move_count + 1
196 } else {
197 self.move_count
198 };
199
200 let halfmove_reset = matches!(
201 mv.move_type,
202 MoveType::PawnPush | MoveType::DoublePawnPush | MoveType::Capture(_)
203 );
204 let halfmove_count = if halfmove_reset {
205 0
206 } else {
207 self.halfmove_count + 1
208 };
209
210 let mut position_occurences = self.position_occurences.clone();
211 let po = position_occurences.entry(position_hash).or_insert(0);
212 *po += 1;
213
214 let board_hash = zobrist::board_state_hash(position_hash, *po, halfmove_count);
215 log::trace!("Board hash: {}", util::hash_to_string(board_hash));
217
218 log::trace!("New BoardState created from move: {:?}", mv);
219 Self {
220 side_to_move,
221 last_move,
222 legal_moves,
223 position,
224 board_hash,
225 position_hash,
226 move_count,
227 halfmove_count,
228 position_occurences,
229 lazy_legal_moves: true,
230 }
231 }
232
233 pub fn next_state(&self, mv: &Move) -> Result<Self, BoardStateError> {
234 if mv == &NULL_MOVE {
235 let err = BoardStateError::NullMove(
236 "&NULL_MOVE was passed as an argument to BoardState::next_state()".to_string(),
237 );
238 log_and_return_error!(err)
239 }
240 if self.lazy_legal_moves {
241 let err = BoardStateError::LazyIncompatiblity("next_state called on BoardState with lazy_legal_moves flag set, cannot generate next state without all legal moves being generated.".to_string());
242 log_and_return_error!(err)
243 }
244 if !self.legal_moves.contains(mv) {
245 let err = BoardStateError::IllegalMove(format!("{:?} is not a legal move", mv));
246 log_and_return_error!(err)
247 }
248
249 let current_game_state = self.get_gamestate();
250
251 if current_game_state == GameState::Checkmate
252 || current_game_state == GameState::Stalemate
253 || current_game_state == GameState::FiftyMove
254 || current_game_state == GameState::Repetition
255 {
256 let err = BoardStateError::NoLegalMoves(current_game_state);
257 log_and_return_error!(err)
258 }
259
260 let position = self.position.new_position(mv);
261 log::trace!("New Position created from move: {:?}", mv);
262 let position_hash = zobrist::pos_next_hash(
263 &self.position.movegen_flags,
264 &position.movegen_flags,
265 self.position_hash,
266 mv,
267 );
268 log::trace!(
269 "New position hash generated: {}",
270 util::hash_to_string(position_hash)
271 );
272 let side_to_move = position.side;
273 let last_move = *mv;
274 let legal_moves = position.get_legal_moves().into_iter().cloned().collect();
276 log::trace!("Legal moves generated: {legal_moves:?}");
277
278 let move_count = if side_to_move == PieceColour::White {
279 self.move_count + 1
280 } else {
281 self.move_count
282 };
283
284 let halfmove_reset = matches!(
285 mv.move_type,
286 MoveType::PawnPush | MoveType::DoublePawnPush | MoveType::Capture(_)
287 );
288 let halfmove_count = if halfmove_reset {
289 0
290 } else {
291 self.halfmove_count + 1
292 };
293
294 let mut position_occurences = self.position_occurences.clone();
295 let po = position_occurences.entry(position_hash).or_insert(0);
296 *po += 1;
297
298 let board_hash = zobrist::board_state_hash(position_hash, *po, halfmove_count);
299 log::trace!("Board hash: {}", util::hash_to_string(board_hash));
301
302 log::trace!("New BoardState created from move: {:?}", mv);
303 Ok(Self {
304 side_to_move,
305 last_move,
306 legal_moves,
307 position,
308 board_hash,
309 position_hash,
310 move_count,
311 halfmove_count,
312 position_occurences,
313 lazy_legal_moves: false,
314 })
315 }
316
317 pub fn get_legal_moves(&self) -> Result<&[Move], BoardStateError> {
327 if self.lazy_legal_moves {
328 let err = BoardStateError::LazyIncompatiblity("get_legal_moves called on BoardState with lazy_legal_moves flag set, legal_moves vec is empty".to_string());
329 log_and_return_error!(err)
330 }
331 Ok(&self.legal_moves)
332 }
333
334 pub fn get_occurences_of_current_position(&self) -> u8 {
335 *self
336 .position_occurences
337 .get(&self.position_hash)
338 .unwrap_or(&1)
339 }
340 pub fn get_gamestate(&self) -> GameState {
342 let legal_moves_empty = if self.lazy_legal_moves {
343 self.lazy_get_legal_moves().peekable().peek().is_none()
344 } else {
345 self.legal_moves.is_empty()
346 };
347 let is_in_check = self.position.is_in_check();
348
349 if is_in_check && legal_moves_empty {
351 GameState::Checkmate
352 } else if !is_in_check && legal_moves_empty {
353 GameState::Stalemate
354 } else if self.halfmove_count >= 100 {
355 GameState::FiftyMove
356 } else if self.get_occurences_of_current_position() >= 3 {
357 GameState::Repetition
358 } else if is_in_check {
359 GameState::Check
360 } else if false {
361 GameState::InsufficientMaterial
363 } else {
364 GameState::Active
365 }
366 }
367
368 pub fn get_pos64(&self) -> &Pos64 {
404 &self.position.pos64
405 }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
409pub enum GameOverState {
410 WhiteResign,
411 BlackResign,
412 AgreedDraw,
413 Forced(GameState),
414}
415
416#[derive(Debug)]
417pub struct Board {
418 current_state: BoardState,
419 state_history: Vec<BoardState>,
420 move_history: Vec<Move>,
421 game_over_state: Option<GameOverState>,
422 transposition_table: transposition::TranspositionTable,
423}
424
425impl Default for Board {
426 fn default() -> Self {
427 Self::new()
428 }
429}
430
431impl From<FEN> for Board {
432 fn from(fen: FEN) -> Self {
433 let current_state = BoardState::from(fen);
434 let state_history: Vec<BoardState> = vec![current_state.clone()];
435 let transposition_table = transposition::TranspositionTable::new();
436 log::info!("New Board created from FEN: {}", fen.to_string());
438 Board {
439 current_state,
440 state_history,
441 move_history: Vec::new(),
442 game_over_state: None,
443 transposition_table,
444 }
445 }
446}
447
448impl TryFrom<pgn::PGN> for Board {
449 type Error = PGNParseError;
450
451 fn try_from(pgn: pgn::PGN) -> Result<Self, PGNParseError> {
452 let mut board = Self::new();
453 for notation in pgn.moves() {
454 let mv = notation.to_move_with_context(board.get_current_state())?;
455 match board.make_move(&mv) {
456 Ok(_) => {}
457 Err(e) => log_and_return_error!(PGNParseError::NotationParseError(e.to_string())),
458 }
459 }
460 for tag in pgn.tags() {
462 if let Tag::Result(result) = tag {
463 match result.as_str() {
464 "1-0" => board.set_resign(PieceColour::Black),
466 "0-1" => board.set_resign(PieceColour::White),
467 "1/2-1/2" => board.set_draw(),
468 _ => {}
469 }
470 }
471 }
472 Ok(board)
473 }
474}
475
476impl Board {
477 pub fn new() -> Self {
478 let current_state = BoardState::new_starting();
479 let mut state_history: Vec<BoardState> = Vec::new();
480 log::info!("State history created");
481 state_history.push(current_state.clone());
482
483 let transposition_table = transposition::TranspositionTable::new();
484 log::info!("Transposition table created");
485 log::info!("New Board created");
486 Board {
487 current_state,
488 state_history,
489 move_history: Vec::new(),
490 game_over_state: None,
491 transposition_table,
492 }
493 }
494
495 pub fn set_resign(&mut self, side: PieceColour) {
496 let gos = match side {
497 PieceColour::White => GameOverState::WhiteResign,
498 PieceColour::Black => GameOverState::BlackResign,
499 };
500 if self.game_over_state.is_none() {
501 self.game_over_state = Some(gos);
502 } else {
503 log::warn!("Game over state already set, ignoring set_resign");
504 }
505 }
506
507 pub fn set_draw(&mut self) {
508 if self.game_over_state.is_none() {
509 self.game_over_state = Some(GameOverState::AgreedDraw);
510 } else {
511 log::warn!("Game over state already set, ignoring set_draw");
512 }
513 }
514
515 pub fn get_starting_state(&self) -> &BoardState {
516 &self.state_history[0]
518 }
519
520 pub fn get_side_to_move(&self) -> PieceColour {
521 self.current_state.side_to_move
522 }
523
524 pub fn get_current_state(&self) -> &BoardState {
525 &self.current_state
526 }
527
528 pub fn get_state_history(&self) -> &Vec<BoardState> {
529 &self.state_history
530 }
531
532 pub fn get_game_over_state(&self) -> Option<GameOverState> {
533 self.game_over_state
534 }
535
536 pub fn make_move(&mut self, mv: &Move) -> Result<GameState, BoardStateError> {
537 if let Some(gos) = self.game_over_state {
538 let err = BoardStateError::GameOver(gos);
539 log_and_return_error!(err)
540 }
541 let next_state = self.current_state.next_state(mv)?;
542 self.current_state = next_state;
543 self.state_history.push(self.current_state.clone());
544 self.move_history.push(*mv);
545
546 let game_state = self.current_state.get_gamestate();
547 if game_state.is_game_over() {
548 self.game_over_state = Some(GameOverState::Forced(game_state));
549 }
550 Ok(game_state)
551 }
552
553 pub fn make_engine_move(&mut self, depth: u8) -> Result<GameState, BoardStateError> {
554 if let Some(gos) = self.game_over_state {
555 let err = BoardStateError::GameOver(gos);
556 log_and_return_error!(err)
557 }
558 let (eval, engine_move) =
559 engine::choose_move(&self.current_state, depth, &mut self.transposition_table);
560 let mv = *engine_move;
561 log::info!("Engine move chosen: {:?} @ eval: {}", engine_move, eval);
562
563 self.make_move(&mv)
564 }
565
566 pub fn move_history_string_notation(&self) -> Vec<String> {
567 let mut notations_string = Vec::new();
568 let notations = self.move_history_notation();
569 for n in notations {
570 notations_string.push(n.to_string());
571 }
572 notations_string
573 }
574
575 pub fn move_history_notation(&self) -> Vec<Notation> {
576 let mut notations = Vec::new();
577 for (state, mv) in self.state_history.iter().zip(self.move_history.iter()) {
578 let notation = Notation::from_mv_with_context(state, mv).unwrap();
580 notations.push(notation);
581 }
582 notations
583 }
584
585 pub fn unmake_move(&mut self) -> Result<Rc<BoardState>, BoardStateError> {
586 todo!()
587 }
588
589 pub fn get_current_gamestate(&self) -> GameState {
590 self.current_state.get_gamestate()
591 }
592}