Skip to main content

chess/formats/chessbase/
tournament.rs

1//! Chessbase tournament file format (CBT)
2
3use crate::formats::{ByteInput as Input, prelude::*};
4
5use super::{
6    cstr,
7    index::{Date, le_date},
8    optional,
9    table::{Header, header},
10};
11
12/// ChessBase tournament filename suffix.
13pub const SUFFIX: &str = "cbt";
14
15#[derive(Clone, Debug)]
16pub struct Tournament {
17    pub title: String,
18    pub place: String,
19    pub date: Date,
20    pub mode: Option<Mode>,
21    pub speed: Option<Speed>,
22    pub nation: Option<u8>,
23    pub category: Option<u8>,
24    pub rounds: Option<u8>,
25    pub games: usize,
26    pub first_game: usize,
27}
28
29pub fn tournament(input: &mut Input<'_>) -> ModalResult<Tournament> {
30    let _what = be_u16.parse_next(input)?;
31    // 0x19FC = deleted? 0xFFFF = exist?
32    // In the TWIC file, we see all these values:
33    // 0, 100, ffff, 200, 300, d00, ...
34    // println!("{_what:x}");
35    // assert!(what == 0x19FC || what == 0xFFFF);
36    // assert!(what == 0x19FC || what == 0xFFFF);
37    let _ = take(7u8).parse_next(input)?;
38
39    let title = take(40u8).map(cstr).parse_next(input)?;
40    let place = take(30u8).map(cstr).parse_next(input)?;
41
42    let date = le_date.parse_next(input)?;
43
44    let _byte82 = u8.parse_next(input)?;
45    let (mode, speed) = u8.verify_map(byte83).parse_next(input)?;
46
47    let _byte84 = u8.parse_next(input)?;
48    let nation = u8.map(optional).parse_next(input)?;
49
50    let _byte86 = u8.parse_next(input)?;
51    let category = u8.verify(|x| *x < 100).map(optional).parse_next(input)?;
52
53    let _byte88 = u8.parse_next(input)?;
54    let rounds = u8.map(optional).parse_next(input)?;
55    let _byte90 = u8.parse_next(input)?;
56    let games = le_u32.parse_next(input)? as usize;
57    let first_game = le_u32.parse_next(input)? as usize;
58
59    Ok(Tournament {
60        title,
61        place,
62        date,
63        mode,
64        speed: Some(speed),
65        nation,
66        category,
67        rounds,
68        games,
69        first_game,
70    })
71}
72
73#[repr(u8)]
74#[derive(Clone, Copy, Debug)]
75pub enum Mode {
76    Game = 1,
77    Match = 2,
78    Tournament = 3,
79    Swiss = 4,
80    Team = 5,
81    Knockout = 6,
82    Simultaneous = 7,
83    Scheveningen = 8,
84}
85
86#[repr(u8)]
87#[derive(Clone, Copy, Debug)]
88pub enum Speed {
89    Normal,
90    Blitz,
91    Rapid,
92    Correspondence,
93}
94
95fn byte83(byte: u8) -> Option<(Option<Mode>, Speed)> {
96    let mode = byte & 0b11111;
97    let mode = if mode == 0 {
98        None
99    } else {
100        use Mode::*;
101        Some(match mode {
102            1 => Game,
103            2 => Match,
104            3 => Tournament,
105            4 => Swiss,
106            5 => Team,
107            6 => Knockout,
108            7 => Simultaneous,
109            8 => Scheveningen,
110            _ => return None,
111        })
112    };
113    let blitz = (byte >> 5) & 1 != 0;
114    let rapid = (byte >> 6) & 1 != 0;
115    let correspondence = (byte >> 7) & 1 != 0;
116    if blitz as u8 + rapid as u8 + correspondence as u8 > 1 {
117        return None;
118    }
119    let mut speed = Speed::Normal;
120    if blitz {
121        speed = Speed::Blitz
122    };
123    if rapid {
124        speed = Speed::Rapid
125    };
126    if correspondence {
127        speed = Speed::Correspondence
128    };
129
130    Some((mode, speed))
131}
132
133#[derive(Clone, Debug)]
134pub struct Tournaments {
135    pub header: Header,
136    pub tournaments: Vec<Tournament>,
137}
138
139pub fn tournaments(input: &mut Input<'_>) -> ModalResult<Tournaments> {
140    let header = header.verify(|header| header.record_len == 90).parse_next(input)?;
141    let tournaments: Vec<Tournament> = repeat(..=header.len, tournament).parse_next(input)?;
142    Ok(Tournaments { header, tournaments })
143}
144
145#[cfg(test)]
146mod tests {
147    use hex_literal::hex;
148
149    use crate::formats::twic;
150
151    use super::*;
152
153    #[test]
154    #[ignore = "exploratory output from the external TWIC fixture"]
155    fn twic_tournaments() {
156        let input = twic::data(1616, "cbt");
157        let header = header.parse_next(&mut input.as_slice()).expect("can parse CBT header");
158        println!("{header:?}");
159        let tournaments =
160            tournaments.parse(input.as_slice()).map_err(drop).expect("can parse CBT file");
161        println!("{:?}", tournaments.header);
162        for tournament in tournaments.tournaments.iter().take(5) {
163            println!("{tournament:?}");
164        }
165        for tournament in tournaments.tournaments.iter().rev().take(5) {
166            println!("{tournament:?}");
167        }
168    }
169
170    #[test]
171    fn tournament() {
172        const INPUT: [u8; 99] = hex!(
173            "ffffffffffffffff0031327468204265
174             69727574204f70656e20323032350068
175             0000009f0dde010000e8f9e176de0100
176             00426569727574204c424e0053500072
177             74204155540001000080d8c0b9f87f52
178             d30f000400520000000900f8000000d6
179             070000"
180        );
181
182        let tournament =
183            super::tournament.parse(INPUT.as_slice()).expect("can parse CBT tournament");
184        assert_eq!(tournament.title, "12th Beirut Open 2025");
185        assert_eq!(tournament.place, "Beirut LBN");
186        assert_eq!(tournament.date, Date { year: Some(2025), month: Some(10), day: Some(18) });
187        assert!(matches!(tournament.mode, Some(Mode::Swiss)));
188        assert!(matches!(tournament.speed, Some(Speed::Normal)));
189        assert_eq!(tournament.nation, Some(82));
190        assert_eq!(tournament.category, None);
191        assert_eq!(tournament.rounds, Some(9));
192        assert_eq!(tournament.games, 248);
193        assert_eq!(tournament.first_game, 2006);
194    }
195}