1#[cfg(test)]
14extern crate alloc;
15
16use core::num::NonZeroU32;
17
18use crate::{
19 Board, Player, Role, Scharnagl, Square,
20 board::Bitboard,
21 square::File,
22};
23
24use Player::*;
25use Role::*;
26
27mod moves;
28mod play;
29mod rights;
30mod validate;
31
32pub use play::*;
33pub use rights::*;
34
35pub use Kind::Normal;
36pub use Special::{Castle, Promote};
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
39pub enum Error {
40 #[error("inconsistent board bitboards")]
41 InconsistentBoard,
42 #[error("expected exactly one {0} king")]
43 KingCount(Player),
44 #[error("pawns cannot be on the first or eighth rank")]
45 PawnOnBackrank,
46 #[error("kings cannot be adjacent")]
47 AdjacentKings,
48 #[error("{0} king is attacked")]
49 KingAttacked(Player),
50 #[error("{0} king with castling rights is not on its back rank")]
51 CastleKing(Player),
52 #[error("no {player} rook for {side:?}-side castling on {file}")]
53 CastleRook { player: Player, side: Side, file: File },
54 #[error("{player} castling rook on {file} is not on the {side:?} side")]
55 CastleSide { player: Player, side: Side, file: File },
56 #[error("no piece on {0}")]
57 MissingPiece(Square),
58}
59
60pub type Result<T, E = Error> = core::result::Result<T, E>;
61
62#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
88pub struct Position {
89 board: Board,
91 turn: Player,
93 castles: Castles,
95 en_passant: Option<EnPassant>,
97 reversible: u32,
99 round: NonZeroU32,
101}
102
103#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
105pub struct Parts {
106 pub board: Board,
108 pub turn: Player,
110 pub castles: Castles,
112 pub en_passant: Option<EnPassant>,
114 pub reversible: u32,
116 pub round: NonZeroU32,
118}
119
120impl Parts {
121 pub const fn new(board: Board, turn: Player) -> Self {
122 Self {
123 board,
124 turn,
125 castles: Castles::empty(),
126 en_passant: None,
127 reversible: 0,
128 round: NonZeroU32::MIN,
129 }
130 }
131
132 pub const fn empty() -> Self {
133 Self::new(Board::EMPTY, White)
134 }
135
136 pub const fn ply(&self) -> usize {
137 let round = self.round.get() as usize - 1;
138 round * 2 + self.turn.eq(Black) as usize
139 }
140
141}
142
143impl Default for Parts {
144 fn default() -> Self {
145 Self::empty()
146 }
147}
148
149impl Position {
150 pub const fn start() -> Position {
151 Position {
152 board: Board::standard(),
153 turn: White,
154 castles: Castles::chess(),
155 en_passant: None,
156 reversible: 0,
157 round: NonZeroU32::MIN,
158 }
159 }
160
161 pub const fn freestyle(i: Scharnagl) -> Position {
162 let board = Board::freestyle(i);
164
165 let king = board.king_of(White).unwrap();
167 let mut rooks = board.rooks();
168 let queen_rook = rooks.pop_first().unwrap().file();
169 let king_rook = rooks.first().unwrap().file();
170
171 let mut castles = Castles::empty();
172 castles.set(White, Side::of_rook(king, queen_rook), queen_rook);
173 castles.set(White, Side::of_rook(king, king_rook), king_rook);
174 castles.set(Black, Side::Queen, queen_rook);
175 castles.set(Black, Side::King, king_rook);
176
177 Position {
178 board,
179 turn: White,
180 castles,
181 en_passant: None,
182 reversible: 0,
183 round: NonZeroU32::MIN,
184 }
185 }
186}
187
188impl Position {
189 #[inline]
190 pub const fn board(&self) -> Board {
191 self.board
192 }
193
194 #[inline]
195 pub const fn turn(&self) -> Player {
196 self.turn
197 }
198
199 #[inline]
200 pub const fn castles(&self) -> Castles {
201 self.castles
202 }
203
204 #[inline]
205 pub const fn en_passant(&self) -> Option<EnPassant> {
206 self.en_passant
207 }
208
209 #[inline]
210 pub const fn reversible(&self) -> u32 {
211 self.reversible
212 }
213
214 #[inline]
215 pub const fn round(&self) -> NonZeroU32 {
216 self.round
217 }
218
219 pub const fn checkers(&self) -> Bitboard {
220 match self.board.king_of(self.turn) {
221 Some(king) => self.board.attacks_on(king, self.turn.other(), self.board.occupied()),
222 None => Bitboard::EMPTY,
223 }
224 }
225
226 pub const fn is_check(&self) -> bool {
227 !self.checkers().is_empty()
228 }
229}
230
231impl Position {
232 pub fn capture_moves(&self) -> Vec<Move> {
233 self.legal_moves().into_iter().filter(|m| m.is_capture()).collect()
234 }
235
236 pub fn castle_side_moves(&self, side: Side) -> Vec<Move> {
237 self.legal_moves().into_iter().filter(|m| m.is_castle_side(side)).collect()
238 }
239
240 pub fn castle_moves(&self) -> Vec<Move> {
241 self.legal_moves().into_iter().filter(|m| m.is_castle()).collect()
242 }
243}
244
245impl Position {
246 pub const fn parts(self) -> Parts {
247 Parts {
248 board: self.board,
249 turn: self.turn,
250 castles: self.castles,
251 en_passant: self.en_passant,
252 reversible: self.reversible,
253 round: self.round,
254 }
255 }
256
257 pub fn first_ply(&self) -> usize {
258 let round = self.round.get() as usize - 1;
259 round * 2 + usize::from(self.turn == Black)
260 }
261
262}
263
264impl From<Position> for Parts {
265 fn from(position: Position) -> Self {
266 position.parts()
267 }
268}
269
270impl Position {
271 pub(crate) fn apply_unchecked(mut self, play: Move) -> Position {
272 let player = self.turn;
273 let captured = if play.is_en_passant() {
274 Some(Square::new(play.to.file(), play.from.rank()))
275 } else if play.capture.is_some() {
276 Some(play.to)
277 } else {
278 None
279 };
280
281 if play.role == King {
282 self.castles.clear_player(player);
283 }
284
285 if play.role == Rook {
286 self.clear_castle_rook(player, play.from);
287 }
288
289 if let Some(captured) = captured {
290 self.clear_castle_rook(player.other(), captured);
291 }
292
293 self.board.play_unchecked(player, play);
294
295 let mut en_passant = None;
296 if play.role == Pawn {
297 let from = play.from as u8;
298 let to = play.to as u8;
299 if from.abs_diff(to) == 16 {
300 en_passant = EnPassant::try_from(Square::panicky_from_index((from + to) / 2)).ok();
301 }
302 }
303
304 if play.role == Pawn || play.capture.is_some() || play.is_en_passant() {
305 self.reversible = 0;
306 } else {
307 self.reversible += 1;
308 }
309
310 if player == Black {
311 self.round = self.round.saturating_add(1);
312 }
313
314 self.turn = player.other();
315 self.en_passant = match en_passant {
316 Some(en_passant) => self.board.attacked_en_passant(en_passant, self.turn),
317 None => None,
318 };
319 self
320 }
321
322 fn clear_castle_rook(&mut self, player: Player, square: Square) {
323 if square.rank() != player.backrank() {
324 return;
325 }
326
327 for side in Side::ALL {
328 if self.castles.get(player, side) == Some(square.file()) {
329 self.castles.clear(player, side);
330 }
331 }
332 }
333}
334
335#[test]
336fn all_random() {
337 use alloc::collections::BTreeSet as Set;
338
339 let mut positions = Vec::new();
340 let all = Set::from_iter(1usize..=8);
341 for rook_l in 1usize..=8 {
342 for rook_r in rook_l + 2..=8 {
343 for king in (rook_l + 1)..rook_r {
344 assert!(rook_l < king);
345 assert!(king < rook_r);
346 let it = (1usize..rook_l)
347 .chain((rook_l + 1)..king)
348 .chain((king + 1)..rook_r)
349 .chain((rook_r + 1)..=8);
350 for bishop_b in it.clone().filter(|i| (i & 1) == 1) {
351 for bishop_w in it.clone().filter(|i| (i & 1) == 0) {
352 let used = Set::from([rook_l, rook_r, king, bishop_b, bishop_w]);
353 let remaining = all.difference(&used);
354 for queen in remaining.clone() {
355 let mut position = vec!['-'; 8];
356 position[rook_l - 1] = 'r';
357 position[rook_r - 1] = 'r';
358 position[king - 1] = 'k';
359 position[queen - 1] = 'q';
360 position[bishop_w - 1] = 'b';
361 position[bishop_b - 1] = 'b';
362 for knight in remaining.clone() {
363 if knight != queen {
364 position[knight - 1] = 'n';
365 }
366 }
367 assert!(!position.contains(&'-'));
368 positions.push(position);
369 }
370 }
371 }
372 }
373 }
374 }
375 for position in positions.iter() {
376 println!("{}", position.iter().collect::<String>());
377 }
378 assert_eq!(960, positions.len());
379}