Skip to main content

chess/formats/
cbt.rs

1//! Chessbase tournament file format (CBT)
2
3use super::{ByteInput as Input, cbv::cstr, prelude::*};
4
5use super::cbh::optional;
6pub use super::{
7    cbh::{Date, le_date},
8    cbp::{Header, header},
9};
10
11#[derive(Clone, Debug)]
12pub struct Record {
13    pub title: String,
14    pub place: String,
15    pub date: Date,
16    pub mode: Option<Mode>,
17    pub speed: Option<Speed>,
18    pub nation: Option<u8>,
19    pub category: Option<u8>,
20    pub rounds: Option<u8>,
21    pub games: usize,
22    pub first_game: usize,
23}
24
25pub fn record(input: &mut Input<'_>) -> ModalResult<Record> {
26    let _what = be_u16.parse_next(input)?;
27    // 0x19FC = deleted? 0xFFFF = exist?
28    // In the TWIC file, we see all these values:
29    // 0, 100, ffff, 200, 300, d00, ...
30    // println!("{_what:x}");
31    // assert!(what == 0x19FC || what == 0xFFFF);
32    // assert!(what == 0x19FC || what == 0xFFFF);
33    let _ = take(7u8).parse_next(input)?;
34
35    let title = take(40u8).map(cstr).parse_next(input)?;
36    let place = take(30u8).map(cstr).parse_next(input)?;
37
38    let date = le_date.parse_next(input)?;
39
40    let _byte82 = u8.parse_next(input)?;
41    let (mode, speed) = u8.map(byte83).parse_next(input)?;
42
43    let _byte84 = u8.parse_next(input)?;
44    let nation = u8.map(optional).parse_next(input)?;
45
46    let _byte86 = u8.parse_next(input)?;
47    let category = u8.verify(|x| *x < 100).map(optional).parse_next(input)?;
48
49    let _byte88 = u8.parse_next(input)?;
50    let rounds = u8.map(optional).parse_next(input)?;
51    let _byte90 = u8.parse_next(input)?;
52    let games = le_u32.parse_next(input)? as usize;
53    let first_game = le_u32.parse_next(input)? as usize;
54
55    Ok(Record {
56        title,
57        place,
58        date,
59        mode,
60        speed: Some(speed),
61        nation,
62        category,
63        rounds,
64        games,
65        first_game,
66    })
67}
68
69#[repr(u8)]
70#[derive(Clone, Copy, Debug)]
71pub enum Mode {
72    Game = 1,
73    Match = 2,
74    Tournament = 3,
75    Swiss = 4,
76    Team = 5,
77    Knockout = 6,
78    Simultaneous = 7,
79    Scheveningen = 8,
80}
81
82#[repr(u8)]
83#[derive(Clone, Copy, Debug)]
84pub enum Speed {
85    Normal,
86    Blitz,
87    Rapid,
88    Correspondence,
89}
90
91fn byte83(byte: u8) -> (Option<Mode>, Speed) {
92    let mode = byte & 0b11111;
93    let mode = if mode == 0 {
94        None
95    } else {
96        use Mode::*;
97        Some(match mode {
98            1 => Game,
99            2 => Match,
100            3 => Tournament,
101            4 => Swiss,
102            5 => Team,
103            6 => Knockout,
104            7 => Simultaneous,
105            8 => Scheveningen,
106            _ => unreachable!(),
107        })
108    };
109    let blitz = (byte >> 5) & 1 != 0;
110    let rapid = (byte >> 6) & 1 != 0;
111    let correspondence = (byte >> 7) & 1 != 0;
112    assert!(blitz as u8 + rapid as u8 + correspondence as u8 <= 1);
113    let mut speed = Speed::Normal;
114    if blitz {
115        speed = Speed::Blitz
116    };
117    if rapid {
118        speed = Speed::Rapid
119    };
120    if correspondence {
121        speed = Speed::Correspondence
122    };
123
124    (mode, speed)
125}
126
127#[derive(Clone, Debug)]
128pub struct Tournaments {
129    pub header: Header,
130    pub records: Vec<Record>,
131}
132
133pub fn tournaments(input: &mut Input<'_>) -> ModalResult<Tournaments> {
134    let header = header.parse_next(input)?;
135    assert_eq!(header.record_len + 9, 99);
136    let records: Vec<Record> = repeat(..=header.len, record).parse_next(input)?;
137    Ok(Tournaments { header, records })
138}
139
140#[test]
141fn example() {
142    let input = include_bytes!("../../examples/twic1616.cbt");
143    let header = header.parse_next(&mut input.as_slice()).expect("can parse CBT header");
144    println!("{header:?}");
145    let tournaments =
146        tournaments.parse(input.as_slice()).map_err(drop).expect("can parse CBT file");
147    println!("{:?}", tournaments.header);
148    for record in tournaments.records.iter().take(5) {
149        println!("{record:?}");
150    }
151    for record in tournaments.records.iter().rev().take(5) {
152        println!("{record:?}");
153    }
154}