Skip to main content

chess/formats/chessbase/
player.rs

1//! Chessbase player file format
2
3use crate::formats::{ByteInput as Input, prelude::*};
4
5use super::{
6    cstr,
7    table::{Header, header},
8};
9
10/// ChessBase player filename suffix.
11pub const SUFFIX: &str = "cbp";
12
13// numbers where -1 mean missing or unknown
14// fn optional1<T: Default + PartialEq>(x: T) -> Option<T> {
15//     (x != T::default()).then_some(x)
16// }
17fn optional1(x: i32) -> Option<Option<usize>> {
18    match x {
19        -1 => Some(None),
20        0.. => Some(Some(x as usize)),
21        _ => None,
22    }
23}
24
25#[derive(Clone, Debug)]
26pub struct Player {
27    pub deleted: bool,
28    pub first_name: String,
29    pub last_name: String,
30    pub games: usize,
31    pub first_game: usize,
32}
33
34pub fn player(input: &mut Input<'_>) -> ModalResult<Player> {
35    let (left_child, deleted) =
36        le_i32
37            .verify_map(|x| {
38                if x == -999 { Some((None, true)) } else { optional1(x).map(|x| (x, false)) }
39            })
40            .parse_next(input)?;
41    let (right_child, next_deleted) = le_i32
42        .verify_map(|x| {
43            if deleted { optional1(x).map(|x| (None, x)) } else { optional1(x).map(|x| (x, None)) }
44        })
45        .parse_next(input)?;
46    let _right_height_minus_left_height =
47        i8.verify(|height| !deleted || *height == 0).parse_next(input)?;
48    let last_name = take(30u8).map(cstr).parse_next(input)?;
49    let first_name = take(20u8).map(cstr).parse_next(input)?;
50    let games = le_u32.parse_next(input)? as usize;
51    let first_game = le_u32.parse_next(input)? as usize;
52
53    let _ = (left_child, right_child, next_deleted);
54
55    Ok(Player { deleted, first_name, last_name, games, first_game })
56}
57
58#[derive(Clone, Debug)]
59pub struct Players {
60    pub header: Header,
61    pub players: Vec<Player>,
62}
63
64pub fn players(input: &mut Input<'_>) -> ModalResult<Players> {
65    let header = header.parse_next(input)?;
66    let players: Vec<Player> = repeat(..=header.len, player).parse_next(input)?;
67    Ok(Players { header, players })
68}
69
70#[cfg(test)]
71mod tests {
72    use hex_literal::hex;
73
74    use crate::formats::twic;
75
76    use super::*;
77
78    #[test]
79    #[ignore = "exploratory output from the external TWIC fixture"]
80    fn twic_players() {
81        let input = twic::data(1616, "cbp");
82        let header = header.parse_next(&mut input.as_slice()).expect("can parse CBP header");
83        println!("{header:?}");
84        let players = players.parse(input.as_slice()).map_err(drop).expect("can parse CBP file");
85        println!("{:?}", players.header);
86        for player in players.players.iter().take(3) {
87            println!("{player:?}");
88        }
89        for player in players.players.iter().rev().take(3) {
90            println!("{player:?}");
91        }
92    }
93
94    #[test]
95    fn player() {
96        const INPUT: [u8; 67] = hex!(
97            "ffffffffffffffff0041616761617264
98             00000000360038002500000000000080
99             000000009e86004a00004d003a007000
100             6d0000000000000000000005000000f0
101             0b0000"
102        );
103
104        let player = super::player.parse(INPUT.as_slice()).expect("can parse CBP player");
105        assert!(!player.deleted);
106        assert_eq!(player.first_name, "J");
107        assert_eq!(player.last_name, "Aagaard");
108        assert_eq!(player.games, 5);
109        assert_eq!(player.first_game, 3056);
110    }
111}