Skip to main content

chess/board/
scharnagl.rs

1use core::{fmt, str};
2
3use crate::{
4    Square, finite_for,
5    square::{File, Rank},
6};
7
8use super::{Board, Player, Role};
9
10use Player::*;
11use Rank::*;
12use Role::*;
13
14#[cfg(test)]
15use File::*;
16
17/// Reinhard Scharnagl's enumeration of all 960 starting positions.
18///
19/// Standard chess is position 518.
20#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
21pub struct Scharnagl(u16);
22
23impl Scharnagl {
24    pub const CHESS: Self = Self(518);
25
26    pub const fn new(i: u16) -> Option<Self> {
27        if i < 960 { Some(Self(i)) } else { None }
28    }
29
30    pub const fn board(self) -> Board {
31        const KNIGHTS: [(u8, u8); 10] =
32            [(0, 0), (0, 1), (0, 2), (0, 3), (1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)];
33
34        const fn nth_free(roles: &[Role; 8], n: u8) -> File {
35            let mut seen = 0;
36            finite_for!(file in File {
37                if Pawn.eq(roles[file.index()]) {
38                    if seen == n {
39                        return file;
40                    }
41                    seen += 1;
42                }
43            });
44            unreachable!()
45        }
46
47        let mut i = self.0;
48        let mut roles = [Pawn; 8];
49
50        // Place light bishop on b/d/f/h according to i % 4
51        // IOW, last two bits
52        let light_bishop = i % 4;
53        i /= 4;
54        roles[(light_bishop * 2 + 1) as usize] = Bishop;
55
56        // Place dark bishop on a/c/e/g according to i % 4
57        // IOW, next two bits
58        let dark_bishop = i % 4;
59        i /= 4;
60        roles[(dark_bishop * 2) as usize] = Bishop;
61
62        // Place queen on remaining files according to i % 6
63        // IOW, next six numbers
64        let queen = i % 6;
65        i /= 6;
66        let queen = nth_free(&roles, queen as u8);
67        roles[queen.index()] = Queen;
68
69        // There are 960/4/4/6=10 cases left.
70        // Place the knights in any two remaining files, using the lookup table
71        // of all 2-of-4 subsets with replacement
72        let (left_knight, right_knight) = KNIGHTS[i as usize];
73        let left_knight = nth_free(&roles, left_knight);
74        roles[left_knight.index()] = Knight;
75        let right_knight = nth_free(&roles, right_knight);
76        roles[right_knight.index()] = Knight;
77
78        // Now fill in the remaining files with rooks and king,
79        // ensuring the king is between the rooks
80        let rook = nth_free(&roles, 0);
81        roles[rook.index()] = Rook;
82        let king = nth_free(&roles, 0);
83        roles[king.index()] = King;
84        let rook = nth_free(&roles, 0);
85        roles[rook.index()] = Rook;
86
87        let mut board = Board::EMPTY;
88        finite_for!(file in File {
89            let role = roles[file.index()];
90            board.insert(Square::new(file, One), role.of(White));
91            board.insert(Square::new(file, Two), White.pawn());
92            board.insert(Square::new(file, Seven), Black.pawn());
93            board.insert(Square::new(file, Eight), role.of(Black));
94        });
95        board
96    }
97
98    pub fn from_board(board: Board) -> Option<Self> {
99        (0..960).map(Self).find(|scharnagl| scharnagl.board() == board)
100    }
101}
102
103impl fmt::Display for Scharnagl {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        self.0.fmt(f)
106    }
107}
108
109impl str::FromStr for Scharnagl {
110    type Err = &'static str;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        let i = s.parse().map_err(|_| "Scharnagl index must be an integer from 0 to 959")?;
114        Self::new(i).ok_or("Scharnagl index must be an integer from 0 to 959")
115    }
116}
117
118#[test]
119fn freestyle_positions() {
120    use crate::{Position, Side};
121
122    assert_eq!(Scharnagl::new(960), None);
123    assert_eq!(Board::freestyle(Scharnagl(0)).fen(), "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR");
124    assert_eq!(
125        Board::freestyle(Scharnagl(631)).fen(),
126        "rnbkqrnb/pppppppp/8/8/8/8/PPPPPPPP/RNBKQRNB"
127    );
128    assert_eq!(Board::freestyle(Scharnagl::CHESS), Board::standard());
129    assert_eq!(
130        Board::freestyle(Scharnagl(959)).fen(),
131        "rkrnnqbb/pppppppp/8/8/8/8/PPPPPPPP/RKRNNQBB"
132    );
133    assert_eq!(Scharnagl::from_board(Board::EMPTY), None);
134    for i in 0..960 {
135        let scharnagl = Scharnagl(i);
136        assert_eq!(Scharnagl::from_board(scharnagl.board()), Some(scharnagl));
137    }
138
139    let position = Position::freestyle(Scharnagl::CHESS);
140    assert_eq!(position.board(), Position::start().board());
141    assert_eq!(position.castles().get(White, Side::Queen), Some(A));
142    assert_eq!(position.castles().get(White, Side::King), Some(H));
143    assert_eq!(position.castles().get(Black, Side::Queen), Some(A));
144    assert_eq!(position.castles().get(Black, Side::King), Some(H));
145}