1use core::num::NonZeroU32;
2
3use winnow::binary::bits::{self, Bits as BitInput};
4
5use crate::{
6 Player, Side,
7 board::{Piece, Role},
8 formats::{ByteInput as Input, prelude::*},
9 position::SideTable,
10 square::{File, Rank, Square},
11};
12
13use super::{Castles, Game, Header, Info, Mode, Move, PawnMove, Pieces, Position, Token};
14
15pub fn header(input: &mut Input<'_>) -> ModalResult<Header> {
16 let first_game = be_u16.parse_next(input)?;
17 let len = be_u32.parse_next(input)?;
18 let accum = be_u32.parse_next(input)?;
20 let zero1 = be_u32.parse_next(input)?;
21 let len2 = be_u32.parse_next(input)?;
22 let zero2 = be_u32.parse_next(input)?;
23 let accum2 = be_u32.parse_next(input)?;
24
25 debug_assert_eq!(len, len2);
26 debug_assert_eq!(accum, accum2);
27 debug_assert_eq!(zero1, 0);
28 debug_assert_eq!(zero2, 0);
29
30 Ok(Header { first_game, len })
31}
32
33pub fn game_with_info(input: &mut Input<'_>) -> ModalResult<(Info, Game)> {
34 let info = info.parse_next(input)?;
35 let game = take(info.len).and_then(game(info)).parse_next(input)?;
36 Ok((info, game))
37}
38
39pub fn game(info: Info) -> impl FnMut(&mut Input<'_>) -> ModalResult<Game> {
40 let mode = info.mode;
41 move |input: &mut Input<'_>| {
42 if mode.is_undecodable() || mode.has_special_encoding() {
43 return Err(backtrack());
44 }
45
46 let position = if mode.has_setup() {
47 let position = position.parse_next(input)?;
48 if mode.is_freestyle() {
49 let _: (u32, u32) = (be_u32, be_u32).parse_next(input)?;
50 }
51 position
52 } else {
53 Position::standard()
54 };
55 let tokens = tokens.parse_next(input)?;
56 Ok(Game { position, tokens })
57 }
58}
59
60pub fn info(input: &mut Input<'_>) -> ModalResult<Info> {
61 let mode = mode.parse_next(input)?;
62 let len = be_u24.verify(|len| *len >= 4).parse_next(input)? as usize - 4;
63 Ok(Info { len, mode })
64}
65
66pub fn mode(input: &mut Input<'_>) -> ModalResult<Mode> {
67 u8.map(Mode).parse_next(input)
68}
69
70pub fn position(input: &mut Input<'_>) -> ModalResult<Position> {
71 bits::bits::<_, _, ContextMode, _, _>(position_bits).parse_next(input)
72}
73
74fn position_bits(input: &mut BitInput<Input<'_>>) -> ModalResult<Position> {
75 let _: u16 = bits::take(11usize).parse_next(input)?;
76 let turn = turn.parse_next(input)?;
77 let en_passant = en_passant.parse_next(input)?;
78 let _: u8 = bits::take(4usize).parse_next(input)?;
79 let castles = castles.parse_next(input)?;
80 let round = round.parse_next(input)?;
81 let (pieces, count) = pieces.parse_next(input)?;
82 let padding = 128usize.checked_sub(4 * count).ok_or_else(backtrack)?;
83 let _: u128 = bits::take(padding).parse_next(input)?;
84
85 Ok(Position { pieces, turn, castles, en_passant, round })
86}
87
88fn turn(input: &mut BitInput<Input<'_>>) -> ModalResult<Player> {
89 bits::bool.map(|black| if black { Player::Black } else { Player::White }).parse_next(input)
90}
91
92fn en_passant(input: &mut BitInput<Input<'_>>) -> ModalResult<Option<File>> {
93 bits::take(4usize)
94 .verify_map(|file: u8| match file {
95 0 => Some(None),
96 1..=8 => Some(File::from_index(file - 1)),
97 _ => None,
98 })
99 .parse_next(input)
100}
101
102fn castles(input: &mut BitInput<Input<'_>>) -> ModalResult<Castles> {
103 use Player::*;
104 use Side::*;
105
106 let mut castles = Castles::empty();
107 for player in [Black, White] {
108 for side in [King, Queen] {
109 castles[player][side] = bits::bool.parse_next(input)?;
110 }
111 }
112 Ok(castles)
113}
114
115fn round(input: &mut BitInput<Input<'_>>) -> ModalResult<NonZeroU32> {
116 bits::take(8usize)
117 .map(|round: u8| NonZeroU32::new(u32::from(round).max(1)).unwrap())
119 .parse_next(input)
120}
121
122fn pieces(input: &mut BitInput<Input<'_>>) -> ModalResult<(Pieces, usize)> {
123 let mut pieces = Pieces::empty();
124 let mut count = 0;
125 for index in 0..64 {
126 let Some(piece) = piece.parse_next(input)? else { continue };
127 let identity = pieces[piece.player][piece.role]
128 .iter_mut()
129 .find(|square| square.is_none())
130 .ok_or_else(backtrack)?;
131 *identity = Some(square(index));
132 count += 1;
133 }
134 Ok((pieces, count))
135}
136
137fn piece(input: &mut BitInput<Input<'_>>) -> ModalResult<Option<Piece>> {
138 if !bits::bool.parse_next(input)? {
139 return Ok(None);
140 }
141 bits::take(4usize).verify_map(encoded_piece).map(Some).parse_next(input)
142}
143
144pub fn tokens(input: &mut Input<'_>) -> ModalResult<Vec<Token>> {
145 let mut unscrambler = Unscrambler::default();
146 repeat(0.., token(&mut unscrambler)).parse_next(input)
147}
148
149impl Position {
150 fn standard() -> Self {
151 use Player::*;
152
153 Self {
154 pieces: Pieces::standard(),
155 turn: White,
156 castles: Castles::filled(SideTable::filled(true)),
157 en_passant: None,
158 round: NonZeroU32::MIN,
159 }
160 }
161}
162
163fn encoded_piece(piece: u8) -> Option<Piece> {
164 use Player::*;
165
166 Some(match piece {
167 1 => White.king(),
168 2 => White.queen(),
169 3 => White.knight(),
170 4 => White.bishop(),
171 5 => White.rook(),
172 6 => White.pawn(),
173 9 => Black.king(),
174 10 => Black.queen(),
175 11 => Black.knight(),
176 12 => Black.bishop(),
177 13 => Black.rook(),
178 14 => Black.pawn(),
179 _ => return None,
180 })
181}
182
183#[derive(Clone, Copy, Default)]
184pub struct Unscrambler {
185 offset: u8,
186}
187
188impl Unscrambler {
189 pub fn byte(self, input: &mut Input<'_>) -> ModalResult<u8> {
190 u8.map(|byte| {
191 let index = byte.wrapping_sub(self.offset) as usize;
192 PERMUTE[index]
193 })
194 .parse_next(input)
195 }
196
197 pub fn bump(&mut self) {
198 self.offset = self.offset.wrapping_add(1);
199 }
200}
201
202fn token<'i, 'u>(
203 unscrambler: &'u mut Unscrambler,
204) -> impl FnMut(&mut Input<'i>) -> ModalResult<Token> + 'u {
205 move |input| {
206 let token = scrambled_token(*unscrambler).parse_next(input)?;
207 if matches!(token, Token::Move(_)) {
208 unscrambler.bump();
209 }
210 Ok(token)
211 }
212}
213
214fn scrambled_token<'a>(
215 unscrambler: Unscrambler,
216) -> impl FnMut(&mut Input<'a>) -> ModalResult<Token> {
217 use Move::{Castle, King, Null};
218
219 move |input: &mut Input<'_>| {
220 Ok(Token::Move(match unscrambler.byte(input)? {
221 0x00 => Null,
223
224 0x01 => King(0, 1),
226 0x02 => King(1, 1),
227 0x03 => King(1, 0),
228 0x04 => King(1, -1),
229 0x05 => King(0, -1),
230 0x06 => King(-1, -1),
231 0x07 => King(-1, 0),
232 0x08 => King(-1, 1),
233 0x09 => Castle(2),
234 0x0a => Castle(-2),
235
236 queen @ 0x0b..=0x26 => Move::queen(0, queen - 0x0b),
238 queen @ 0x8f..=0xaa => Move::queen(1, queen - 0x8f),
239 queen @ 0xab..=0xc6 => Move::queen(2, queen - 0xab),
240
241 rook @ 0x27..=0x34 => Move::rook(0, rook - 0x27),
243 rook @ 0x35..=0x42 => Move::rook(1, rook - 0x35),
244 rook @ 0xc7..=0xd4 => Move::rook(2, rook - 0xc7),
245
246 bishop @ 0x43..=0x50 => Move::bishop(0, bishop - 0x43),
248 bishop @ 0x51..=0x5e => Move::bishop(1, bishop - 0x51),
249 bishop @ 0xd5..=0xe2 => Move::bishop(2, bishop - 0xd5),
250
251 knight @ 0x5f..=0x66 => Move::knight(0, knight - 0x5f),
253 knight @ 0x67..=0x6e => Move::knight(1, knight - 0x67),
254 knight @ 0xe3..=0xea => Move::knight(2, knight - 0xe3),
255
256 pawn @ 0x6f..=0x8e => Move::pawn(pawn - 0x6f),
258
259 0xeb => {
261 let hi = u16::from(unscrambler.byte(input)?);
262 let lo = u16::from(unscrambler.byte(input)?);
263 Move::promote((hi << 8) | lo)
264 }
265
266 0xec..=0xfd => return Ok(Token::Skip),
268
269 0xfe => return Ok(Token::Push),
271
272 0xff => return Ok(Token::Pop),
274 }))
275 }
276}
277
278fn square(index: u8) -> Square {
279 debug_assert!(index < 64);
280 Square::new(File::panicky_from_index(index >> 3), Rank::panicky_from_index(index & 7))
281}
282
283impl Move {
284 fn bishop(i: u8, bishop: u8) -> Self {
285 use Move::Bishop;
286
287 match bishop {
288 d @ 0x0..=0x6 => Bishop(i, d + 1, d + 1), e @ 0x7..=0xd => Bishop(i, e - 6, 14 - e), _ => unreachable!(),
291 }
292 }
293
294 fn knight(i: u8, knight: u8) -> Self {
295 use Move::Knight;
296
297 match knight {
298 0 => Knight(i, 2, 1),
299 1 => Knight(i, 1, 2),
300 2 => Knight(i, -1, 2),
301 3 => Knight(i, -2, 1),
302 4 => Knight(i, -2, -1),
303 5 => Knight(i, -1, -2),
304 6 => Knight(i, 1, -2),
305 7 => Knight(i, 2, -1),
306 _ => unreachable!(),
307 }
308 }
309
310 fn pawn(pawn: u8) -> Self {
311 use Move::Pawn;
312 use PawnMove::*;
313
314 let file = File::panicky_from_index(pawn / 4);
315 match pawn % 4 {
316 0 => Pawn(file, One),
317 1 => Pawn(file, Two),
318 2 => Pawn(file, Right),
319 3 => Pawn(file, Left),
320 _ => unreachable!(),
321 }
322 }
323
324 fn promote(word: u16) -> Self {
325 use Role::*;
326
327 let from = word & 63;
328 let to = (word >> 6) & 63;
329 let role = match (word >> 12) & 3 {
330 0 => Queen,
331 1 => Rook,
332 2 => Bishop,
333 3 => Knight,
334 _ => unreachable!(),
335 };
336 Self::FromTo(square(from as u8), square(to as u8), role)
337 }
338
339 fn queen(i: u8, queen: u8) -> Self {
340 use Move::Queen;
341
342 match queen {
343 y @ 0x00..=0x06 => Queen(i, 0, y + 1), x @ 0x07..=0x0d => Queen(i, x - 6, 0), d @ 0x0e..=0x14 => Queen(i, d - 0x0d, d - 0x0d), e @ 0x15..=0x1b => Queen(i, e - 0x14, 0x1c - e), _ => unreachable!(),
348 }
349 }
350
351 fn rook(i: u8, rook: u8) -> Self {
352 use Move::Rook;
353
354 match rook {
355 x @ 0x0..=0x6 => Rook(i, 0, x + 1), y @ 0x7..=0xd => Rook(i, y - 6, 0), _ => unreachable!(),
358 }
359 }
360}
361
362pub const PERMUTE: [u8; 256] = [
363 0xa2, 0x95, 0x43, 0xf5, 0xc1, 0x3d, 0x4a, 0x6c, 0x53, 0x83, 0xcc, 0x7c, 0xff, 0xae, 0x68, 0xad, 0xd1, 0x92, 0x8b, 0x8d, 0x35, 0x81, 0x5e, 0x74, 0x26, 0x8e, 0xab, 0xca, 0xfd, 0x9a, 0xf3, 0xa0, 0xa5, 0x15, 0xfc, 0xb1, 0x1e, 0xed, 0x30, 0xea, 0x22, 0xeb, 0xa7, 0xcd, 0x4e, 0x6f, 0x2e, 0x24, 0x32, 0x94, 0x41, 0x8c, 0x6e, 0x58, 0x82, 0x50, 0xbb, 0x02, 0x8a, 0xd8, 0xfa, 0x60, 0xde, 0x52, 0xba, 0x46, 0xac, 0x29, 0x9d, 0xd7, 0xdf, 0x08, 0x21, 0x01, 0x66, 0xa3, 0xf1, 0x19, 0x27, 0xb5, 0x91, 0xd5, 0x42, 0x0e, 0xb4, 0x4c, 0xd9, 0x18, 0x5f, 0xbc, 0x25, 0xa6, 0x96, 0x04, 0x56, 0x6a, 0xaa, 0x33, 0x1c, 0x2b, 0x73, 0xf0, 0xdd, 0xa4, 0x37, 0xd3, 0xc5, 0x10, 0xbf, 0x5a, 0x23, 0x34, 0x75, 0x5b, 0xb8, 0x55, 0xd2, 0x6b, 0x09, 0x3a, 0x57, 0x12, 0xb3, 0x77, 0x48, 0x85, 0x9b, 0x0f, 0x9e, 0xc7, 0xc8, 0xa1, 0x7f, 0x7a, 0xc0, 0xbd, 0x31, 0x6d, 0xf6, 0x3e, 0xc3, 0x11, 0x71, 0xce, 0x7d, 0xda, 0xa8, 0x54, 0x90, 0x97, 0x1f, 0x44, 0x40, 0x16, 0xc9, 0xe3, 0x2c, 0xcb, 0x84, 0xec, 0x9f, 0x3f, 0x5c, 0xe6, 0x76, 0x0b, 0x3c, 0x20, 0xb7, 0x36, 0x00, 0xdc, 0xe7, 0xf9, 0x4f, 0xf7, 0xaf, 0x06, 0x07, 0xe0, 0x1a, 0x0a, 0xa9, 0x4b, 0x0c, 0xd6, 0x63, 0x87, 0x89, 0x1d, 0x13, 0x1b, 0xe4, 0x70, 0x05, 0x47, 0x67, 0x7b, 0x2f, 0xee, 0xe2, 0xe8, 0x98, 0x0d, 0xef, 0xcf, 0xc4, 0xf4, 0xfb, 0xb0, 0x17, 0x99, 0x64, 0xf2, 0xd4, 0x2a, 0x03, 0x4d, 0x78, 0xc6, 0xfe, 0x65, 0x86, 0x88, 0x79, 0x45, 0x3b, 0xe5, 0x49, 0x8f, 0x2d, 0xb9, 0xbe, 0x62, 0x93, 0x14, 0xe9, 0xd0, 0x38, 0x9c, 0xb2, 0xc2, 0x59, 0x5d, 0xb6, 0x72, 0x51, 0xf8, 0x28, 0x7e, 0x61, 0x39, 0xe1, 0xdb, 0x69, 0x80, ];
396
397#[cfg(test)]
398mod tests {
399 use std::collections::BTreeMap;
400 use std::io::Cursor;
401
402 use hex_literal::hex;
403 use winnow::Parser as _;
404
405 use crate::formats::{
406 Fen,
407 chessbase::{archive, index},
408 pgn, twic,
409 };
410
411 use super::*;
412
413 fn unpack(input: &[u8]) -> BTreeMap<String, Vec<u8>> {
414 let mut input = input;
415 let header = archive::read_header(&mut input).expect("can parse CBV header");
416 header
417 .members
418 .iter()
419 .map(|member| {
420 let mut data = Vec::new();
421 archive::unpack_to(member, &mut input, &mut data).expect("can unpack CBV member");
422 assert_eq!(data.len(), member.len);
423 (member.name.clone(), data)
424 })
425 .collect()
426 }
427
428 fn encode(tokens: &[u8]) -> Vec<u8> {
429 let mut count = 0_u8;
430 tokens
431 .iter()
432 .map(|token| {
433 let index = PERMUTE
434 .iter()
435 .position(|candidate| candidate == token)
436 .expect("canonical token is represented") as u8;
437 let encoded = index.wrapping_add(count);
438 if *token <= 0xeb {
439 count = count.wrapping_add(1);
440 }
441 encoded
442 })
443 .collect()
444 }
445
446 fn mainline(game: &crate::Game) -> Vec<crate::Move> {
447 let mut plays = Vec::new();
448 let mut position = game.start();
449 while let Ok(play) = position.main() {
450 plays.push(play.play());
451 position = play.position();
452 }
453 plays
454 }
455
456 #[test]
457 #[ignore = "exhaustive comparison against the external TWIC fixture"]
458 fn twic_games() {
459 let index = twic::data(1616, "cbh");
460 let index = index::index.parse(index.as_slice()).expect("can parse CBH file");
461
462 let input = twic::data(1616, "cbg");
463 let header = header.parse_next(&mut input.as_slice()).expect("can parse CBG header");
464 assert_eq!(input.len(), header.len as usize);
465 let pgns = pgn::stream::pgns(Cursor::new(twic::data(1616, "pgn")));
466
467 let mut count = 0;
468 for (i, (indexed, pgn)) in index.games.iter().zip(pgns).enumerate() {
469 let mut input = &input[indexed.offsets.data..];
470 let encoded = game_with_info.parse_next(&mut input).unwrap().1;
471 let actual = crate::Game::from_chessbase(encoded)
472 .unwrap_or_else(|error| panic!("CBG game {i}: {error}"));
473 let expected = crate::Game::from_pgn(
474 pgn.unwrap_or_else(|error| panic!("PGN game {i}: {error}"))
475 .unwrap_or_else(|error| panic!("PGN game {i}: {error}")),
476 )
477 .unwrap_or_else(|error| panic!("PGN game {i}: {error}"));
478
479 assert_eq!(mainline(&actual), mainline(&expected), "game {i}");
480 count += 1;
481 }
482 assert_eq!(count, index.games.len());
483 }
484
485 #[test]
486 fn twic_game() {
487 const INPUT: [u8; 109] = hex!(
488 "0000006d0b08dc870210c19a0a110081
489 82882659a72344d4124253447cf81804
490 98bb07014e172835883cab0772e32431
491 097526317d4384464d3e0a1490fc4c78
492 fa61f88cb815f05b46c97dc02979f1e2
493 462aafd3c9de2f9d06aa9914c7ffa818
494 54e1b5db120fc36c1c9b9f2674"
495 );
496
497 let (_, encoded) = game_with_info.parse(INPUT.as_slice()).expect("can parse CBG game");
498 let actual = crate::Game::from_chessbase(encoded).expect("can convert CBG game");
499 let actual = mainline(&actual);
500 let expected = crate::Game::from_pgn(
501 pgn::pgn.parse("1. d4 Nf6 2. c4 e6 *").expect("valid expected PGN"),
502 )
503 .expect("valid expected game");
504
505 assert_eq!(actual.len(), 104);
506 assert!(actual.starts_with(&mainline(&expected)));
507 }
508
509 #[test]
510 fn twic_freestyle_position() {
511 const INPUT: [u8; 116] = hex!(
512 "4a00007401000f019d83db9d83dbad83
513 dd8d83d9a583dc9583daad83dda583dc
514 181f30103717004bb9345889fd31341a
515 aca950064f5218214585e02302590678
516 e2f98f214964f66fbafa7cbc500affe0
517 82feace6bb8c620a20d1db06775b6b9c
518 8b44fd09fb752a58dd162a16e007df08
519 2197d8a6"
520 );
521
522 let (info, encoded) =
523 game_with_info.parse(INPUT.as_slice()).expect("can parse Freestyle CBG game");
524 let position = crate::Position::try_from(encoded.position)
525 .expect("can convert Freestyle CBG starting position");
526 let expected: Fen = "nnrkbqrb/pppppppp/8/8/8/8/PPPPPPPP/NNRKBQRB w GCgc - 0 1"
527 .parse()
528 .expect("valid expected FEN");
529
530 assert!(info.mode.has_setup());
531 assert!(info.mode.is_freestyle());
532 assert_eq!(position, expected.0);
533 }
534
535 #[test]
536 fn variations() {
537 let bytes = encode(&[0xfe, 0x80, 0x80, 0xff, 0x7c, 0x7c, 0xff]);
538 let len = u32::try_from(bytes.len() + 4).unwrap();
539 let mut input = vec![0, (len >> 16) as u8, (len >> 8) as u8, len as u8];
540 input.extend(bytes);
541 let encoded = game_with_info.parse(input.as_slice()).unwrap().1;
542 let actual = crate::Game::from_chessbase(encoded).unwrap();
543 let expected =
544 crate::Game::from_pgn(pgn::pgn.parse("1. e4 (1. d4 d5) 1... e5 *").unwrap()).unwrap();
545
546 assert_eq!(pgn::Pgn::from(actual).movetext(), pgn::Pgn::from(expected).movetext(),);
547 }
548
549 #[test]
550 #[ignore = "exhaustive Freestyle scan of the external TWIC fixture"]
551 fn twic_freestyle() {
552 let files = unpack(&twic::data(1632, "cbv"));
553 let mut input = files["twic1632.cbg"].as_slice();
554 header.parse_next(&mut input).expect("can parse CBG header");
555 let pgns = pgn::stream::pgns(Cursor::new(twic::data(1632, "pgn")));
556
557 let mut games = 0;
558 let mut custom = 0;
559 let mut freestyle = 0;
560 for (i, pgn) in pgns.enumerate() {
562 let (info, encoded) = game_with_info.parse_next(&mut input).unwrap_or_else(|error| {
563 panic!("can parse CBG game {games} with {} bytes remaining: {error}", input.len())
564 });
565 let expected = crate::Game::from_pgn(
566 pgn.unwrap_or_else(|error| panic!("PGN game {i}: {error}"))
567 .unwrap_or_else(|error| panic!("PGN game {i}: {error}")),
568 )
569 .unwrap_or_else(|error| panic!("PGN game {i}: {error}"));
570 if info.mode.has_setup() {
571 let position = crate::Position::try_from(encoded.position.clone())
572 .unwrap_or_else(|error| panic!("CBG starting position of game {i}: {error}"));
573 assert_eq!(position, expected.start().position(), "starting position of game {i}");
574 }
581 games += 1;
582 custom += usize::from(info.mode.has_setup());
583 freestyle += usize::from(info.mode.is_freestyle());
584 }
585
586 assert!(input.is_empty());
587 assert_eq!(games, 5_350);
588 assert_eq!(custom, 61);
589 assert_eq!(freestyle, 61);
590 }
591
592 }