Skip to main content

rooky_core/
pgn_standards.rs

1#[derive(Debug, PartialEq, Clone, Eq, Default)]
2pub enum PgnEvent {
3    #[default]
4    Casual,
5    Unknown,
6    Named(String),
7}
8impl std::str::FromStr for PgnEvent {
9    type Err = crate::errors::ChessError;
10
11    fn from_str(s: &str) -> Result<Self, Self::Err> {
12        if s.is_empty() || s == "?" {
13            return Ok(Self::Unknown);
14        }
15        if s.contains("Casual") {
16            return Ok(Self::Casual);
17        }
18        Ok(Self::Named(s.to_string()))
19    }
20}
21impl std::fmt::Display for PgnEvent {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            Self::Casual => write!(f, "Casual Game"),
25            Self::Unknown => write!(f, "?"),
26            Self::Named(name) => write!(f, "{name}"),
27        }
28    }
29}
30
31#[derive(Debug, PartialEq, Clone, Eq, Default)]
32pub enum PgnSite {
33    #[default]
34    Unknown,
35    Named(String),
36}
37impl std::str::FromStr for PgnSite {
38    type Err = crate::errors::ChessError;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        if s.is_empty() || s == "?" {
42            return Ok(Self::Unknown);
43        }
44        Ok(Self::Named(s.to_string()))
45    }
46}
47impl std::fmt::Display for PgnSite {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::Unknown => write!(f, "?"),
51            Self::Named(name) => write!(f, "{name}"),
52        }
53    }
54}
55
56#[derive(Debug, PartialEq, Clone, Eq, Default)]
57pub enum PgnRound {
58    #[default]
59    Unknown,
60    Named(String),
61}
62impl std::str::FromStr for PgnRound {
63    type Err = crate::errors::ChessError;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        if s.is_empty() || s == "-" {
67            return Ok(Self::Unknown);
68        }
69        Ok(Self::Named(s.to_string()))
70    }
71}
72impl std::fmt::Display for PgnRound {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            Self::Unknown => write!(f, "-"),
76            Self::Named(name) => write!(f, "{name}"),
77        }
78    }
79}
80
81#[derive(Debug, PartialEq, Clone, Eq, Default)]
82pub enum PgnResult {
83    #[default]
84    Unknown,
85    Outcome(shakmaty::Outcome),
86}
87impl std::str::FromStr for PgnResult {
88    type Err = crate::errors::ChessError;
89
90    fn from_str(s: &str) -> Result<Self, Self::Err> {
91        match s {
92            "1-0" => Ok(Self::Outcome(shakmaty::Outcome::Decisive {
93                winner: shakmaty::Color::White,
94            })),
95            "0-1" => Ok(Self::Outcome(shakmaty::Outcome::Decisive {
96                winner: shakmaty::Color::Black,
97            })),
98            "1/2-1/2" => Ok(Self::Outcome(shakmaty::Outcome::Draw)),
99            "*" => Ok(Self::Unknown),
100            _ => Err(crate::errors::ChessError::InvalidPgn(std::io::Error::new(
101                std::io::ErrorKind::InvalidData,
102                "Invalid result",
103            ))),
104        }
105    }
106}
107impl std::fmt::Display for PgnResult {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            Self::Unknown => write!(f, "*"),
111            Self::Outcome(outcome) => match outcome {
112                shakmaty::Outcome::Decisive { winner } => {
113                    if winner == &shakmaty::Color::White {
114                        write!(f, "1-0")
115                    } else {
116                        write!(f, "0-1")
117                    }
118                }
119                shakmaty::Outcome::Draw => write!(f, "1/2-1/2"),
120            },
121        }
122    }
123}
124
125#[derive(Debug, PartialEq, Clone, Eq, Default)]
126pub enum PgnTermination {
127    Abandoned,
128    Adjudication,
129    Death,
130    Emergency,
131    #[default]
132    Normal,
133    RulesInfraction,
134    TimeForfeit,
135    Unterminated,
136}
137impl std::str::FromStr for PgnTermination {
138    type Err = crate::errors::ChessError;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        match s {
142            "Abandoned" => Ok(Self::Abandoned),
143            "Adjudication" => Ok(Self::Adjudication),
144            "Death" => Ok(Self::Death),
145            "Emergency" => Ok(Self::Emergency),
146            "Normal" => Ok(Self::Normal),
147            "Rules infraction" => Ok(Self::RulesInfraction),
148            "Time forfeit" => Ok(Self::TimeForfeit),
149            "Unterminated" => Ok(Self::Unterminated),
150            _ => Err(crate::errors::ChessError::InvalidPgn(std::io::Error::new(
151                std::io::ErrorKind::InvalidData,
152                "Invalid termination reason",
153            ))),
154        }
155    }
156}
157impl std::fmt::Display for PgnTermination {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            Self::Abandoned => write!(f, "Abandoned"),
161            Self::Adjudication => write!(f, "Adjudication"),
162            Self::Death => write!(f, "Death"),
163            Self::Emergency => write!(f, "Emergency"),
164            Self::Normal => write!(f, "Normal"),
165            Self::RulesInfraction => write!(f, "Rules infraction"),
166            Self::TimeForfeit => write!(f, "Time forfeit"),
167            Self::Unterminated => write!(f, "Unterminated"),
168        }
169    }
170}
171
172#[derive(Debug, PartialEq, Clone, Eq)]
173pub enum TimeControl {
174    Unknown,
175    NoTimeControl,
176    MovesInTime { moves: u32, seconds: u32 },
177    SuddenDeath { seconds: u32 },
178    Incremental { base: u32, increment: u32 },
179    Sandclock { seconds: u32 },
180    Multiple(Vec<TimeControl>),
181}
182#[derive(Debug, PartialEq, Eq)]
183pub enum TimeControlParseError {
184    EmptyString,
185    InvalidFormat(String),
186    InvalidNumber(String),
187    UnexpectedField(String),
188}
189impl TryFrom<&[u8]> for TimeControl {
190    type Error = TimeControlParseError;
191
192    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
193        let s = std::str::from_utf8(value)
194            .map_err(|_| TimeControlParseError::InvalidFormat("Invalid UTF-8".to_string()))?;
195        s.parse()
196    }
197}
198impl std::str::FromStr for TimeControl {
199    type Err = TimeControlParseError;
200
201    fn from_str(s: &str) -> Result<Self, Self::Err> {
202        if s.is_empty() {
203            return Err(TimeControlParseError::EmptyString);
204        }
205
206        // Handle single-character cases first
207        match s {
208            "?" => return Ok(Self::Unknown),
209            "-" => return Ok(Self::NoTimeControl),
210            _ => (),
211        }
212
213        // Check for multiple fields separated by colons
214        if s.contains(':') {
215            let fields: Vec<&str> = s.split(':').collect();
216            let mut controls = Vec::new();
217
218            for field in fields {
219                controls.push(field.parse()?);
220            }
221
222            return Ok(Self::Multiple(controls));
223        }
224
225        // Parse individual time control fields
226        if s.contains('/') {
227            // Moves in time format: "moves/seconds"
228            let parts: Vec<&str> = s.split('/').collect();
229            if parts.len() != 2 {
230                return Err(TimeControlParseError::InvalidFormat(s.to_string()));
231            }
232
233            let moves = parts[0]
234                .parse()
235                .map_err(|_| TimeControlParseError::InvalidNumber(parts[0].to_string()))?;
236            let seconds = parts[1]
237                .parse()
238                .map_err(|_| TimeControlParseError::InvalidNumber(parts[1].to_string()))?;
239
240            return Ok(Self::MovesInTime { moves, seconds });
241        } else if s.contains('+') {
242            // Incremental format: "base+increment"
243            let parts: Vec<&str> = s.split('+').collect();
244            if parts.len() != 2 {
245                return Err(TimeControlParseError::InvalidFormat(s.to_string()));
246            }
247
248            let base = parts[0]
249                .parse()
250                .map_err(|_| TimeControlParseError::InvalidNumber(parts[0].to_string()))?;
251            let increment = parts[1]
252                .parse()
253                .map_err(|_| TimeControlParseError::InvalidNumber(parts[1].to_string()))?;
254
255            return Ok(Self::Incremental { base, increment });
256        } else if let Some(stripped) = s.strip_prefix("*") {
257            // Sandclock format: "*seconds"
258            let seconds = stripped
259                .parse()
260                .map_err(|_| TimeControlParseError::InvalidNumber(stripped.to_string()))?;
261
262            return Ok(Self::Sandclock { seconds });
263        } else
264        // Try to parse as sudden death (simple number)
265        if let Ok(seconds) = s.parse() {
266            return Ok(Self::SuddenDeath { seconds });
267        }
268
269        Err(TimeControlParseError::InvalidFormat(s.to_string()))
270    }
271}
272
273impl std::fmt::Display for TimeControl {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        match self {
276            Self::Unknown => write!(f, "?"),
277            Self::NoTimeControl => write!(f, "-"),
278            Self::MovesInTime { moves, seconds } => write!(f, "{moves}/{seconds}"),
279            Self::SuddenDeath { seconds } => write!(f, "{seconds}"),
280            Self::Incremental { base, increment } => write!(f, "{base}+{increment}"),
281            Self::Sandclock { seconds } => write!(f, "*{seconds}"),
282            Self::Multiple(controls) => {
283                let parts: Vec<String> = controls
284                    .iter()
285                    .map(std::string::ToString::to_string)
286                    .collect();
287                write!(f, "{}", parts.join(":"))
288            }
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_parse_unknown_time() {
299        assert_eq!("?".parse(), Ok(TimeControl::Unknown));
300    }
301
302    #[test]
303    fn test_parse_no_time_control() {
304        assert_eq!("-".parse(), Ok(TimeControl::NoTimeControl));
305    }
306
307    #[test]
308    fn test_parse_moves_in_time() {
309        assert_eq!(
310            "40/9000".parse(),
311            Ok(TimeControl::MovesInTime {
312                moves: 40,
313                seconds: 9000
314            })
315        );
316    }
317
318    #[test]
319    fn test_parse_sudden_death_time() {
320        assert_eq!("300".parse(), Ok(TimeControl::SuddenDeath { seconds: 300 }));
321    }
322
323    #[test]
324    fn test_parse_incremental_time() {
325        assert_eq!(
326            "4500+60".parse(),
327            Ok(TimeControl::Incremental {
328                base: 4500,
329                increment: 60
330            })
331        );
332    }
333
334    #[test]
335    fn test_parse_sandclock_time() {
336        assert_eq!("*180".parse(), Ok(TimeControl::Sandclock { seconds: 180 }));
337    }
338
339    #[test]
340    fn test_parse_multiple_time() {
341        assert_eq!(
342            "40/9000:300".parse(),
343            Ok(TimeControl::Multiple(vec![
344                TimeControl::MovesInTime {
345                    moves: 40,
346                    seconds: 9000
347                },
348                TimeControl::SuddenDeath { seconds: 300 }
349            ]))
350        );
351    }
352
353    #[test]
354    fn test_display_time() {
355        assert_eq!(TimeControl::Unknown.to_string(), "?");
356        assert_eq!(TimeControl::NoTimeControl.to_string(), "-");
357        assert_eq!(
358            TimeControl::MovesInTime {
359                moves: 40,
360                seconds: 9000
361            }
362            .to_string(),
363            "40/9000"
364        );
365        assert_eq!(
366            TimeControl::Multiple(vec![
367                TimeControl::MovesInTime {
368                    moves: 40,
369                    seconds: 9000
370                },
371                TimeControl::SuddenDeath { seconds: 300 }
372            ])
373            .to_string(),
374            "40/9000:300"
375        );
376    }
377    #[test]
378    fn test_from_str() {
379        assert_eq!(
380            "USA".parse::<OlympicCountryCode>().unwrap(),
381            OlympicCountryCode::UnitedStatesOfAmerica
382        );
383        assert_eq!(
384            "ENG".parse::<OlympicCountryCode>().unwrap(),
385            OlympicCountryCode::England
386        );
387        assert_eq!(
388            "RUS".parse::<OlympicCountryCode>().unwrap(),
389            OlympicCountryCode::Russia
390        );
391        assert_eq!(
392            "JAP".parse::<OlympicCountryCode>().unwrap(),
393            OlympicCountryCode::Japan
394        );
395    }
396
397    #[test]
398    fn test_display() {
399        assert_eq!(OlympicCountryCode::UnitedStatesOfAmerica.to_string(), "USA");
400        assert_eq!(OlympicCountryCode::England.to_string(), "ENG");
401        assert_eq!(OlympicCountryCode::Russia.to_string(), "RUS");
402        assert_eq!(OlympicCountryCode::Japan.to_string(), "JAP");
403    }
404
405    #[test]
406    fn test_unknown_code() {
407        assert!("XYZ".parse::<OlympicCountryCode>().is_err());
408    }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
412pub enum OlympicCountryCode {
413    Afghanistan,
414    AboardAircraft,
415    Albania,
416    Algeria,
417    Andorra,
418    Angola,
419    Antigua,
420    Argentina,
421    Armenia,
422    Antarctica,
423    Australia,
424    Azerbaijan,
425    Bangladesh,
426    Bahrain,
427    Bahamas,
428    Belgium,
429    Bermuda,
430    BosniaHerzegovina,
431    Belarus,
432    Bulgaria,
433    Belize,
434    Bolivia,
435    Barbados,
436    Brazil,
437    Brunei,
438    Botswana,
439    Canada,
440    Chile,
441    Columbia,
442    CostaRica,
443    Croatia,
444    Czechoslovakia,
445    Cuba,
446    Cyprus,
447    Denmark,
448    DominicanRepublic,
449    Ecuador,
450    Egypt,
451    England,
452    Spain,
453    Estonia,
454    FaroeIslands,
455    Fiji,
456    Finland,
457    France,
458    Gambia,
459    GuernseyJersey,
460    Georgia,
461    Germany,
462    Ghana,
463    Greece,
464    Guatemala,
465    Guyana,
466    Haiti,
467    HongKong,
468    Honduras,
469    Hungary,
470    India,
471    Ireland,
472    Iran,
473    Iraq,
474    Iceland,
475    Israel,
476    Italy,
477    IvoryCoast,
478    Jamaica,
479    Japan,
480    Jordan,
481    Yugoslavia,
482    Kazakhstan,
483    Kenya,
484    Kyrgyzstan,
485    Kuwait,
486    Latvia,
487    Lebanon,
488    Libya,
489    Liechtenstein,
490    Lithuania,
491    Luxembourg,
492    Malaysia,
493    Mauritania,
494    Mexico,
495    Mali,
496    Malta,
497    Monaco,
498    Moldova,
499    Mongolia,
500    Mozambique,
501    Morocco,
502    Mauritius,
503    Myanmar,
504    Nicaragua,
505    TheInternet,
506    Nigeria,
507    NetherlandsAntilles,
508    Netherlands,
509    Norway,
510    NewZealand,
511    Austria,
512    Pakistan,
513    Palestine,
514    Panama,
515    Paraguay,
516    Peru,
517    Philippines,
518    PapuaNewGuinea,
519    Poland,
520    Portugal,
521    PeoplesRepublicOfChina,
522    PuertoRico,
523    Qatar,
524    Indonesia,
525    Romania,
526    Russia,
527    SouthAfrica,
528    ElSalvador,
529    Scotland,
530    AtSea,
531    Senegal,
532    Seychelles,
533    Singapore,
534    Slovenia,
535    SanMarino,
536    AboardSpacecraft,
537    SriLanka,
538    Sudan,
539    Surinam,
540    Sweden,
541    Switzerland,
542    Syria,
543    Thailand,
544    Turkmenistan,
545    Turkey,
546    TrinidadAndTobago,
547    Tunisia,
548    UnitedArabEmirates,
549    Uganda,
550    Ukraine,
551    Unknown,
552    Uruguay,
553    UnitedStatesOfAmerica,
554    Uzbekistan,
555    Venezuela,
556    BritishVirginIslands,
557    Vietnam,
558    USVirginIslands,
559    Wales,
560    Yemen,
561    Zambia,
562    Zimbabwe,
563    Zaire,
564}
565
566impl std::str::FromStr for OlympicCountryCode {
567    type Err = crate::errors::ChessError;
568
569    #[allow(clippy::too_many_lines)]
570    fn from_str(code: &str) -> Result<Self, Self::Err> {
571        match code {
572            "AFG" => Ok(Self::Afghanistan),
573            "AIR" => Ok(Self::AboardAircraft),
574            "ALB" => Ok(Self::Albania),
575            "ALG" => Ok(Self::Algeria),
576            "AND" => Ok(Self::Andorra),
577            "ANG" => Ok(Self::Angola),
578            "ANT" => Ok(Self::Antigua),
579            "ARG" => Ok(Self::Argentina),
580            "ARM" => Ok(Self::Armenia),
581            "ATA" => Ok(Self::Antarctica),
582            "AUS" => Ok(Self::Australia),
583            "AZB" => Ok(Self::Azerbaijan),
584            "BAN" => Ok(Self::Bangladesh),
585            "BAR" => Ok(Self::Bahrain),
586            "BHM" => Ok(Self::Bahamas),
587            "BEL" => Ok(Self::Belgium),
588            "BER" => Ok(Self::Bermuda),
589            "BIH" => Ok(Self::BosniaHerzegovina),
590            "BLA" => Ok(Self::Belarus),
591            "BLG" => Ok(Self::Bulgaria),
592            "BLZ" => Ok(Self::Belize),
593            "BOL" => Ok(Self::Bolivia),
594            "BRB" => Ok(Self::Barbados),
595            "BRS" => Ok(Self::Brazil),
596            "BRU" => Ok(Self::Brunei),
597            "BSW" => Ok(Self::Botswana),
598            "CAN" => Ok(Self::Canada),
599            "CHI" => Ok(Self::Chile),
600            "COL" => Ok(Self::Columbia),
601            "CRA" => Ok(Self::CostaRica),
602            "CRO" => Ok(Self::Croatia),
603            "CSR" => Ok(Self::Czechoslovakia),
604            "CUB" => Ok(Self::Cuba),
605            "CYP" => Ok(Self::Cyprus),
606            "DEN" => Ok(Self::Denmark),
607            "DOM" => Ok(Self::DominicanRepublic),
608            "ECU" => Ok(Self::Ecuador),
609            "EGY" => Ok(Self::Egypt),
610            "ENG" => Ok(Self::England),
611            "ESP" => Ok(Self::Spain),
612            "EST" => Ok(Self::Estonia),
613            "FAI" => Ok(Self::FaroeIslands),
614            "FIJ" => Ok(Self::Fiji),
615            "FIN" => Ok(Self::Finland),
616            "FRA" => Ok(Self::France),
617            "GAM" => Ok(Self::Gambia),
618            "GCI" => Ok(Self::GuernseyJersey),
619            "GEO" => Ok(Self::Georgia),
620            "GER" => Ok(Self::Germany),
621            "GHA" => Ok(Self::Ghana),
622            "GRC" => Ok(Self::Greece),
623            "GUA" => Ok(Self::Guatemala),
624            "GUY" => Ok(Self::Guyana),
625            "HAI" => Ok(Self::Haiti),
626            "HKG" => Ok(Self::HongKong),
627            "HON" => Ok(Self::Honduras),
628            "HUN" => Ok(Self::Hungary),
629            "IND" => Ok(Self::India),
630            "IRL" => Ok(Self::Ireland),
631            "IRN" => Ok(Self::Iran),
632            "IRQ" => Ok(Self::Iraq),
633            "ISD" => Ok(Self::Iceland),
634            "ISR" => Ok(Self::Israel),
635            "ITA" => Ok(Self::Italy),
636            "IVO" => Ok(Self::IvoryCoast),
637            "JAM" => Ok(Self::Jamaica),
638            "JAP" => Ok(Self::Japan),
639            "JRD" => Ok(Self::Jordan),
640            "JUG" => Ok(Self::Yugoslavia),
641            "KAZ" => Ok(Self::Kazakhstan),
642            "KEN" => Ok(Self::Kenya),
643            "KIR" => Ok(Self::Kyrgyzstan),
644            "KUW" => Ok(Self::Kuwait),
645            "LAT" => Ok(Self::Latvia),
646            "LEB" => Ok(Self::Lebanon),
647            "LIB" => Ok(Self::Libya),
648            "LIC" => Ok(Self::Liechtenstein),
649            "LTU" => Ok(Self::Lithuania),
650            "LUX" => Ok(Self::Luxembourg),
651            "MAL" => Ok(Self::Malaysia),
652            "MAU" => Ok(Self::Mauritania),
653            "MEX" => Ok(Self::Mexico),
654            "MLI" => Ok(Self::Mali),
655            "MLT" => Ok(Self::Malta),
656            "MNC" => Ok(Self::Monaco),
657            "MOL" => Ok(Self::Moldova),
658            "MON" => Ok(Self::Mongolia),
659            "MOZ" => Ok(Self::Mozambique),
660            "MRC" => Ok(Self::Morocco),
661            "MRT" => Ok(Self::Mauritius),
662            "MYN" => Ok(Self::Myanmar),
663            "NCG" => Ok(Self::Nicaragua),
664            "NET" => Ok(Self::TheInternet),
665            "NIG" => Ok(Self::Nigeria),
666            "NLA" => Ok(Self::NetherlandsAntilles),
667            "NLD" => Ok(Self::Netherlands),
668            "NOR" => Ok(Self::Norway),
669            "NZD" => Ok(Self::NewZealand),
670            "OST" => Ok(Self::Austria),
671            "PAK" => Ok(Self::Pakistan),
672            "PAL" => Ok(Self::Palestine),
673            "PAN" => Ok(Self::Panama),
674            "PAR" => Ok(Self::Paraguay),
675            "PER" => Ok(Self::Peru),
676            "PHI" => Ok(Self::Philippines),
677            "PNG" => Ok(Self::PapuaNewGuinea),
678            "POL" => Ok(Self::Poland),
679            "POR" => Ok(Self::Portugal),
680            "PRC" => Ok(Self::PeoplesRepublicOfChina),
681            "PRO" => Ok(Self::PuertoRico),
682            "QTR" => Ok(Self::Qatar),
683            "RIN" => Ok(Self::Indonesia),
684            "ROM" => Ok(Self::Romania),
685            "RUS" => Ok(Self::Russia),
686            "SAF" => Ok(Self::SouthAfrica),
687            "SAL" => Ok(Self::ElSalvador),
688            "SCO" => Ok(Self::Scotland),
689            "SEA" => Ok(Self::AtSea),
690            "SEN" => Ok(Self::Senegal),
691            "SEY" => Ok(Self::Seychelles),
692            "SIP" => Ok(Self::Singapore),
693            "SLV" => Ok(Self::Slovenia),
694            "SMA" => Ok(Self::SanMarino),
695            "SPC" => Ok(Self::AboardSpacecraft),
696            "SRI" => Ok(Self::SriLanka),
697            "SUD" => Ok(Self::Sudan),
698            "SUR" => Ok(Self::Surinam),
699            "SVE" => Ok(Self::Sweden),
700            "SWZ" => Ok(Self::Switzerland),
701            "SYR" => Ok(Self::Syria),
702            "TAI" => Ok(Self::Thailand),
703            "TMT" => Ok(Self::Turkmenistan),
704            "TRK" => Ok(Self::Turkey),
705            "TTO" => Ok(Self::TrinidadAndTobago),
706            "TUN" => Ok(Self::Tunisia),
707            "UAE" => Ok(Self::UnitedArabEmirates),
708            "UGA" => Ok(Self::Uganda),
709            "UKR" => Ok(Self::Ukraine),
710            "UNK" => Ok(Self::Unknown),
711            "URU" => Ok(Self::Uruguay),
712            "USA" => Ok(Self::UnitedStatesOfAmerica),
713            "UZB" => Ok(Self::Uzbekistan),
714            "VEN" => Ok(Self::Venezuela),
715            "VGB" => Ok(Self::BritishVirginIslands),
716            "VIE" => Ok(Self::Vietnam),
717            "VUS" => Ok(Self::USVirginIslands),
718            "WLS" => Ok(Self::Wales),
719            "YEM" => Ok(Self::Yemen),
720            "ZAM" => Ok(Self::Zambia),
721            "ZIM" => Ok(Self::Zimbabwe),
722            "ZRE" => Ok(Self::Zaire),
723            _ => Err(crate::errors::ChessError::InvalidPgn(std::io::Error::new(
724                std::io::ErrorKind::InvalidData,
725                format!("Unknown country code: {code}"),
726            ))),
727        }
728    }
729}
730
731impl std::fmt::Display for OlympicCountryCode {
732    #[allow(clippy::too_many_lines)]
733    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
734        let code = match self {
735            Self::Afghanistan => "AFG",
736            Self::AboardAircraft => "AIR",
737            Self::Albania => "ALB",
738            Self::Algeria => "ALG",
739            Self::Andorra => "AND",
740            Self::Angola => "ANG",
741            Self::Antigua => "ANT",
742            Self::Argentina => "ARG",
743            Self::Armenia => "ARM",
744            Self::Antarctica => "ATA",
745            Self::Australia => "AUS",
746            Self::Azerbaijan => "AZB",
747            Self::Bangladesh => "BAN",
748            Self::Bahrain => "BAR",
749            Self::Bahamas => "BHM",
750            Self::Belgium => "BEL",
751            Self::Bermuda => "BER",
752            Self::BosniaHerzegovina => "BIH",
753            Self::Belarus => "BLA",
754            Self::Bulgaria => "BLG",
755            Self::Belize => "BLZ",
756            Self::Bolivia => "BOL",
757            Self::Barbados => "BRB",
758            Self::Brazil => "BRS",
759            Self::Brunei => "BRU",
760            Self::Botswana => "BSW",
761            Self::Canada => "CAN",
762            Self::Chile => "CHI",
763            Self::Columbia => "COL",
764            Self::CostaRica => "CRA",
765            Self::Croatia => "CRO",
766            Self::Czechoslovakia => "CSR",
767            Self::Cuba => "CUB",
768            Self::Cyprus => "CYP",
769            Self::Denmark => "DEN",
770            Self::DominicanRepublic => "DOM",
771            Self::Ecuador => "ECU",
772            Self::Egypt => "EGY",
773            Self::England => "ENG",
774            Self::Spain => "ESP",
775            Self::Estonia => "EST",
776            Self::FaroeIslands => "FAI",
777            Self::Fiji => "FIJ",
778            Self::Finland => "FIN",
779            Self::France => "FRA",
780            Self::Gambia => "GAM",
781            Self::GuernseyJersey => "GCI",
782            Self::Georgia => "GEO",
783            Self::Germany => "GER",
784            Self::Ghana => "GHA",
785            Self::Greece => "GRC",
786            Self::Guatemala => "GUA",
787            Self::Guyana => "GUY",
788            Self::Haiti => "HAI",
789            Self::HongKong => "HKG",
790            Self::Honduras => "HON",
791            Self::Hungary => "HUN",
792            Self::India => "IND",
793            Self::Ireland => "IRL",
794            Self::Iran => "IRN",
795            Self::Iraq => "IRQ",
796            Self::Iceland => "ISD",
797            Self::Israel => "ISR",
798            Self::Italy => "ITA",
799            Self::IvoryCoast => "IVO",
800            Self::Jamaica => "JAM",
801            Self::Japan => "JAP",
802            Self::Jordan => "JRD",
803            Self::Yugoslavia => "JUG",
804            Self::Kazakhstan => "KAZ",
805            Self::Kenya => "KEN",
806            Self::Kyrgyzstan => "KIR",
807            Self::Kuwait => "KUW",
808            Self::Latvia => "LAT",
809            Self::Lebanon => "LEB",
810            Self::Libya => "LIB",
811            Self::Liechtenstein => "LIC",
812            Self::Lithuania => "LTU",
813            Self::Luxembourg => "LUX",
814            Self::Malaysia => "MAL",
815            Self::Mauritania => "MAU",
816            Self::Mexico => "MEX",
817            Self::Mali => "MLI",
818            Self::Malta => "MLT",
819            Self::Monaco => "MNC",
820            Self::Moldova => "MOL",
821            Self::Mongolia => "MON",
822            Self::Mozambique => "MOZ",
823            Self::Morocco => "MRC",
824            Self::Mauritius => "MRT",
825            Self::Myanmar => "MYN",
826            Self::Nicaragua => "NCG",
827            Self::TheInternet => "NET",
828            Self::Nigeria => "NIG",
829            Self::NetherlandsAntilles => "NLA",
830            Self::Netherlands => "NLD",
831            Self::Norway => "NOR",
832            Self::NewZealand => "NZD",
833            Self::Austria => "OST",
834            Self::Pakistan => "PAK",
835            Self::Palestine => "PAL",
836            Self::Panama => "PAN",
837            Self::Paraguay => "PAR",
838            Self::Peru => "PER",
839            Self::Philippines => "PHI",
840            Self::PapuaNewGuinea => "PNG",
841            Self::Poland => "POL",
842            Self::Portugal => "POR",
843            Self::PeoplesRepublicOfChina => "PRC",
844            Self::PuertoRico => "PRO",
845            Self::Qatar => "QTR",
846            Self::Indonesia => "RIN",
847            Self::Romania => "ROM",
848            Self::Russia => "RUS",
849            Self::SouthAfrica => "SAF",
850            Self::ElSalvador => "SAL",
851            Self::Scotland => "SCO",
852            Self::AtSea => "SEA",
853            Self::Senegal => "SEN",
854            Self::Seychelles => "SEY",
855            Self::Singapore => "SIP",
856            Self::Slovenia => "SLV",
857            Self::SanMarino => "SMA",
858            Self::AboardSpacecraft => "SPC",
859            Self::SriLanka => "SRI",
860            Self::Sudan => "SUD",
861            Self::Surinam => "SUR",
862            Self::Sweden => "SVE",
863            Self::Switzerland => "SWZ",
864            Self::Syria => "SYR",
865            Self::Thailand => "TAI",
866            Self::Turkmenistan => "TMT",
867            Self::Turkey => "TRK",
868            Self::TrinidadAndTobago => "TTO",
869            Self::Tunisia => "TUN",
870            Self::UnitedArabEmirates => "UAE",
871            Self::Uganda => "UGA",
872            Self::Ukraine => "UKR",
873            Self::Unknown => "UNK",
874            Self::Uruguay => "URU",
875            Self::UnitedStatesOfAmerica => "USA",
876            Self::Uzbekistan => "UZB",
877            Self::Venezuela => "VEN",
878            Self::BritishVirginIslands => "VGB",
879            Self::Vietnam => "VIE",
880            Self::USVirginIslands => "VUS",
881            Self::Wales => "WLS",
882            Self::Yemen => "YEM",
883            Self::Zambia => "ZAM",
884            Self::Zimbabwe => "ZIM",
885            Self::Zaire => "ZRE",
886        };
887        write!(f, "{code}")
888    }
889}