// https://red-bean.com/sgf/user_guide/index.html // https://red-bean.com/sgf/sgf4.html // todo: support collections in a file // Properties to support. Remove each one as it gets support. // B // KO // MN // W // AB // AE // AW // PL // C // DM // GB // GW // HO // N // UC // V // BM // DO // IT // TE // AR // CR // DD // LB // LN // MA // SL // SQ // TR // AP // CA // FF // GM // ST // SZ // AN // BR // BT // CP // DT // EV // GN // GC // ON // OT // PB // PC // PW // RE // RO // RU // SO // TM // US // WR // WT // BL // OB // OW // WL // FG // PM // VW use nom; use thiserror::Error; pub enum Warning {} #[derive(Debug, PartialEq, Error)] pub enum ParseError { #[error("An unknown error was found")] UnknownError, } // todo: support ST root node #[derive(Debug)] pub struct GameTree { pub file_format: i8, pub app: Option, pub game_type: GameType, pub board_size: Size, pub text: String, } pub struct GameInfo { pub annotator: Option, pub copyright: Option, pub event: Option, // Games can be played across multiple days, even multiple years. The format specifies // shortcuts. pub date_time: Vec, pub location: Option, // special rules for the round-number and type pub round: Option, pub ruleset: Option, pub source: Option, pub time_limits: Option, pub game_keeper: Option, pub game_name: Option, pub game_comments: Option, pub black_player: Option, pub black_rank: Option, pub black_team: Option, pub white_player: Option, pub white_rank: Option, pub white_team: Option, pub opening: Option, pub overtime: Option, pub result: Option, } pub enum GameResult { Annulled, Draw, Black(Win), White(Win), } pub enum Win { Score(i32), Resignation, Forfeit, Time, } #[derive(Debug, PartialEq)] pub struct Size { width: i32, height: i32, } #[derive(Debug, PartialEq)] pub enum GameType { Go, Unsupported, } struct Sequence(Node); struct Node { // properties } struct Property { ident: String, value: Vec, } enum PropType { Move, Setup, Root, GameInfo, } enum PropValue { Empty, Number, Real, Double, Color, SimpleText, Text, Point, Move, Stone, } // note: must preserve unknown properties // note: must fix or preserve illegally formatted game-info properties // note: must correct or delete illegally foramtted properties, but display a warning pub fn parse_sgf(input: &str) -> Result<(GameTree, Vec), ParseError> { Err(ParseError::UnknownError) } pub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; const EXAMPLE_1: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c]) (;C[d];C[e])) (;C[f](;C[g];C[h];C[i]) (;C[j])))"; const EXAMPLE_2: &'static str = "(;FF[4]GM[1]SZ[19]AP[SGFC:1.13b] PB[troy]BR[12k*] PW[john]WR[11k*] KM[0.5]RE[W+12.5] DT[1998-06-15] TM[600] ;B[pd];W[dp];B[pq];W[dd];B[qk];W[jd];B[fq];W[dj];B[jp];W[jj] ;B[cn]LB[dn:A][po:B]C[dada: other ideas are 'A' (d6) or 'B' (q5)] ;W[eo](;B[dl]C[dada: hm - looks troublesome. Usually B plays the 3,3 invasion - see variation];W[qo];B[qp] ... ;W[sr];B[sk];W[sg];B[pa];W[gc];B[pi];W[ph];B[de];W[ed];B[kn] ;W[dh];B[eh];W[se];B[sd];W[af];B[ie];W[id];B[hf];W[hd];B[if] ;W[fp];B[gq];W[qj];B[sj];W[rh];B[sn];W[so];B[sm];W[ep];B[mn]) ... (;W[dq]N[wrong direction];B[qo];W[qp]))"; fn with_examples(f: impl FnOnce(GameTree, GameTree)) { let (example_1, _) = parse_sgf(EXAMPLE_1).unwrap(); let (example_2, _) = parse_sgf(EXAMPLE_2).unwrap(); f(example_1, example_2); } #[test] fn it_parses_game_root() { with_examples(|ex_1, ex_2| { assert_eq!(ex_1.file_format, 4); assert_eq!(ex_1.app, None); assert_eq!(ex_1.game_type, GameType::Go); assert_eq!( ex_1.board_size, Size { width: 19, height: 19 } ); assert_eq!(ex_1.text, EXAMPLE_1.to_owned()); assert_eq!(ex_2.file_format, 4); assert_eq!(ex_2.app, Some("SGFC:1.13b".to_owned())); assert_eq!(ex_2.game_type, GameType::Go); assert_eq!( ex_2.board_size, Size { width: 19, height: 19 } ); assert_eq!(ex_2.text, EXAMPLE_2.to_owned()); }); } }