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