1use crate::engine::BitBoard;
2use crate::lookup::neighbor::has_neighbor;
3
4use std::{
5 fmt::{self, Write},
6 mem,
7};
8
9use serde::{Deserialize, Serialize};
10use serde_big_array::BigArray;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct Board {
15 #[serde(with = "BigArray")]
16 pub board: [Option<Piece>; 64],
17 pub can_castle: CanCastle,
19 pub en_passant: Option<Square>,
21 pub next_move: Option<Side>,
22 pub moved_piece: bool,
25 pub winner: Option<Side>,
26}
27
28macro_rules! setup_board {
30 ($board:expr, $($id:tt),*) => (
31 $board.board = [
32 $(
33 setup_board!($id)
34 ),*
35 ];
36 );
37 (e) => (None);
38 (bR) => (Some(Piece { kind: PieceKind::Rook, side: Side::Black }));
39 (bN) => (Some(Piece { kind: PieceKind::Knight, side: Side::Black }));
40 (bB) => (Some(Piece { kind: PieceKind::Bishop, side: Side::Black }));
41 (bQ) => (Some(Piece { kind: PieceKind::Queen, side: Side::Black }));
42 (bK) => (Some(Piece { kind: PieceKind::King, side: Side::Black }));
43 (bP) => (Some(Piece { kind: PieceKind::Pawn, side: Side::Black }));
44 (wR) => (Some(Piece { kind: PieceKind::Rook, side: Side::White }));
45 (wN) => (Some(Piece { kind: PieceKind::Knight, side: Side::White }));
46 (wB) => (Some(Piece { kind: PieceKind::Bishop, side: Side::White }));
47 (wQ) => (Some(Piece { kind: PieceKind::Queen, side: Side::White }));
48 (wK) => (Some(Piece { kind: PieceKind::King, side: Side::White }));
49 (wP) => (Some(Piece { kind: PieceKind::Pawn, side: Side::White }));
50}
51
52impl Board {
53 pub fn new() -> Self {
54 Self {
55 board: [None; 64],
56 can_castle: CanCastle {
57 white: (true, true),
58 black: (true, true),
59 },
60 en_passant: None,
61 next_move: None,
62 moved_piece: false,
63 winner: None,
64 }
65 }
66
67 pub fn set_start_position(&mut self) {
68 #[rustfmt::skip]
69 setup_board!(self,
70 bR, bN, bB, bQ, bK, bB, bN, bR,
71 bP, bP, bP, bP, bP, bP, bP, bP,
72 e, e, e, e, e, e, e, e,
73 e, e, e, e, e, e, e, e,
74 e, e, e, e, e, e, e, e,
75 e, e, e, e, e, e, e, e,
76 wP, wP, wP, wP, wP, wP, wP, wP,
77 wR, wN, wB, wQ, wK, wB, wN, wR
78 );
79 self.can_castle = CanCastle {
80 white: (true, true),
81 black: (true, true),
82 };
83 self.en_passant = None;
84 self.next_move = Some(Side::White);
85 self.moved_piece = false;
86 self.winner = None;
87 }
88
89 pub fn piece_at(&self, square: Square) -> Option<Piece> {
90 unsafe { *self.board.get_unchecked(square as u8 as usize) }
91 }
92
93 pub fn piece_at_mut(&mut self, square: Square) -> &mut Option<Piece> {
94 unsafe { self.board.get_unchecked_mut(square as u8 as usize) }
95 }
96
97 fn can_eat_piece(piece: Piece, side: Side) -> bool {
98 !piece.kind.is_duck() && piece.side != side
99 }
100
101 fn valid_square_for_piece(
103 &self,
104 square: Square,
105 side: Side,
106 ) -> (bool, Option<PieceKind>) {
107 let Some(piece) = self.piece_at(square) else {
108 return (true, None);
109 };
110
111 let valid = Self::can_eat_piece(piece, side);
112 if valid {
113 (true, Some(piece.kind))
114 } else {
115 (false, None)
116 }
117 }
118
119 fn available_moves_by_dir(
120 &self,
121 from: Square,
122 dirs: &[Direction],
123 max_one: bool,
125 list: &mut Vec<PieceMove>,
126 ) {
127 let dist = if max_one { 1 } else { 8 };
128 let Some(piece) = self.piece_at(from) else {
129 panic!("no piece")
130 };
131
132 for dir in dirs {
133 let mut to = from;
134 for _ in 0..dist {
135 if !to.apply_dir(*dir) {
136 break;
137 }
138
139 let (valid, capture) =
140 self.valid_square_for_piece(to, piece.side);
141
142 if !valid {
143 break;
144 }
145
146 list.push(PieceMove::Piece {
147 piece: piece.kind,
148 from,
149 to,
150 capture,
151 promotion: None,
152 });
153
154 if capture.is_some() {
156 break;
157 }
158 }
159 }
160 }
161
162 fn available_castle_moves(
164 &self,
165 square: Square,
166 list: &mut Vec<PieceMove>,
167 ) {
168 const LONG_FREE: &[u8] = &[1, 2, 3];
169 const SHORT_FREE: &[u8] = &[5, 6];
170
171 let Some(piece) = self.piece_at(square) else {
172 panic!("no piece")
173 };
174
175 let (y, (can_castle_long, can_castle_short)) = match piece.side {
176 Side::White => (7, self.can_castle.white),
177 Side::Black => (0, self.can_castle.black),
178 };
179
180 if can_castle_long {
181 let all_free = LONG_FREE
182 .iter()
183 .map(|x| Square::from_xy(*x, y))
184 .all(|square| self.piece_at(square).is_none());
185
186 if all_free {
187 list.push(PieceMove::Castle {
188 from_king: square,
189 to_king: Square::from_xy(2, y),
190 from_rook: Square::from_xy(0, y),
191 to_rook: Square::from_xy(3, y),
192 });
193 }
194 }
195
196 if can_castle_short {
197 let all_free = SHORT_FREE
198 .iter()
199 .map(|x| Square::from_xy(*x, y))
200 .all(|square| self.piece_at(square).is_none());
201
202 if all_free {
203 list.push(PieceMove::Castle {
204 from_king: square,
205 to_king: Square::from_xy(6, y),
206 from_rook: Square::from_xy(7, y),
207 to_rook: Square::from_xy(5, y),
208 });
209 }
210 }
211 }
212
213 fn available_pawn_moves(&self, square: Square, list: &mut Vec<PieceMove>) {
214 const CAN_PROMOTE_TO: &[PieceKind] = &[
215 PieceKind::Rook,
216 PieceKind::Knight,
217 PieceKind::Bishop,
218 PieceKind::Queen,
219 ];
220 const DIRECTION_WHITE: Direction = Direction::Up;
221 const DIRECTION_BLACK: Direction = Direction::Down;
222 const TAKE_PIECE_WHITE: &[Direction] =
223 &[Direction::UpLeft, Direction::UpRight];
224 const TAKE_PIECE_BLACK: &[Direction] =
225 &[Direction::DownLeft, Direction::DownRight];
226
227 let Some(piece) = self.piece_at(square) else {
228 panic!("no piece")
229 };
230
231 let (second_rank, to_promotion, dir, take_dirs) = match piece.side {
232 Side::White => (6, 1, DIRECTION_WHITE, TAKE_PIECE_WHITE),
233 Side::Black => (1, 6, DIRECTION_BLACK, TAKE_PIECE_BLACK),
234 };
235 let can_promote = square.y() == to_promotion;
236
237 let move_dist = if square.y() == second_rank { 2 } else { 1 };
238
239 {
241 let mut up_square = square;
242 for _ in 0..move_dist {
243 if !up_square.apply_dir(dir)
244 || self.piece_at(up_square).is_some()
245 {
246 break;
247 }
248
249 list.push(PieceMove::Piece {
250 piece: piece.kind,
251 from: square,
252 to: up_square,
253 capture: None,
254 promotion: None,
255 });
256 }
257 }
258
259 if can_promote {
261 let promotion_square = square.add_dir(dir).unwrap();
262 if self.piece_at(promotion_square).is_none() {
263 for promotion_piece in CAN_PROMOTE_TO {
264 list.push(PieceMove::Piece {
265 piece: piece.kind,
266 from: square,
267 to: promotion_square,
268 capture: None,
269 promotion: Some(*promotion_piece),
270 });
271 }
272 }
273 }
274
275 for dir in take_dirs {
277 let Some(new_square) = square.add_dir(*dir) else {
278 continue;
279 };
280
281 let Some(eat_piece) = self.piece_at(new_square) else {
282 continue;
283 };
284
285 if Self::can_eat_piece(eat_piece, piece.side) {
286 list.push(PieceMove::Piece {
287 piece: piece.kind,
288 from: square,
289 to: new_square,
290 capture: Some(eat_piece.kind),
291 promotion: None,
292 });
293 }
294 }
295
296 if let Some(en_passant_square) = self.en_passant {
298 if en_passant_square.y() != square.y()
300 || square.x().abs_diff(en_passant_square.x()) != 1
301 {
302 return;
303 }
304
305 let mut new_square = en_passant_square;
306 new_square.apply_dir(dir);
307
308 list.push(PieceMove::EnPassant {
309 from: square,
310 to: new_square,
311 });
312 }
313 }
314
315 fn available_knight_moves(
316 &self,
317 square: Square,
318 list: &mut Vec<PieceMove>,
319 ) {
320 let Some(piece) = self.piece_at(square) else {
321 panic!("no piece")
322 };
323
324 for dir in Direction::ALL_KNIGHTS {
325 let Some(new_square) = square.add_dir(*dir) else {
326 continue;
327 };
328
329 let capture = if let Some(eat_piece) = self.piece_at(new_square) {
330 if !Self::can_eat_piece(eat_piece, piece.side) {
331 continue;
332 }
333
334 Some(eat_piece.kind)
335 } else {
336 None
337 };
338
339 list.push(PieceMove::Piece {
340 piece: piece.kind,
341 from: square,
342 to: new_square,
343 capture,
344 promotion: None,
345 });
346 }
347 }
348
349 pub fn available_piece_moves(
350 &self,
351 piece: PieceKind,
352 square: Square,
353 list: &mut Vec<PieceMove>,
354 ) {
355 assert!(!self.moved_piece);
356 assert!(self.next_move.is_some());
357
358 match piece {
359 PieceKind::Rook | PieceKind::Bishop | PieceKind::Queen => {
360 self.available_moves_by_dir(
361 square,
362 piece.directions(),
363 false,
364 list,
365 );
366 }
367 PieceKind::King => {
368 self.available_moves_by_dir(
369 square,
370 piece.directions(),
371 true,
372 list,
373 );
374
375 self.available_castle_moves(square, list);
376 }
377 PieceKind::Pawn => {
378 self.available_pawn_moves(square, list);
379 }
380 PieceKind::Knight => {
381 self.available_knight_moves(square, list);
382 }
383 PieceKind::Duck => unreachable!(),
384 }
385 }
386
387 pub fn available_duck_squares(&self, list: &mut Vec<Square>) {
388 assert!(self.moved_piece);
389
390 for (square, piece) in iter_board!(self.board) {
391 if piece.is_none() {
392 list.push(square);
393 }
394 }
395 }
396
397 pub fn reasonable_duck_squares(&self, list: &mut Vec<Square>) {
399 assert!(self.moved_piece);
400 assert!(list.is_empty());
401
402 let oponnent = self.next_move.unwrap().other();
403 let mut oponnent_pieces = BitBoard::new();
404
405 for (square, piece) in iter_board!(self.board) {
407 let Some(piece) = piece else { continue };
408
409 if piece.side == oponnent || piece.kind.is_duck() {
410 oponnent_pieces.set(square);
411 }
412 }
413
414 for (square, piece) in iter_board!(self.board) {
416 if piece.is_some() {
417 continue;
418 }
419
420 if has_neighbor(square, oponnent_pieces) {
421 list.push(square);
422 }
423 }
424 }
425
426 pub fn set_can_castle(&mut self, can_castle: bool, long: bool) {
428 match self.next_move.unwrap() {
429 Side::White if long => {
430 self.can_castle.white.0 = can_castle;
431 }
432 Side::White => {
433 self.can_castle.white.1 = can_castle;
434 }
435 Side::Black if long => {
436 self.can_castle.black.0 = can_castle;
437 }
438 Side::Black => {
439 self.can_castle.black.1 = can_castle;
440 }
441 }
442 }
443
444 #[cfg_attr(feature = "flamegraph", inline(never))]
446 pub fn apply_piece_move(&mut self, mv: PieceMove) {
447 assert!(!self.moved_piece);
448 let side = self.next_move.unwrap();
449
450 let mut new_en_passant = None;
451
452 match mv {
453 PieceMove::Piece {
454 piece,
455 from,
456 to,
457 promotion,
458 ..
459 } => {
460 match piece {
461 PieceKind::Rook => {
462 if from.x() == 0 {
464 self.set_can_castle(false, true);
465 } else if from.x() == 7 {
467 self.set_can_castle(false, false);
468 }
469 }
470 PieceKind::King => {
471 self.set_can_castle(false, true);
472 self.set_can_castle(false, false);
473 }
474 PieceKind::Pawn => {
475 if from.x() == to.x() && from.y().abs_diff(to.y()) == 2
477 {
478 new_en_passant = Some(to);
479 }
480 }
481 _ => {}
482 }
483
484 let new_piece = match promotion {
486 Some(kind) => Piece { kind, side },
487 None => self.piece_at(from).unwrap(),
488 };
489
490 *self.piece_at_mut(from) = None;
491
492 match self.piece_at(to).map(|p| p.kind) {
493 Some(PieceKind::King) => {
494 self.winner = Some(side);
495 self.next_move = None;
496 }
497 _ => {}
498 }
499 self.piece_at_mut(to).replace(new_piece);
500 }
501 PieceMove::EnPassant { from, to } => {
502 let square = self.en_passant.take().unwrap();
503 *self.piece_at_mut(square) = None;
504 let pawn = self.piece_at(from).unwrap();
505 *self.piece_at_mut(from) = None;
506 self.piece_at_mut(to).replace(pawn);
507 }
508 PieceMove::Castle {
509 from_king,
510 to_king,
511 from_rook,
512 to_rook,
513 } => {
514 let king = self.piece_at(from_king).unwrap();
515 let rook = self.piece_at(from_rook).unwrap();
516 *self.piece_at_mut(from_king) = None;
517 *self.piece_at_mut(from_rook) = None;
518 self.piece_at_mut(to_king).replace(king);
519 self.piece_at_mut(to_rook).replace(rook);
520
521 self.set_can_castle(false, true);
522 self.set_can_castle(false, false);
523 }
524 }
525
526 self.en_passant = new_en_passant;
528 self.moved_piece = true;
529 }
530
531 #[cfg_attr(feature = "flamegraph", inline(never))]
532 pub fn apply_duck_move(
533 &mut self,
534 square: Square,
535 duck_square: Option<Square>,
536 ) {
537 assert!(self.moved_piece);
538 let next_move = self.next_move.unwrap();
539
540 if let Some(duck_square) = duck_square {
547 *self.piece_at_mut(duck_square) = None;
548 }
549
550 self.piece_at_mut(square).replace(Piece {
551 kind: PieceKind::Duck,
552 side: next_move,
553 });
554
555 self.moved_piece = false;
556 self.next_move = Some(next_move.other());
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
561#[serde(rename_all = "camelCase")]
562pub struct Piece {
563 pub kind: PieceKind,
564 pub side: Side,
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
568pub enum PieceKind {
569 Rook,
570 Knight,
571 Bishop,
572 King,
573 Queen,
574 Pawn,
575 Duck,
576}
577
578impl PieceKind {
579 pub fn directions(&self) -> &'static [Direction] {
581 use Direction::*;
582 match self {
583 Self::Rook => &[Up, Right, Down, Left],
584 Self::Knight => &[],
585 Self::Bishop => &[UpRight, DownRight, DownLeft, UpLeft],
586 Self::King => Direction::ALL_NO_KNIGHTS,
587 Self::Queen => Direction::ALL_NO_KNIGHTS,
588 Self::Pawn => &[],
589 Self::Duck => &[],
590 }
591 }
592
593 pub fn is_duck(&self) -> bool {
594 matches!(self, Self::Duck)
595 }
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase")]
600pub struct CanCastle {
601 pub white: (bool, bool),
603 pub black: (bool, bool),
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
607pub enum Side {
608 White,
609 Black,
610}
611
612impl Side {
613 pub fn other(&self) -> Self {
614 match self {
615 Self::White => Self::Black,
616 Self::Black => Self::White,
617 }
618 }
619
620 pub fn multi(&self) -> f32 {
622 match self {
623 Self::White => 1f32,
624 Self::Black => -1f32,
625 }
626 }
627}
628
629#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
630#[serde(rename_all = "camelCase")]
631pub struct Move {
632 pub piece: PieceMove,
633 pub duck: Option<Square>,
636 pub side: Side,
637}
638
639#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
640#[serde(tag = "kind", rename_all_fields = "camelCase")]
641pub enum PieceMove {
642 Piece {
643 piece: PieceKind,
644 from: Square,
645 to: Square,
646 capture: Option<PieceKind>,
647 promotion: Option<PieceKind>,
648 },
649 EnPassant {
650 from: Square,
651 to: Square,
652 },
653 Castle {
654 from_king: Square,
655 to_king: Square,
656 from_rook: Square,
657 to_rook: Square,
658 },
659}
660
661#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
662#[repr(u8)]
663#[rustfmt::skip]
664pub enum Square {
665 A8 = 0, B8, C8, D8, E8, F8, G8, H8,
666 A7, B7, C7, D7, E7, F7, G7, H7,
667 A6, B6, C6, D6, E6, F6, G6, H6,
668 A5, B5, C5, D5, E5, F5, G5, H5,
669 A4, B4, C4, D4, E4, F4, G4, H4,
670 A3, B3, C3, D3, E3, F3, G3, H3,
671 A2, B2, C2, D2, E2, F2, G2, H2,
672 A1, B1, C1, D1, E1, F1, G1, H1,
673}
674
675impl Square {
676 #[inline]
678 pub const fn from_u8(num: u8) -> Self {
679 assert!(num < 64);
680 unsafe { mem::transmute(num) }
681 }
682
683 pub unsafe fn from_u8_unchecked(num: u8) -> Self {
685 unsafe { mem::transmute(num) }
686 }
687
688 pub fn x(&self) -> u8 {
689 *self as u8 % 8
690 }
691
692 pub fn x_letter(&self) -> u8 {
694 self.x() + b'a'
695 }
696
697 pub fn y(&self) -> u8 {
698 *self as u8 / 8
699 }
700
701 #[inline]
703 pub const fn from_xy(x: u8, y: u8) -> Self {
704 Square::from_u8(y * 8 + x)
705 }
706
707 #[inline]
708 pub fn add_dir(&self, dir: Direction) -> Option<Self> {
709 let mut xy = (self.x() as i8, self.y() as i8);
710 dir.update_xy(&mut xy);
711
712 let (x, y) = xy;
713 if x < 0 || x >= 8 || y < 0 || y >= 8 {
714 return None;
715 }
716
717 Some(Self::from_xy(x as u8, y as u8))
718 }
719
720 #[inline]
722 pub fn apply_dir(&mut self, dir: Direction) -> bool {
723 if let Some(next) = self.add_dir(dir) {
724 *self = next;
725 true
726 } else {
727 false
728 }
729 }
730}
731
732impl fmt::Display for Square {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 write!(f, "{}{}", self.x_letter() as char, self.y() + 1)
735 }
736}
737
738#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
739pub enum Direction {
740 Up,
741 UpRight,
742 Right,
743 DownRight,
744 Down,
745 DownLeft,
746 Left,
747 UpLeft,
748 KnUpRight,
749 KnRightUp,
750 KnRightDown,
751 KnDownRight,
752 KnDownLeft,
753 KnLeftDown,
754 KnLeftUp,
755 KnUpLeft,
756}
757
758impl Direction {
759 const ALL_NO_KNIGHTS: &'static [Self] = &[
760 Self::Up,
761 Self::UpRight,
762 Self::Right,
763 Self::DownRight,
764 Self::Down,
765 Self::DownLeft,
766 Self::Left,
767 Self::UpLeft,
768 ];
769
770 const ALL_KNIGHTS: &'static [Self] = &[
771 Self::KnUpRight,
772 Self::KnRightUp,
773 Self::KnRightDown,
774 Self::KnDownRight,
775 Self::KnDownLeft,
776 Self::KnLeftDown,
777 Self::KnLeftUp,
778 Self::KnUpLeft,
779 ];
780
781 fn xy_change(&self) -> (i8, i8) {
782 match self {
783 Self::Up => (0, -1),
784 Self::UpRight => (1, -1),
785 Self::Right => (1, 0),
786 Self::DownRight => (1, 1),
787 Self::Down => (0, 1),
788 Self::DownLeft => (-1, 1),
789 Self::Left => (-1, 0),
790 Self::UpLeft => (-1, -1),
791 Self::KnUpRight => (1, -2),
793 Self::KnRightUp => (2, -1),
794 Self::KnRightDown => (2, 1),
795 Self::KnDownRight => (1, 2),
796 Self::KnDownLeft => (-1, 2),
797 Self::KnLeftDown => (-2, 1),
798 Self::KnLeftUp => (-2, -1),
799 Self::KnUpLeft => (-1, -2),
800 }
801 }
802
803 fn update_xy(&self, xy: &mut (i8, i8)) {
804 let change = self.xy_change();
805 xy.0 += change.0;
806 xy.1 += change.1;
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 fn sq_xy(square: Square) -> (u8, u8) {
815 (square.x(), square.y())
816 }
817
818 #[test]
819 fn square_xy() {
820 assert_eq!(sq_xy(Square::A8), (0, 0));
821 assert_eq!(sq_xy(Square::H8), (7, 0));
822 assert_eq!(sq_xy(Square::A1), (0, 7));
823 assert_eq!(sq_xy(Square::H1), (7, 7));
824 assert_eq!(Square::from_xy(0, 0), Square::A8);
825 assert_eq!(Square::from_xy(7, 0), Square::H8);
826 assert_eq!(Square::from_xy(0, 7), Square::A1);
827 assert_eq!(Square::from_xy(7, 7), Square::H1);
828 }
829
830 #[test]
831 fn apply_dir() {
832 let mut square = Square::E4;
833 assert!(square.apply_dir(Direction::Up));
834 assert_eq!(square, Square::E5);
835 assert!(square.apply_dir(Direction::UpLeft));
836 assert_eq!(square, Square::D6);
837 }
838}