Process GameResult, fix TimeLeft, and allow Pass moves

This commit is contained in:
Savanni D'Gerinel 2023-09-17 00:04:58 -04:00
parent 33d0897096
commit d6a91b6af6
3 changed files with 143 additions and 33 deletions

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
parser::{self, Annotation, Evaluation, GameType, SetupInstr, Size, UnknownProperty}, parser::{self, Annotation, Evaluation, GameType, Move, SetupInstr, Size, UnknownProperty},
Color, Date, GameResult, Color, Date, GameResult,
}; };
use std::{collections::HashSet, time::Duration}; use std::{collections::HashSet, time::Duration};
@ -188,7 +188,7 @@ impl TryFrom<&parser::Node> for GameNode {
pub struct MoveNode { pub struct MoveNode {
id: Uuid, id: Uuid,
color: Color, color: Color,
position: String, mv: Move,
children: Vec<GameNode>, children: Vec<GameNode>,
time_left: Option<Duration>, time_left: Option<Duration>,
@ -202,11 +202,11 @@ pub struct MoveNode {
} }
impl MoveNode { impl MoveNode {
pub fn new(color: Color, position: String) -> Self { pub fn new(color: Color, mv: Move) -> Self {
Self { Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
color, color,
position, mv,
children: Vec::new(), children: Vec::new(),
time_left: None, time_left: None,
@ -237,13 +237,13 @@ impl TryFrom<&parser::Node> for MoveNode {
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> { fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
match n.mv() { match n.mv() {
Some((color, position)) => { Some((color, mv)) => {
let mut s = Self::new(color, position); let mut s = Self::new(color, mv);
for prop in n.properties.iter() { for prop in n.properties.iter() {
match prop { match prop {
parser::Property::Move((color, position)) => { parser::Property::Move((color, mv)) => {
if s.color != *color || s.position != *position { if s.color != *color || s.mv != *mv {
return Err(Self::Error::ConflictingProperty); return Err(Self::Error::ConflictingProperty);
} }
} }
@ -386,9 +386,9 @@ mod test {
Player::default(), Player::default(),
); );
let first_move = MoveNode::new(Color::Black, "dd".to_owned()); let first_move = MoveNode::new(Color::Black, Move::Move("dd".to_owned()));
let first_ = game.add_child(GameNode::MoveNode(first_move.clone())); let first_ = game.add_child(GameNode::MoveNode(first_move.clone()));
let second_move = MoveNode::new(Color::White, "qq".to_owned()); let second_move = MoveNode::new(Color::White, Move::Move("qq".to_owned()));
first_.add_child(GameNode::MoveNode(second_move.clone())); first_.add_child(GameNode::MoveNode(second_move.clone()));
let nodes = game.nodes(); let nodes = game.nodes();
@ -409,7 +409,7 @@ mod test {
fn game_node_can_parse_sgf_move_node() { fn game_node_can_parse_sgf_move_node() {
let n = parser::Node { let n = parser::Node {
properties: vec![ properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())), parser::Property::Move((Color::White, Move::Move("dp".to_owned()))),
parser::Property::TimeLeft((Color::White, Duration::from_secs(176))), parser::Property::TimeLeft((Color::White, Duration::from_secs(176))),
parser::Property::Comment("Comments in the game".to_owned()), parser::Property::Comment("Comments in the game".to_owned()),
], ],
@ -445,7 +445,7 @@ mod move_node_tests {
fn it_can_parse_an_sgf_move_node() { fn it_can_parse_an_sgf_move_node() {
let n = parser::Node { let n = parser::Node {
properties: vec![ properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())), parser::Property::Move((Color::White, Move::Move("dp".to_owned()))),
parser::Property::TimeLeft((Color::White, Duration::from_secs(176))), parser::Property::TimeLeft((Color::White, Duration::from_secs(176))),
parser::Property::Comment("Comments in the game".to_owned()), parser::Property::Comment("Comments in the game".to_owned()),
], ],
@ -453,7 +453,7 @@ mod move_node_tests {
}; };
assert_matches!(MoveNode::try_from(&n), Ok(node) => { assert_matches!(MoveNode::try_from(&n), Ok(node) => {
assert_eq!(node.color, Color::White); assert_eq!(node.color, Color::White);
assert_eq!(node.position, "dp".to_owned()); assert_eq!(node.mv, Move::Move("dp".to_owned()));
assert_eq!(node.children, vec![]); assert_eq!(node.children, vec![]);
assert_eq!(node.time_left, Some(Duration::from_secs(176))); assert_eq!(node.time_left, Some(Duration::from_secs(176)));
assert_eq!(node.comments, Some("Comments in the game".to_owned())); assert_eq!(node.comments, Some("Comments in the game".to_owned()));
@ -464,7 +464,7 @@ mod move_node_tests {
fn it_rejects_an_sgf_setup_node() { fn it_rejects_an_sgf_setup_node() {
let n = parser::Node { let n = parser::Node {
properties: vec![ properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())), parser::Property::Move((Color::White, Move::Move("dp".to_owned()))),
parser::Property::TimeLeft((Color::White, Duration::from_secs(176))), parser::Property::TimeLeft((Color::White, Duration::from_secs(176))),
parser::Property::SetupBlackStones(PositionList(vec![ parser::Property::SetupBlackStones(PositionList(vec![
"dd".to_owned(), "dd".to_owned(),

View File

@ -1,8 +1,8 @@
use crate::{Color, Date, Error, GameResult}; use crate::{Color, Date, Error, GameResult, Win};
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{escaped_transform, tag, take_until1}, bytes::complete::{escaped_transform, tag, take_until, take_until1},
character::complete::{alpha1, digit1, multispace0, multispace1, none_of}, character::complete::{alpha1, digit1, multispace0, multispace1, none_of},
combinator::{opt, value}, combinator::{opt, value},
error::ParseError, error::ParseError,
@ -262,7 +262,7 @@ pub enum SetupInstr {
} }
impl Node { impl Node {
pub fn mv(&self) -> Option<(Color, String)> { pub fn mv(&self) -> Option<(Color, Move)> {
self.find_by(|prop| match prop { self.find_by(|prop| match prop {
Property::Move(val) => Some(val.clone()), Property::Move(val) => Some(val.clone()),
_ => None, _ => None,
@ -343,6 +343,12 @@ impl ToString for Node {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Move {
Move(String),
Pass,
}
// KO // KO
// MN // MN
// N // N
@ -363,7 +369,7 @@ impl ToString for Node {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Property { pub enum Property {
// B, W // B, W
Move((Color, String)), Move((Color, Move)),
// C // C
Comment(String), Comment(String),
@ -488,8 +494,11 @@ pub struct UnknownProperty {
impl ToString for Property { impl ToString for Property {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Property::Move((color, position)) => { Property::Move((color, Move::Move(mv))) => {
format!("{}[{}]", color.abbreviation(), position) format!("{}[{}]", color.abbreviation(), mv)
}
Property::Move((color, Move::Pass)) => {
format!("{}[]", color.abbreviation())
} }
Property::TimeLeft((color, time)) => { Property::TimeLeft((color, time)) => {
format!("{}[{}]", color.abbreviation(), time.as_secs()) format!("{}[{}]", color.abbreviation(), time.as_secs())
@ -643,7 +652,7 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
"BR" => parse_propval(parse_simple_text().map(Property::BlackRank))(input)?, "BR" => parse_propval(parse_simple_text().map(Property::BlackRank))(input)?,
"BT" => parse_propval(parse_simple_text().map(Property::BlackTeam))(input)?, "BT" => parse_propval(parse_simple_text().map(Property::BlackTeam))(input)?,
"CP" => parse_propval(parse_simple_text().map(Property::Copyright))(input)?, "CP" => parse_propval(parse_simple_text().map(Property::Copyright))(input)?,
"DT" => unimplemented!(), "DT" => parse_propval(parse_date_field().map(Property::EventDates))(input)?,
"EV" => parse_propval(parse_simple_text().map(Property::EventName))(input)?, "EV" => parse_propval(parse_simple_text().map(Property::EventName))(input)?,
"GN" => parse_propval(parse_simple_text().map(Property::GameName))(input)?, "GN" => parse_propval(parse_simple_text().map(Property::GameName))(input)?,
"GC" => parse_propval(parse_simple_text().map(Property::ExtraGameInformation))(input)?, "GC" => parse_propval(parse_simple_text().map(Property::ExtraGameInformation))(input)?,
@ -652,7 +661,7 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
"PB" => parse_propval(parse_simple_text().map(Property::BlackPlayer))(input)?, "PB" => parse_propval(parse_simple_text().map(Property::BlackPlayer))(input)?,
"PC" => parse_propval(parse_simple_text().map(Property::GameLocation))(input)?, "PC" => parse_propval(parse_simple_text().map(Property::GameLocation))(input)?,
"PW" => parse_propval(parse_simple_text().map(Property::WhitePlayer))(input)?, "PW" => parse_propval(parse_simple_text().map(Property::WhitePlayer))(input)?,
"RE" => unimplemented!(), "RE" => parse_propval(parse_game_result().map(Property::Result))(input)?,
"RO" => parse_propval(parse_simple_text().map(Property::Round))(input)?, "RO" => parse_propval(parse_simple_text().map(Property::Round))(input)?,
"RU" => parse_propval(parse_simple_text().map(Property::Ruleset))(input)?, "RU" => parse_propval(parse_simple_text().map(Property::Ruleset))(input)?,
"SO" => parse_propval(parse_simple_text().map(Property::Source))(input)?, "SO" => parse_propval(parse_simple_text().map(Property::Source))(input)?,
@ -683,8 +692,14 @@ fn parse_move<'a, E: nom::error::ParseError<&'a str>>(
{ {
let color = color.clone(); let color = color.clone();
move |input: &'a str| { move |input: &'a str| {
take_until1("]") take_until("]")
.map(|text: &'a str| Property::Move((color.clone(), text.to_owned()))) .map(|text: &'a str| {
if text.is_empty() {
Property::Move((color.clone(), Move::Pass))
} else {
Property::Move((color.clone(), Move::Move(text.to_owned())))
}
})
.parse(input) .parse(input)
} }
} }
@ -705,7 +720,6 @@ fn parse_time_left<'a, E: ParseError<&'a str>>(
let color = color.clone(); let color = color.clone();
move |input: &'a str| { move |input: &'a str| {
let (input, value) = parse_real().parse(input)?; let (input, value) = parse_real().parse(input)?;
let (input, _) = tag("]")(input)?;
Ok(( Ok((
input, input,
Property::TimeLeft((color.clone(), Duration::from_secs(value as u64))), Property::TimeLeft((color.clone(), Duration::from_secs(value as u64))),
@ -714,7 +728,7 @@ fn parse_time_left<'a, E: ParseError<&'a str>>(
} }
} }
pub fn parse_size<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, Size, E> { fn parse_size<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, Size, E> {
|input: &'a str| { |input: &'a str| {
let (input, dimensions) = separated_list1(tag(":"), digit1)(input)?; let (input, dimensions) = separated_list1(tag(":"), digit1)(input)?;
let (width, height) = match dimensions.as_slice() { let (width, height) = match dimensions.as_slice() {
@ -729,6 +743,11 @@ pub fn parse_size<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a s
} }
} }
fn parse_game_result<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, GameResult, E>
{
alt((parse_draw_result(), parse_void_result(), parse_win_result()))
}
fn parse_propval<'a, E: nom::error::ParseError<&'a str>>( fn parse_propval<'a, E: nom::error::ParseError<&'a str>>(
mut parser: impl Parser<&'a str, Property, E>, mut parser: impl Parser<&'a str, Property, E>,
) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> { ) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> {
@ -891,6 +910,62 @@ fn parse_three<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment
} }
} }
fn parse_draw_result<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, GameResult, E>
{
tag("0").map(|_| GameResult::Draw)
}
fn parse_void_result<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, GameResult, E>
{
tag("Void").map(|_| GameResult::Void)
}
fn parse_win_result<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, GameResult, E>
{
|input: &'a str| {
let (input, color) = alt((
tag("B").map(|_| Color::Black),
tag("b").map(|_| Color::Black),
tag("W").map(|_| Color::White),
tag("w").map(|_| Color::White),
))(input)?;
let (input, _) = tag("+")(input)?;
let (input, score) = parse_win_score().parse(input)?;
Ok((
input,
match color {
Color::Black => GameResult::Black(score),
Color::White => GameResult::White(score),
},
))
}
}
enum WinType {
St(String),
Num(f32),
}
fn parse_win_score<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, Win, E> {
|input: &'a str| {
let (input, win) = alt((
parse_real().map(|v| WinType::Num(v)),
parse_simple_text().map(|s| WinType::St(s)),
))(input)?;
let w = match win {
WinType::St(s) => match s.to_ascii_lowercase().as_str() {
"r" | "resign" => Win::Resignation,
"t" | "time" => Win::Time,
"f" | "forfeit" => Win::Forfeit,
_ => Win::Unknown,
},
WinType::Num(n) => Win::Score(n),
};
Ok((input, w))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -913,7 +988,7 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Move::Move("ab".to_owned())))],
next: vec![] next: vec![]
} }
); );
@ -925,12 +1000,12 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Move::Move("ab".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, "dp".to_owned()))], properties: vec![Property::Move((Color::White, Move::Move("dp".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, "pq".to_owned())), Property::Move((Color::Black, Move::Move("pq".to_owned()))),
Property::Comment("some comments".to_owned()) Property::Comment("some comments".to_owned())
], ],
next: vec![], next: vec![],
@ -949,12 +1024,12 @@ mod test {
assert_eq!( assert_eq!(
sequence, sequence,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Move::Move("ab".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, "dp".to_owned()))], properties: vec![Property::Move((Color::White, Move::Move("dp".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, "pq".to_owned())), Property::Move((Color::Black, Move::Move("pq".to_owned()))),
Property::Comment("some comments".to_owned()) Property::Comment("some comments".to_owned())
], ],
next: vec![], next: vec![],
@ -1190,3 +1265,37 @@ mod date_test {
); );
} }
} }
#[cfg(test)]
mod property_tests {
use super::*;
#[test]
fn it_can_parse_time_left() {
let (_, val) =
parse_time_left::<nom::error::VerboseError<&str>>(Color::Black)("170]").unwrap();
assert_eq!(
val,
Property::TimeLeft((Color::Black, Duration::from_secs(170)))
);
let (_, val) =
parse_time_left::<nom::error::VerboseError<&str>>(Color::Black)("170.6]").unwrap();
assert_eq!(
val,
Property::TimeLeft((Color::Black, Duration::from_secs(170)))
);
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("BL[170]").unwrap();
assert_eq!(
prop,
Property::TimeLeft((Color::Black, Duration::from_secs(170)))
);
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("BL[170.5]").unwrap();
assert_eq!(
prop,
Property::TimeLeft((Color::Black, Duration::from_secs(170)))
);
}
}

View File

@ -106,4 +106,5 @@ pub enum Win {
Resignation, Resignation,
Forfeit, Forfeit,
Time, Time,
Unknown,
} }