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::{
parser::{self, Annotation, Evaluation, GameType, SetupInstr, Size, UnknownProperty},
parser::{self, Annotation, Evaluation, GameType, Move, SetupInstr, Size, UnknownProperty},
Color, Date, GameResult,
};
use std::{collections::HashSet, time::Duration};
@ -188,7 +188,7 @@ impl TryFrom<&parser::Node> for GameNode {
pub struct MoveNode {
id: Uuid,
color: Color,
position: String,
mv: Move,
children: Vec<GameNode>,
time_left: Option<Duration>,
@ -202,11 +202,11 @@ pub struct MoveNode {
}
impl MoveNode {
pub fn new(color: Color, position: String) -> Self {
pub fn new(color: Color, mv: Move) -> Self {
Self {
id: Uuid::new_v4(),
color,
position,
mv,
children: Vec::new(),
time_left: None,
@ -237,13 +237,13 @@ impl TryFrom<&parser::Node> for MoveNode {
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
match n.mv() {
Some((color, position)) => {
let mut s = Self::new(color, position);
Some((color, mv)) => {
let mut s = Self::new(color, mv);
for prop in n.properties.iter() {
match prop {
parser::Property::Move((color, position)) => {
if s.color != *color || s.position != *position {
parser::Property::Move((color, mv)) => {
if s.color != *color || s.mv != *mv {
return Err(Self::Error::ConflictingProperty);
}
}
@ -386,9 +386,9 @@ mod test {
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 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()));
let nodes = game.nodes();
@ -409,7 +409,7 @@ mod test {
fn game_node_can_parse_sgf_move_node() {
let n = parser::Node {
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::Comment("Comments in the game".to_owned()),
],
@ -445,7 +445,7 @@ mod move_node_tests {
fn it_can_parse_an_sgf_move_node() {
let n = parser::Node {
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::Comment("Comments in the game".to_owned()),
],
@ -453,7 +453,7 @@ mod move_node_tests {
};
assert_matches!(MoveNode::try_from(&n), Ok(node) => {
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.time_left, Some(Duration::from_secs(176)));
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() {
let n = parser::Node {
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::SetupBlackStones(PositionList(vec![
"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 nom::{
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},
combinator::{opt, value},
error::ParseError,
@ -262,7 +262,7 @@ pub enum SetupInstr {
}
impl Node {
pub fn mv(&self) -> Option<(Color, String)> {
pub fn mv(&self) -> Option<(Color, Move)> {
self.find_by(|prop| match prop {
Property::Move(val) => Some(val.clone()),
_ => None,
@ -343,6 +343,12 @@ impl ToString for Node {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Move {
Move(String),
Pass,
}
// KO
// MN
// N
@ -363,7 +369,7 @@ impl ToString for Node {
#[derive(Clone, Debug, PartialEq)]
pub enum Property {
// B, W
Move((Color, String)),
Move((Color, Move)),
// C
Comment(String),
@ -488,8 +494,11 @@ pub struct UnknownProperty {
impl ToString for Property {
fn to_string(&self) -> String {
match self {
Property::Move((color, position)) => {
format!("{}[{}]", color.abbreviation(), position)
Property::Move((color, Move::Move(mv))) => {
format!("{}[{}]", color.abbreviation(), mv)
}
Property::Move((color, Move::Pass)) => {
format!("{}[]", color.abbreviation())
}
Property::TimeLeft((color, time)) => {
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)?,
"BT" => parse_propval(parse_simple_text().map(Property::BlackTeam))(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)?,
"GN" => parse_propval(parse_simple_text().map(Property::GameName))(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)?,
"PC" => parse_propval(parse_simple_text().map(Property::GameLocation))(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)?,
"RU" => parse_propval(parse_simple_text().map(Property::Ruleset))(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();
move |input: &'a str| {
take_until1("]")
.map(|text: &'a str| Property::Move((color.clone(), text.to_owned())))
take_until("]")
.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)
}
}
@ -705,7 +720,6 @@ fn parse_time_left<'a, E: ParseError<&'a str>>(
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))),
@ -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| {
let (input, dimensions) = separated_list1(tag(":"), digit1)(input)?;
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>>(
mut parser: impl Parser<&'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)]
mod test {
use super::*;
@ -913,7 +988,7 @@ mod test {
assert_eq!(
node,
Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))],
properties: vec![Property::Move((Color::Black, Move::Move("ab".to_owned())))],
next: vec![]
}
);
@ -925,12 +1000,12 @@ mod test {
assert_eq!(
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 {
properties: vec![Property::Move((Color::White, "dp".to_owned()))],
properties: vec![Property::Move((Color::White, Move::Move("dp".to_owned())))],
next: vec![Node {
properties: vec![
Property::Move((Color::Black, "pq".to_owned())),
Property::Move((Color::Black, Move::Move("pq".to_owned()))),
Property::Comment("some comments".to_owned())
],
next: vec![],
@ -949,12 +1024,12 @@ mod test {
assert_eq!(
sequence,
Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))],
properties: vec![Property::Move((Color::Black, Move::Move("ab".to_owned())))],
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 {
properties: vec![
Property::Move((Color::Black, "pq".to_owned())),
Property::Move((Color::Black, Move::Move("pq".to_owned()))),
Property::Comment("some comments".to_owned())
],
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,
Forfeit,
Time,
Unknown,
}