Skip to main content

chess/board/
scharnagl.rs

1use core::fmt;
2
3use crate::{
4    Id, 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
99impl fmt::Display for Scharnagl {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        self.0.fmt(f)
102    }
103}
104
105#[cfg(feature = "const-fn-scharnagl-id")]
106// This allow turns the error into a warning, which cannot currently be suppressed.
107#[allow(long_running_const_eval)]
108// has to be ordered by ID itself
109pub static SCHARNAGL_BY_ID: [(Id, Scharnagl); 960] = generate_scharnagl_by_id();
110#[cfg(not(feature = "const-fn-scharnagl-id"))]
111include!("scharnagl-id.rs");
112
113#[cfg(feature = "const-fn-scharnagl-id")]
114const fn generate_scharnagl_by_id() -> [(Id, Scharnagl); 960] {
115    let mut table = [(Id(0), Scharnagl(0)); 960];
116
117    let mut i = 0;
118    // binary insertion sort
119    while i < 960 {
120        let scharnagl = Scharnagl(i as u16);
121        let entry = (Board::freestyle(scharnagl).standard_id(), scharnagl);
122
123        let mut j = i;
124        while j > 0 && entry.0.0 < table[j - 1].0.0 {
125            table[j] = table[j - 1];
126            j -= 1;
127        }
128        table[j] = entry;
129        i += 1;
130    }
131
132    table
133}
134
135pub fn scharnagl_by_id(id: Id) -> Option<Scharnagl> {
136    SCHARNAGL_BY_ID.binary_search_by_key(&id, |(id, _)| *id).ok().map(|i| SCHARNAGL_BY_ID[i].1)
137}
138
139#[test]
140fn freestyle_positions() {
141    use crate::{Position, Side};
142
143    assert_eq!(Scharnagl::new(960), None);
144    assert_eq!(Board::freestyle(Scharnagl(0)).fen(), "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR");
145    assert_eq!(
146        Board::freestyle(Scharnagl(631)).fen(),
147        "rnbkqrnb/pppppppp/8/8/8/8/PPPPPPPP/RNBKQRNB"
148    );
149    assert_eq!(Board::freestyle(Scharnagl::CHESS), Board::standard());
150    assert_eq!(
151        Board::freestyle(Scharnagl(959)).fen(),
152        "rkrnnqbb/pppppppp/8/8/8/8/PPPPPPPP/RKRNNQBB"
153    );
154
155    let position = Position::freestyle(Scharnagl::CHESS);
156    assert_eq!(position.board(), Position::start().board());
157    assert_eq!(position.castles().get(White, Side::Queen), Some(A));
158    assert_eq!(position.castles().get(White, Side::King), Some(H));
159    assert_eq!(position.castles().get(Black, Side::Queen), Some(A));
160    assert_eq!(position.castles().get(Black, Side::King), Some(H));
161}