Skip to main content

chess/formats/
cbp.rs

1//! Chessbase player file format
2
3use super::{ByteInput as Input, cbv::cstr, prelude::*};
4
5#[derive(Clone, Copy, Debug)]
6pub struct Header {
7    pub len: usize,
8    pub avl_root: u32,
9    pub first_deleted: Option<usize>,
10    pub record_len: usize,
11    pub len_existing: usize,
12}
13
14pub fn header(input: &mut Input<'_>) -> ModalResult<Header> {
15    let len = le_u32.parse_next(input)? as usize;
16    let avl_root = le_u32.parse_next(input)?;
17    let one_to_zero = le_u32.parse_next(input)?;
18    assert_eq!(one_to_zero, 1234567890);
19    let record_len = le_u32.parse_next(input)? as usize;
20    // Not true for CBT which reuses the header
21    // println!("size: {size}");
22    // assert_eq!(67, size + 9);
23    let first_deleted = le_i32.map(|x| (x != -1).then_some(x as usize)).parse_next(input)?;
24    let len_existing = le_u32.parse_next(input)? as usize;
25    // assert!(len_existing >= len);
26    // padding? is 4 in an example
27    let more_offset = le_u32.parse_next(input)?;
28    assert!(more_offset == 0 || more_offset == 4);
29    // println!("more offset: {more_offset}");
30    // compared to talkchess.com post, there's another four bytes
31    let padding = take(more_offset).parse_next(input)?;
32    assert!(padding.iter().all(|x| *x == 0));
33
34    Ok(Header { len, avl_root, record_len, first_deleted, len_existing })
35}
36
37// numbers where -1 mean missing or unknown
38// fn optional1<T: Default + PartialEq>(x: T) -> Option<T> {
39//     (x != T::default()).then_some(x)
40// }
41fn optional1(x: i32) -> Option<usize> {
42    assert!(x >= -1);
43    if x == -1 { None } else { Some(x as usize) }
44}
45
46#[derive(Clone, Debug)]
47pub struct Record {
48    pub deleted: bool,
49    pub first_name: String,
50    pub last_name: String,
51    pub games: usize,
52    pub first_game: usize,
53}
54
55pub fn record(input: &mut Input<'_>) -> ModalResult<Record> {
56    let (left_child, deleted) = le_i32
57        .map(|x| if x == -999 { (None, true) } else { (optional1(x), false) })
58        .parse_next(input)?;
59    let (right_child, next_deleted) = le_i32
60        .map(|x| if deleted { (None, optional1(x)) } else { (optional1(x), None) })
61        .parse_next(input)?;
62    let right_height_minus_left_height = i8.parse_next(input)?;
63    if deleted {
64        assert_eq!(right_height_minus_left_height, 0)
65    };
66    let last_name = take(30u8).map(cstr).parse_next(input)?;
67    let first_name = take(20u8).map(cstr).parse_next(input)?;
68    let games = le_u32.parse_next(input)? as usize;
69    let first_game = le_u32.parse_next(input)? as usize;
70
71    let _ = (left_child, right_child, next_deleted);
72
73    Ok(Record { deleted, first_name, last_name, games, first_game })
74}
75
76#[derive(Clone, Debug)]
77pub struct Players {
78    pub header: Header,
79    pub records: Vec<Record>,
80}
81
82pub fn players(input: &mut Input<'_>) -> ModalResult<Players> {
83    let header = header.parse_next(input)?;
84    let records: Vec<Record> = repeat(..=header.len, record).parse_next(input)?;
85    Ok(Players { header, records })
86}
87
88#[test]
89fn example() {
90    let input = include_bytes!("../../examples/twic1616.cbp");
91    let header = header.parse_next(&mut input.as_slice()).expect("can parse CBP header");
92    println!("{header:?}");
93    let players = players.parse(input.as_slice()).map_err(drop).expect("can parse CBP file");
94    println!("{:?}", players.header);
95    for player in players.records.iter().take(3) {
96        println!("{player:?}");
97    }
98    for player in players.records.iter().rev().take(3) {
99        println!("{player:?}");
100    }
101}