1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use super::board_map::BoardMap;
use super::castling_rights::CastlingRights;
use super::castlinginfo;
use super::enpassant::Enpassant;
use super::game_error::GameError;
use super::gamestate::Gamestate;
use super::play;
use super::side::Side;
use super::state::State;
use chess_notation_parser::{Castling, Move, Piece, Square, Turn};
use std::collections::hash_map::DefaultHasher;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
/// Board tracks state of the game.
pub struct Board {
/// 64 squares containing all chess pieces
pub map: BoardMap,
/// Current player
pub active_player: Side,
/// Cache for location of the kings
pub king: HashMap<Side, Square>,
/// En-passant possibility
///
/// Is updated every turn.
/// If pawn moves two squares, then `enpassant` state is stored.
/// Set enpassant allows enemy pawn to perform an en-passant action.
/// If en-passant capture is taken while available, then next turn will
/// reset it to `None`.
pub enpassant: Option<Enpassant>,
/// Track info whether certain castling is possible
///
/// If `CastlingType` can be found in the struct, that castling is possible
/// for that `Side`.
pub castling_rights: CastlingRights,
/// Game status info
pub gamestate: Gamestate,
/// Counter for the *fifty move rule*. If counter reaches 50, game is a draw
///
/// Counter is incremented by default every turn.
/// It will reset if following conditions are fulfilled:
/// - Capture occured
/// - Pawn has moved
pub fifty_move_rule: u8,
/// Stored hashes of every board state
pub state_hashes: BTreeMap<u64, u8>,
}
impl Board {
/// Play the next turn and update the board state.
/// Return `State` of the previous turn. That info can be used to undo turn
/// with an `undo` function
pub fn next_turn(&mut self, turn: &str) -> Result<State, GameError> {
// Moved to `play` module due to complexity
play::next_turn(self, turn)
}
/// Move piece from `src` to `dst` and return captured piece if any
/// Function caches king location in case of king movement.
///
/// # Arguments
///
/// * `src` - Source square
/// * `dst` - Destination square
pub fn move_piece(
&mut self,
dst: Square,
src: Square,
) -> Option<(Piece, Side)> {
let (piece, side) = self
.map
.remove(&src)
.expect(format!("Src square {} empty (dst:{})", src, dst).as_str());
// Update our cache location of the king
if piece == Piece::King {
self.king.insert(side, dst);
}
self.map.insert(dst, (piece, side))
}
/// Getter for a king's location
#[inline]
pub fn get_king_pos(&self, side: Side) -> Square {
// Basically a shortcut function
self.king.get(&side).unwrap().clone()
}
/// Push hash state
pub fn hash_state_push(&mut self) -> Gamestate {
let hash = self.calc_hash();
let hash_cnt = self.state_hashes.entry(hash).or_insert(0);
*hash_cnt += 1;
match *hash_cnt {
3 => Gamestate::DrawThreeFoldRepetition,
_ => self.gamestate.clone(),
}
}
/// Pop hash state
///
/// Beware: Pop is allowed on a given state the same number of times
/// that state was previosly pushed!
pub fn hash_state_pop(&mut self) {
let hash = self.calc_hash();
*self.state_hashes.entry(hash).or_insert(0) -= 1;
}
/// Snapshot the board state into a hash value
///
/// Hashed parts of the board are conditions used for threefold repetition
/// rule
fn calc_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.active_player.hash(&mut hasher);
self.map.hash(&mut hasher);
self.enpassant.hash(&mut hasher);
self.castling_rights.hash(&mut hasher);
hasher.finish()
}
/// Undo move based on the previous turn `State`
///
/// Function does not modify active_player nor does it affect hash tree
pub fn undo(&mut self, prev_state: State) {
// Restore these states independently of the turn type
self.enpassant = prev_state.enpassant;
self.castling_rights = prev_state.castling_rights;
self.fifty_move_rule = prev_state.fifty_move_rule;
self.gamestate = Gamestate::Ongoing;
// Undone turn was already parsed successfully before, so unwrap!
match Turn::try_from(prev_state.get_turn()).unwrap() {
Turn::Castling(turn) => self.undo_castling(turn),
Turn::Move(turn) => self.undo_move(turn, prev_state),
}
}
fn undo_castling(&mut self, turn: Castling) {
let side = self.active_player;
let castling_type = turn.r#type;
let king_path = castlinginfo::get_path_king(side, castling_type);
let rook_path = castlinginfo::get_path_rook(side, castling_type);
// Reverse path to undo their movement
self.move_piece(king_path.src, king_path.dst);
self.move_piece(rook_path.src, rook_path.dst);
}
fn undo_move(&mut self, turn: Move, mut state: State) {
// Move piece back to it's original location
let src = state.moving_piece_src.expect("Source not set");
self.move_piece(src, turn.dst);
// Demotion if necessary
if turn.promotion.is_some() {
let (_, side) = self.map.get(&src).unwrap();
self.map.insert(src, (Piece::Pawn, side));
}
// Restore captured piece
if let Some((square, captured)) = state.captured.take() {
self.map.insert(square, captured);
}
}
}