diff --git a/sgf/src/game.rs b/sgf/src/game.rs index b59b9b9..c7b0578 100644 --- a/sgf/src/game.rs +++ b/sgf/src/game.rs @@ -268,7 +268,7 @@ pub struct MoveNode { name: Option, evaluation: Option, value: Option, - comments: Vec, + comments: Option, annotation: Option, unknown_props: Vec<(String, String)>, } @@ -286,7 +286,7 @@ impl MoveNode { name: None, evaluation: None, value: None, - comments: vec![], + comments: None, annotation: None, unknown_props: vec![], } @@ -309,7 +309,14 @@ impl TryFrom<&parser::Node> for MoveNode { fn try_from(n: &parser::Node) -> Result { match n.move_() { - Some((color, position)) => Ok(MoveNode::new(color, position)), + Some((color, position)) => { + let mut s = Self::new(color, position); + + s.time_left = n.time_left(); + s.comments = n.comments(); + + Ok(s) + } None => Err(ConversionError::IncompatibleNodeType), } } @@ -419,16 +426,8 @@ mod test { let n = parser::Node { properties: vec![ parser::Property::Move((Color::White, "dp".to_owned())), - /* - parser::Property { - ident: "WL".to_owned(), - values: vec!["176.099".to_owned()], - }, - parser::Property { - ident: "C".to_owned(), - values: vec!["Comments in the game".to_owned()], - }, - */ + parser::Property::TimeLeft((Color::White, Duration::from_secs(176))), + parser::Property::Comment("Comments in the game".to_owned()), ], next: vec![], }; @@ -461,16 +460,8 @@ mod move_node_tests { let n = parser::Node { properties: vec![ parser::Property::Move((Color::White, "dp".to_owned())), - /* - parser::Property { - ident: "WL".to_owned(), - values: vec!["176.099".to_owned()], - }, - parser::Property { - ident: "C".to_owned(), - values: vec!["Comments in the game".to_owned()], - }, - */ + parser::Property::TimeLeft((Color::White, Duration::from_secs(176))), + parser::Property::Comment("Comments in the game".to_owned()), ], next: vec![], }; @@ -479,7 +470,7 @@ mod move_node_tests { assert_eq!(node.position, "dp".to_owned()); assert_eq!(node.children, vec![]); assert_eq!(node.time_left, Some(Duration::from_secs(176))); - assert_eq!(node.comments, vec!["Comments in the game".to_owned()]); + assert_eq!(node.comments, Some("Comments in the game".to_owned())); }); } diff --git a/sgf/src/parser.rs b/sgf/src/parser.rs index 5d91415..1134594 100644 --- a/sgf/src/parser.rs +++ b/sgf/src/parser.rs @@ -8,7 +8,7 @@ use nom::{ multi::{many0, many1, separated_list1}, IResult, Parser, }; -use std::num::ParseIntError; +use std::{num::ParseIntError, time::Duration}; impl From for Error { fn from(_: ParseSizeError) -> Self { @@ -240,13 +240,31 @@ pub struct Node { impl Node { pub fn move_(&self) -> Option<(Color, String)> { - self.properties - .iter() - .filter_map(|prop| match prop { - Property::Move(val) => Some(val.clone()), - _ => None, - }) - .next() + self.find_by(|prop| match prop { + Property::Move(val) => Some(val.clone()), + _ => None, + }) + } + + pub fn time_left(&self) -> Option { + self.find_by(|prop| match prop { + Property::TimeLeft((_, duration)) => Some(duration.clone()), + _ => None, + }) + } + + pub fn comments(&self) -> Option { + self.find_by(|prop| match prop { + Property::Comment(c) => Some(c.clone()), + _ => None, + }) + } + + fn find_by(&self, f: F) -> Option + where + F: FnMut(&Property) -> Option, + { + self.properties.iter().filter_map(f).next() } } @@ -287,10 +305,8 @@ impl ToString for Node { // SL // SQ // TR -// BL // OB // OW -// WL // FG // PM // VW @@ -414,7 +430,7 @@ pub enum Property { Source(String), // TM - TimeLimit(std::time::Duration), + TimeLimit(Duration), // US User(String), @@ -425,6 +441,9 @@ pub enum Property { // WT WhiteTeam(String), + // BL, WL + TimeLeft((Color, Duration)), + Unknown(UnknownProperty), } @@ -440,6 +459,9 @@ impl ToString for Property { Property::Move((color, position)) => { format!("{}[{}]", color.abbreviation(), position) } + Property::TimeLeft((color, time)) => { + format!("{}[{}]", color.abbreviation(), time.as_secs()) + } Property::Comment(value) => format!("C[{}]", value), Property::BadMove => "BM[]".to_owned(), Property::DoubtfulMove => "DO[]".to_owned(), @@ -550,6 +572,8 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>( "W" => parse_propval(parse_move(Color::White))(input)?, "B" => parse_propval(parse_move(Color::Black))(input)?, "C" => parse_propval(parse_comment())(input)?, + "WL" => parse_propval(parse_time_left(Color::White))(input)?, + "BL" => parse_propval(parse_time_left(Color::Black))(input)?, "BM" => discard_propval().map(|_| Property::BadMove).parse(input)?, "DO" => discard_propval() .map(|_| Property::DoubtfulMove) @@ -626,6 +650,22 @@ fn parse_move<'a, E: nom::error::ParseError<&'a str>>( } } +fn parse_time_left<'a, E: ParseError<&'a str>>( + color: Color, +) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> { + { + let color = color.clone(); + move |input: &'a str| { + let (input, value) = parse_real().parse(input)?; + let (input, _) = tag("]")(input)?; + Ok(( + input, + Property::TimeLeft((color.clone(), Duration::from_secs(value as u64))), + )) + } + } +} + fn parse_propval<'a, E: nom::error::ParseError<&'a str>>( mut parser: impl Parser<&'a str, Property, E>, ) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> {