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
//! # Position History
//!
//! [`History`] bundles the per-position bookkeeping that the bare
//! [`Board`](crate::board::Board) doesn't carry: the previous move (as UCI),
//! castling rights, which rooks have not moved, the fifty-move clock, and a
//! rolling trail of Zobrist hashes used for repetition detection.
//!
//! Like the rest of the crate, `History` is persistent: every mutating
//! method returns a new value rather than modifying in place.
use crate::{
castles::Castles,
halfmoveclock::HalfMoveClock,
hash::{Hash, PositionHash},
mve::Move,
position::Position,
role::Role,
uci::Uci,
unmoved_rooks::UnmovedRooks,
};
/// All the per-position state that lives outside the [`Board`](crate::board::Board).
#[derive(Debug, Clone, PartialEq)]
pub struct History {
/// The most recent move, in UCI form, or `None` at the start of a game.
pub last_move: Option<Uci>,
/// Castling rights currently available to both sides.
pub castles: Castles,
/// Squares that still contain rooks that have never moved.
pub unmoved_rooks: UnmovedRooks,
/// Half-moves since the last pawn move or capture (for the 50-move rule).
pub half_move_clock: HalfMoveClock,
/// Trail of Zobrist hashes used to detect repetitions.
pub position_hashes: PositionHash,
}
impl History {
/// Returns the starting-position history: no prior move, standard
/// castling rights, all four rooks unmoved, clock at zero, empty hash
/// trail.
///
/// # Example
/// ```
/// # use ruchess::history::History;
/// let h = History::new();
/// assert!(h.last_move.is_none());
/// assert_eq!(h.half_moves(), 0);
/// ```
pub fn new() -> Self {
Self {
last_move: None,
castles: Castles::standard(),
unmoved_rooks: UnmovedRooks::standard(),
half_move_clock: HalfMoveClock::new(),
position_hashes: PositionHash::empty(),
}
}
/// Like [`Self::new`], but with repetition tracking permanently disabled.
///
/// Skips the Zobrist hash computation and the `Vec<u8>` growth that
/// [`Self::update`] would otherwise do on every move. Use this for
/// perft, fixed-depth search, and any other workload that never calls
/// [`Self::is_threefold_repetition`] or [`Self::is_fivefold_repetition`].
///
/// Once disabled, the `Disabled` state propagates through every
/// descendant history produced by [`Self::update`].
pub fn new_no_repetition() -> Self {
Self {
last_move: None,
castles: Castles::standard(),
unmoved_rooks: UnmovedRooks::standard(),
half_move_clock: HalfMoveClock::new(),
position_hashes: PositionHash::disabled(),
}
}
/// Returns a new history with `castles` replacing the current rights,
/// leaving every other field unchanged.
///
/// # Example
/// ```
/// # use ruchess::history::History;
/// # use ruchess::castles::Castles;
/// let h = History::new().with_castles(Castles::NONE);
/// assert!(h.castles.is_empty());
/// ```
#[must_use]
pub fn with_castles(self, castles: Castles) -> Self {
Self { castles, ..self }
}
/// Appends the Zobrist hash of `position` to the front of the hash trail.
///
/// Used to record a position without playing a move through it — for
/// example to seed the trail from a FEN string before any moves have
/// been generated. No-op when repetition tracking is disabled.
#[must_use]
pub fn push_position(self, position: &Position) -> Self {
if self.position_hashes.is_disabled() {
return self;
}
let entry = PositionHash::from_hash(Hash::from_position(position));
Self {
position_hashes: entry.combine(&self.position_hashes),
..self
}
}
/// Returns the history that should follow playing `mve` from `prev`.
///
/// Updates the hash trail (prepending `prev`'s hash), records the move
/// in `last_move`, updates castling rights and unmoved rooks, and either
/// resets or increments the half-move clock based on whether `mve` was a
/// pawn move or capture.
///
/// Skips the hash work entirely when repetition tracking is disabled.
#[must_use]
pub fn update(self, prev: &Position, mve: &Move) -> Self {
let position_hashes = if self.position_hashes.is_disabled() {
PositionHash::disabled()
} else {
let entry = PositionHash::from_hash(Hash::from_position(prev));
entry.combine(&self.position_hashes)
};
let mover_role = prev.board().role_at(mve.orig());
let is_capture = crate::position::is_capture(prev.board(), mve);
let half_move_clock = if mover_role == Some(Role::Pawn) || is_capture {
self.half_move_clock.reset()
} else {
self.half_move_clock.incr()
};
Self {
position_hashes,
last_move: Some((*mve).into()),
castles: self
.castles
.update(mve, mover_role == Some(Role::King), prev.color()),
unmoved_rooks: self.unmoved_rooks.update(mve),
half_move_clock,
}
}
/// Returns `true` if the current position has appeared at least three
/// times in the recorded hash trail.
pub fn is_threefold_repetition(&self) -> bool {
self.position_hashes.is_repetition(3)
}
/// Returns `true` if the current position has appeared at least five
/// times in the recorded hash trail.
pub fn is_fivefold_repetition(&self) -> bool {
self.position_hashes.is_repetition(5)
}
/// Returns the current half-move clock value.
///
/// # Example
/// ```
/// # use ruchess::history::History;
/// assert_eq!(History::new().half_moves(), 0);
/// ```
pub fn half_moves(&self) -> u8 {
self.half_move_clock.get()
}
}
impl Default for History {
fn default() -> Self {
Self::new()
}
}