Process GameResult, fix TimeLeft, and allow Pass moves
This commit is contained in:
parent
33d0897096
commit
d6a91b6af6
|
@ -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(),
|
||||||
|
|
|
@ -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)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,4 +106,5 @@ pub enum Win {
|
||||||
Resignation,
|
Resignation,
|
||||||
Forfeit,
|
Forfeit,
|
||||||
Time,
|
Time,
|
||||||
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue