1use enumflags2::{BitFlags, bitflags};
11
12use super::{Bits, ByteInput as Input, prelude::*};
13
14pub(crate) fn optional<T: Default + PartialEq>(x: T) -> Option<T> {
16 (x != T::default()).then_some(x)
17}
18
19fn u32_as_usize(x: u32) -> usize {
20 x as usize
21}
22
23#[derive(Clone, Copy, Debug)]
24pub enum Creator {
25 Chessbase9_10_11,
26 ChessbaseLight,
27 ChessbaseX,
29 Unknown,
30}
31
32#[derive(Clone, Copy, Debug)]
33pub struct Header {
34 pub creator: Creator,
35 pub len: usize,
36}
37
38pub struct Headers {
39 pub header: Header,
40 pub records: Vec<Record>,
41}
42
43pub fn headers(input: &mut Input<'_>) -> ModalResult<Headers> {
44 let header = header.parse_next(input)?;
45 let records: Vec<Record> = repeat(..=header.len, record).parse_next(input)?;
46 assert_eq!(records.len(), header.len);
47 Ok(Headers { header, records })
48}
49
50#[test]
51fn example() {
52 let input = include_bytes!("../../examples/twic1616.cbh");
53 header.parse_next(&mut input.as_slice()).expect("can pass CBH header");
54
55 let cbh = headers.parse(input).expect("can parse CBH file");
56 println!("{:?}", cbh.header);
57 for game in cbh.records.iter().take(3) {
58 println!("{game:?}");
59 println!("{}", game.date.pgn());
60 }
61 for game in cbh.records.iter().rev().take(3) {
62 println!("{game:?}");
63 println!("{}", game.date.pgn());
64 }
65}
66
67fn creator(input: &mut Input<'_>) -> ModalResult<Creator> {
68 use Creator::*;
69 const CHESSBASE_9_10_11: &[u8] = &[0x00, 0x00, 0x2C, 0x00, 0x2E, 0x01];
70 const CHESSBASE_LIGHT: &[u8] = &[0x00, 0x00, 0x24, 0x00, 0x2E, 0x01];
71 const CHESSBASE_X: &[u8] = &[0x00, 0x00, 0x2c, 0x00, 0x2E, 0x05];
73 alt((
74 CHESSBASE_9_10_11.value(Chessbase9_10_11),
75 CHESSBASE_LIGHT.value(ChessbaseLight),
76 CHESSBASE_X.value(ChessbaseX),
77 take(6u8)
78 .map(|bytes| {
79 bytes
81 })
82 .value(Unknown),
83 ))
84 .parse_next(input)
85}
86
87pub fn header(input: &mut Input<'_>) -> ModalResult<Header> {
88 assert!(input.len() >= 46);
89 let creator = creator.parse_next(input).unwrap();
90 let len = be_u32.map(|len_plus_one: u32| len_plus_one as usize - 1).parse_next(input)?; take(36u8).parse_next(input)?;
92 Ok(Header { creator, len })
96}
97
98#[derive(Clone, Copy, Debug)]
101pub struct Meta {
102 pub game: bool,
103 pub guiding_text: bool,
104 pub deleted: bool,
105}
106
107#[derive(Clone, Copy, Debug)]
108pub struct Date {
109 pub year: Option<u16>,
110 pub month: Option<u8>,
111 pub day: Option<u16>,
112}
113
114impl Date {
115 pub fn pgn(self) -> String {
116 let year = if let Some(year) = self.year { year.to_string() } else { "????".to_string() };
117 let month =
118 if let Some(month) = self.month { format!("{month:02}") } else { "??".to_string() };
119 let day = if let Some(day) = self.day { format!("{day:02}") } else { "??".to_string() };
120 format!("{year}.{month}.{day}")
121 }
122}
123
124pub fn be_date(input: &mut Input<'_>) -> ModalResult<Date> {
131 let date = be_u24.parse_next(input)?;
132 let year = (date >> 9) & ((1 << 12) - 1);
133 let month = (date >> 5) & 0b1111;
134 let day = date & 0b11111;
135 Ok(Date {
136 year: optional(year as u16),
137 month: optional(month as u8),
138 day: optional(day as u16),
139 })
140}
141
142pub fn le_date(input: &mut Input<'_>) -> ModalResult<Date> {
143 let date = le_u24.parse_next(input)?;
144 let year = (date >> 9) & ((1 << 12) - 1);
145 let month = (date >> 5) & 0b1111;
146 let day = date & 0b11111;
147 Ok(Date {
148 year: optional(year as u16),
149 month: optional(month as u8),
150 day: optional(day as u16),
151 })
152}
153
154#[repr(u8)]
163#[derive(Clone, Copy, Debug)]
164pub enum Outcome {
165 BlackWins = 0,
167 Draw = 1,
169 WhiteWins = 2,
171 WhiteBye = 4,
173 BothBye = 5,
175 BlackBye = 6,
177 Lost = 7,
179
180 Unknown,
181}
182
183impl From<u8> for Outcome {
192 fn from(outcome: u8) -> Self {
193 use Outcome::*;
194 match outcome {
195 0 => BlackWins,
196 1 => Draw,
197 2 => WhiteWins,
198 4 => WhiteBye,
199 5 => BothBye,
200 6 => BlackBye,
201 7 => Lost,
202 _ => Unknown,
203 }
204 }
205}
206
207#[bitflags]
208#[repr(u16)]
209#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
210pub enum Flag {
211 BestGame = 1 << 0,
212 DecidedTournament = 1 << 1,
213 ModelGame = 1 << 2,
214 Novelty = 1 << 3,
215 PawnStructure = 1 << 4,
216 Strategy = 1 << 5,
217 Tactics = 1 << 6,
218 WithAttack = 1 << 7,
219 Sacrifice = 1 << 8,
220 Defense = 1 << 9,
221 Material = 1 << 10,
222 PiecePlay = 1 << 11,
223 EndGame = 1 << 12,
224 TacticalBlunder = 1 << 13,
225 StrategicalBlunder = 1 << 14,
226 User = 1 << 15,
227}
228
229pub type Flags = BitFlags<Flag>;
230
231pub mod eco {
257 use super::*;
258
259 #[derive(Clone, Copy, Debug)]
261 pub enum Volume {
262 A,
263 B,
264 C,
265 D,
266 E,
267 }
268
269 #[derive(Clone, Copy, Debug)]
271 pub struct Number(pub u8);
272
273 #[derive(Clone, Copy, Debug)]
274 pub struct Eco {
275 pub volume: Volume,
276 pub number: Number,
277 pub sub: Option<Number>,
278 }
279
280 pub fn eco(input: &mut Input<'_>) -> ModalResult<Option<Eco>> {
281 use winnow::error::{ContextError, ErrMode};
282
283 let err = || ErrMode::Backtrack(ContextError::new());
284
285 let mut bits = Bits::new(input);
286 let code = bits.unsigned(9).ok_or_else(err)?;
287 if code > 500 {
288 return Err(err());
289 };
290 take(2u8).parse_next(input)?;
292
293 Ok(if code == 0 {
295 None
296 } else {
297 use Volume::*;
298 let volume = match (code - 1) / 100 {
299 0 => A,
300 1 => B,
301 2 => C,
302 3 => D,
303 4 => E,
304 _ => unreachable!(),
305 };
306 let number = Number(((code - 1) % 100) as u8);
307 let sub = bits.unsigned(7).ok_or_else(err)?;
309 assert!(sub < 100);
310 Some(Eco { volume, number, sub: Some(Number(sub as u8)) })
313 })
314 }
315}
316
317pub use eco::{Eco, eco};
318
319#[derive(Clone, Copy, Debug)]
321pub struct Record {
322 pub meta: Meta,
323 pub game_offset: usize, pub annotation_offset: Option<usize>, pub white: usize, pub black: usize, pub tournament: usize, pub annotator: Option<usize>,
329 pub source: usize,
330 pub date: Date,
331 pub outcome: Outcome,
332 pub round: Option<u8>,
333 pub subround: Option<u8>,
334 pub white_elo: Option<u16>,
335 pub black_elo: Option<u16>,
336 pub medals: Flags,
337 pub eco: Option<Eco>,
338}
339
340pub fn record(input: &mut Input<'_>) -> ModalResult<Record> {
341 let meta = u8.parse_next(input)?;
342 let meta = Meta {
343 game: meta & (1 << 0) != 0,
344 guiding_text: meta & (1 << 1) != 0,
345 deleted: meta & (1 << 7) != 0,
346 };
347 assert!(meta.game);
349 assert!(!meta.guiding_text);
350
351 let game_offset = be_u32.parse_next(input)? as usize;
352 let annotation_offset = be_u32.map(u32_as_usize).map(optional).parse_next(input)?;
353 let white = be_u24.parse_next(input)? as usize;
354 let black = be_u24.parse_next(input)? as usize;
355 let tournament = be_u24.parse_next(input)? as usize;
356 let annotator = be_u24.map(u32_as_usize).map(optional).parse_next(input)?;
357 let source = be_u24.parse_next(input)? as usize;
358 let date = be_date.parse_next(input)?;
359 let outcome = u8.map(Outcome::from).parse_next(input)?;
360 take(1u8).parse_next(input)?;
362
363 let round = u8.map(optional).parse_next(input)?;
364 let subround = u8.map(optional).parse_next(input)?;
365 let white_elo = be_u16.map(optional).parse_next(input)?;
366 let black_elo = be_u16.map(optional).parse_next(input)?;
367
368 let eco = eco.parse_next(input)?;
369
370 let medals = be_u16
371 .map(|flags| Flags::from_bits(flags).expect("all bits are legal"))
372 .parse_next(input)?;
373
374 take(7u8).parse_next(input)?;
378
379 Ok(Record {
380 meta,
381 game_offset,
382 annotation_offset,
383 white,
384 black,
385 tournament,
386 annotator,
387 source,
388 date,
389 outcome,
390 round,
391 subround,
392 white_elo,
393 black_elo,
394 eco,
395 medals,
396 })
397}
398
399#[test]
400fn cbh() {
401 let example = include_bytes!("../../examples/twic1616.cbh");
402 let input = &mut example.as_slice();
403 let header = header.parse_next(input).unwrap();
404 println!("{header:?}");
405 for _ in 0..3 {
406 let record = record.parse_next(input).unwrap();
407 println!("{record:?}");
408 }
409 }