Skip to main content

chess/formats/
cbg.rs

1//! Chessbase game file format (CBG)
2
3// Header: 26 B
4// Records: variable length
5//
6
7use crate::{
8    board::Role,
9    square::{File, Square},
10};
11
12use super::{ByteInput as Input, prelude::*};
13
14#[derive(Clone, Copy, Debug)]
15pub struct Header {
16    pub first_game: u16,
17    pub len: u32,
18    pub accum: u32,
19}
20
21// This seems wrong..
22pub fn header(input: &mut Input<'_>) -> ModalResult<Header> {
23    let first_game = be_u16.parse_next(input)?;
24    let len = be_u32.parse_next(input)?;
25    let accum = be_u32.parse_next(input)?;
26    let zero1 = be_u32.parse_next(input)?;
27    let len2 = be_u32.parse_next(input)?;
28    let zero2 = be_u32.parse_next(input)?;
29
30    assert_eq!(len, len2);
31    assert_eq!(zero1, 0);
32    assert_eq!(zero2, 0);
33
34    Ok(Header { first_game, len, accum })
35}
36
37#[derive(Clone, Copy, Debug)]
38pub struct Info {
39    pub len: u32,
40    pub not_initial: bool,
41    pub not_encoded: bool,
42    pub special_encoding: bool,
43    pub freestyle: bool,
44}
45
46pub fn info(input: &mut Input<'_>) -> ModalResult<Info> {
47    let info = u8.parse_next(input)?;
48    let len = be_u24.parse_next(input)?;
49    let not_initial = info & 0x40 != 0;
50    let not_encoded = info & 0x80 != 0;
51    let special_encoding = info & 4 != 0;
52    let freestyle = info & 0xA > 0;
53    // assert_eq!(info, 0);
54    Ok(Info { len, not_initial, not_encoded, special_encoding, freestyle })
55}
56#[derive(Clone, Copy, Debug)]
57pub struct Game {
58    pub info: Info,
59}
60
61pub fn game(input: &mut Input<'_>) -> ModalResult<Game> {
62    let info = info.parse_next(input)?;
63    if info.not_initial {
64        unimplemented!();
65    }
66    // assert!(!info.special_encoding);
67    // assert!(!info.freestyle);
68
69    Ok(Game { info })
70}
71
72#[test]
73fn example() {
74    let input = include_bytes!("../../examples/twic1616.cbh");
75    let headers = super::cbh::headers.parse(input).expect("can pass CBH file");
76
77    let input = include_bytes!("../../examples/twic1616.cbg");
78    let header = header.parse_next(&mut input.as_slice()).expect("can parse CBG header");
79    assert_eq!(input.len(), header.len as usize);
80    println!("{header:?}");
81
82    for (i, record) in headers.records.iter().enumerate() {
83        let mut input = &input[record.game_offset..];
84        let game = game.parse_next(&mut input).unwrap();
85        println!(":: {i}: {game:?}");
86    }
87    // panic!("wtf");
88}
89
90/// For some obfuscation reason, the (mostly) logical move-to-byte
91/// encodings are permuted.
92fn permute(x: u8, count: usize) -> u8 {
93    let index = x.wrapping_sub(count as u8) as usize;
94    PERMUTE[index]
95}
96
97/// The tokens that moves are encoded as
98#[derive(Clone, Copy, Debug)]
99pub enum Token {
100    Pop,
101    Push,
102    Move(Move),
103    Skip,
104}
105
106#[derive(Clone, Copy, Debug)]
107pub enum Move {
108    Null,
109    // (x, y) moves: -1 <= x,y <= 1
110    King(i8, i8),
111    // x move; x = 2 or x = -2
112    Castle(i8),
113    // queen index 0, 1, or 2, and (x, y) moves: 1 <= x,y < 8
114    Queen(u8, u8, u8),
115    // rook index 0, 1, or 2, and (x, y) moves: 1 <= x,y < 8
116    Rook(u8, u8, u8),
117    // bishop index 0, 1, or 2, and (x, y) moves: 1 <= x,y < 8
118    Bishop(u8, u8, u8),
119    // knight index 0, 1, or 2, and (x, y) moves: -2 <= x,y < 2
120    Knight(u8, i8, i8),
121    // any file
122    Pawn(File, PawnMove),
123    // (from, to, role) squares and valid promotion Role
124    //
125    // It seems that "a fourth piece never promotes, and its movements
126    // are always captured with multiple bytes".
127    //
128    // Only if the move is a pawn move that promotes, is the role valid.
129    //
130    // Any move could be encoded (UCI-style) with this (from, to) method,
131    // but typically it's only done for moving "fourth pieces" or pawns.
132    FromTo(Square, Square, Role),
133}
134
135#[repr(u8)]
136#[derive(Clone, Copy, Debug)]
137pub enum PawnMove {
138    One,
139    Two,
140    // captures, left/right from the perspective of the player
141    Left,
142    Right,
143}
144
145// compactification of scidb's cbh_decoder.cpp
146pub fn token<'a>(count: &mut usize) -> impl FnMut(&mut Input<'a>) -> ModalResult<Token> {
147    use Move::{Castle, King, Null, Pawn};
148
149    move |input: &mut Input<'_>| {
150        // seems like every game is supposed to end with this
151        // but there are (rare) games that are missing the token,
152        // so we patch it in.
153        if input.is_empty() {
154            return Ok(Token::Pop);
155        }
156
157        let byte = permute(u8.parse_next(input)?, *count);
158
159        let play = match byte {
160            // Null
161            0x00 => Null,
162
163            // King
164            0x01 => King(0, 1),
165            0x02 => King(1, 1),
166            0x03 => King(1, 0),
167            0x04 => King(1, -1),
168            0x05 => King(0, -1),
169            0x06 => King(-1, -1),
170            0x07 => King(-1, 0),
171            0x08 => King(-1, 1),
172            0x09 => Castle(2),
173            0x0a => Castle(-2),
174
175            // Queen
176            queen @ 0x0b..=0x26 => queen_move(0, queen - 0x0b),
177            queen @ 0x8f..=0xaa => queen_move(1, queen - 0x8f),
178            queen @ 0xab..=0xc6 => queen_move(2, queen - 0xab),
179
180            // Rook
181            rook @ 0x27..=0x34 => rook_move(0, rook - 0x27),
182            rook @ 0x35..=0x42 => rook_move(1, rook - 0x35),
183            rook @ 0xc7..=0xd4 => rook_move(2, rook - 0xc7),
184
185            // Bishop
186            bishop @ 0x43..=0x50 => bishop_move(0, bishop - 0x43),
187            bishop @ 0x51..=0x5e => bishop_move(1, bishop - 0x51),
188            bishop @ 0xd5..=0xe2 => bishop_move(2, bishop - 0xd5),
189
190            // Knight
191            knight @ 0x5f..=0x66 => knight_move(0, knight - 0x5f),
192            knight @ 0x67..=0x6e => knight_move(1, knight - 0x67),
193            knight @ 0xe3..=0xea => knight_move(2, knight - 0xea),
194
195            // Pawn
196            pawn @ 0x6f..=0x8e => {
197                use PawnMove::*;
198
199                let offset = pawn - 0x6f;
200                let file = File::panicky_from_index(offset / 8);
201                match offset % 4 {
202                    0 => Pawn(file, One),
203                    1 => Pawn(file, Two),
204                    2 => Pawn(file, Right),
205                    3 => Pawn(file, Left),
206                    _ => unreachable!(),
207                }
208            }
209
210            // Promote
211            0xeb => {
212                use Role::*;
213
214                let hi = permute(u8.parse_next(input)?, *count) as u16;
215                let lo = permute(u8.parse_next(input)?, *count) as u16;
216                let word = (hi << 8) | lo;
217
218                let from = word & 63;
219                let to = (word >> 6) & 63;
220
221                let promoted = match (word >> 12) & 3 {
222                    0 => Queen,
223                    1 => Rook,
224                    2 => Bishop,
225                    3 => Knight,
226                    _ => unreachable!(),
227                };
228
229                Move::FromTo(
230                    Square::panicky_from_index(from as u8),
231                    Square::panicky_from_index(to as u8),
232                    promoted,
233                )
234            }
235
236            // Skip
237            0xec..=0xfd => return Ok(Token::Skip),
238
239            // Push
240            0xfe => return Ok(Token::Push),
241
242            // Pop
243            0xff => return Ok(Token::Pop),
244        };
245
246        *count += 1;
247        Ok(Token::Move(play))
248    }
249}
250
251fn queen_move(i: u8, queen: u8) -> Move {
252    use Move::Queen;
253
254    match queen {
255        y @ 0x00..=0x06 => Queen(i, 0, y + 1), // (0, 1) ... (0, 7)
256        x @ 0x07..=0x14 => Queen(i, x - 6, 0), // (1, 0) ... (7, 0)
257        d @ 0x15..=0x1b => Queen(i, d - 0x14, d - 0x14), // (1, 1) ... (7, 7)
258        e @ 0x1c..=0x22 => Queen(i, e - 0x1b, 0x1c + 7 - e), // (1, 7) ... (7, 1)
259        _ => unreachable!(),
260    }
261}
262
263fn rook_move(i: u8, rook: u8) -> Move {
264    use Move::Rook;
265
266    match rook {
267        x @ 0x0..=0x6 => Rook(i, 0, x + 1), // (0, 1) .. (0, 7)
268        y @ 0x7..=0xd => Rook(i, y - 6, 0), // (1, 0) .. (7, 0)
269        _ => unreachable!(),
270    }
271}
272
273fn bishop_move(i: u8, bishop: u8) -> Move {
274    use Move::Bishop;
275
276    match bishop {
277        d @ 0x0..=0x6 => Bishop(i, d + 1, d + 1), // (1, 1) ... (7, 7)
278        e @ 0x7..=0xd => Bishop(i, e - 6, 14 - e), // (1, 7) ... (7, 1)
279        _ => unreachable!(),
280    }
281}
282
283fn knight_move(i: u8, knight: u8) -> Move {
284    use Move::Knight;
285
286    match knight {
287        0 => Knight(i, 2, 1),
288        1 => Knight(i, 1, 2),
289        2 => Knight(i, -1, 2),
290        3 => Knight(i, -2, 1),
291        4 => Knight(i, -2, -1),
292        5 => Knight(i, -1, -2),
293        6 => Knight(i, 1, -2),
294        7 => Knight(i, 2, -1),
295        _ => unreachable!(),
296    }
297}
298
299pub const PERMUTE: [u8; 256] = [
300    0xa2, 0x95, 0x43, 0xf5, 0xc1, 0x3d, 0x4a, 0x6c, //   0 -   7
301    0x53, 0x83, 0xcc, 0x7c, 0xff, 0xae, 0x68, 0xad, //   8 -  15
302    0xd1, 0x92, 0x8b, 0x8d, 0x35, 0x81, 0x5e, 0x74, //  16 -  23
303    0x26, 0x8e, 0xab, 0xca, 0xfd, 0x9a, 0xf3, 0xa0, //  24 -  31
304    0xa5, 0x15, 0xfc, 0xb1, 0x1e, 0xed, 0x30, 0xea, //  32 -  39
305    0x22, 0xeb, 0xa7, 0xcd, 0x4e, 0x6f, 0x2e, 0x24, //  40 -  47
306    0x32, 0x94, 0x41, 0x8c, 0x6e, 0x58, 0x82, 0x50, //  48 -  55
307    0xbb, 0x02, 0x8a, 0xd8, 0xfa, 0x60, 0xde, 0x52, //  56 -  63
308    0xba, 0x46, 0xac, 0x29, 0x9d, 0xd7, 0xdf, 0x08, //  64 -  71
309    0x21, 0x01, 0x66, 0xa3, 0xf1, 0x19, 0x27, 0xb5, //  72 -  79
310    0x91, 0xd5, 0x42, 0x0e, 0xb4, 0x4c, 0xd9, 0x18, //  80 -  87
311    0x5f, 0xbc, 0x25, 0xa6, 0x96, 0x04, 0x56, 0x6a, //  88 -  95
312    0xaa, 0x33, 0x1c, 0x2b, 0x73, 0xf0, 0xdd, 0xa4, //  96 - 103
313    0x37, 0xd3, 0xc5, 0x10, 0xbf, 0x5a, 0x23, 0x34, // 104 - 111
314    0x75, 0x5b, 0xb8, 0x55, 0xd2, 0x6b, 0x09, 0x3a, // 112 - 119
315    0x57, 0x12, 0xb3, 0x77, 0x48, 0x85, 0x9b, 0x0f, // 120 - 127
316    0x9e, 0xc7, 0xc8, 0xa1, 0x7f, 0x7a, 0xc0, 0xbd, // 128 - 135
317    0x31, 0x6d, 0xf6, 0x3e, 0xc3, 0x11, 0x71, 0xce, // 136 - 143
318    0x7d, 0xda, 0xa8, 0x54, 0x90, 0x97, 0x1f, 0x44, // 144 - 151
319    0x40, 0x16, 0xc9, 0xe3, 0x2c, 0xcb, 0x84, 0xec, // 152 - 159
320    0x9f, 0x3f, 0x5c, 0xe6, 0x76, 0x0b, 0x3c, 0x20, // 160 - 167
321    0xb7, 0x36, 0x00, 0xdc, 0xe7, 0xf9, 0x4f, 0xf7, // 168 - 175
322    0xaf, 0x06, 0x07, 0xe0, 0x1a, 0x0a, 0xa9, 0x4b, // 176 - 183
323    0x0c, 0xd6, 0x63, 0x87, 0x89, 0x1d, 0x13, 0x1b, // 184 - 191
324    0xe4, 0x70, 0x05, 0x47, 0x67, 0x7b, 0x2f, 0xee, // 192 - 199
325    0xe2, 0xe8, 0x98, 0x0d, 0xef, 0xcf, 0xc4, 0xf4, // 200 - 207
326    0xfb, 0xb0, 0x17, 0x99, 0x64, 0xf2, 0xd4, 0x2a, // 208 - 215
327    0x03, 0x4d, 0x78, 0xc6, 0xfe, 0x65, 0x86, 0x88, // 216 - 223
328    0x79, 0x45, 0x3b, 0xe5, 0x49, 0x8f, 0x2d, 0xb9, // 224 - 231
329    0xbe, 0x62, 0x93, 0x14, 0xe9, 0xd0, 0x38, 0x9c, // 232 - 239
330    0xb2, 0xc2, 0x59, 0x5d, 0xb6, 0x72, 0x51, 0xf8, // 240 - 247
331    0x28, 0x7e, 0x61, 0x39, 0xe1, 0xdb, 0x69, 0x80, // 248 - 255
332];