chess/formats/chessbase/
tournament.rs1use crate::formats::{ByteInput as Input, prelude::*};
4
5use super::{
6 cstr,
7 index::{Date, le_date},
8 optional,
9 table::{Header, header},
10};
11
12pub 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 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}