Thoroughly process move nodes, start processing setup nodes

This commit is contained in:
Savanni D'Gerinel 2023-09-15 11:07:09 -04:00
parent 349f71fc81
commit 7fc1530245
2 changed files with 194 additions and 238 deletions

View File

@ -1,175 +1,34 @@
use crate::{parser, Color}; use crate::{
parser::{self, Annotation, Evaluation, UnknownProperty},
Color,
};
use std::{collections::HashSet, time::Duration}; use std::{collections::HashSet, time::Duration};
use uuid::Uuid; use uuid::Uuid;
/*
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum PropertyError { pub enum PropertyError {
ConflictingProperty, ConflictingProperty,
} }
*/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SetupError { pub enum MoveNodeError {
IncompatibleProperty,
ConflictingProperty,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SetupNodeError {
IncompatibleProperty,
ConflictingPosition, ConflictingPosition,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ConversionError { pub enum GameNodeError {
IncompatibleNodeType, UnsupportedGameNode,
InvalidPositionSyntax,
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Evaluation {
Even,
GoodForBlack,
GoodForWhite,
Unclear,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Annotation {
BadMove,
DoubtfulMove,
InterestingMove,
Tesuji,
}
/*
#[derive(Clone, Debug, PartialEq)]
pub enum NodeProperty {
Comment(String),
EvenResult(Double),
GoodForBlack(Double),
GoodForWhite(Double),
Hotspot(Double),
NodeName(String),
Unclear(Double),
Value(f64),
Unknown((String, String)),
}
#[derive(Clone, Debug, PartialEq)]
pub struct NodeProperties(Vec<NodeProperty>);
impl NodeProperties {
fn property_conflicts(&self, prop: &NodeProperty) -> bool {
match prop {
NodeProperty::EvenResult(_)
| NodeProperty::GoodForBlack(_)
| NodeProperty::GoodForWhite(_)
| NodeProperty::Unclear(_) => self.contains_exclusive(),
_ => false,
}
}
fn contains_exclusive(&self) -> bool {
self.0.iter().any(|prop| match prop {
NodeProperty::EvenResult(_)
| NodeProperty::GoodForBlack(_)
| NodeProperty::GoodForWhite(_)
| NodeProperty::Unclear(_) => true,
_ => false,
})
}
}
impl From<&MoveProperties> for NodeProperties {
fn from(props: &MoveProperties) -> NodeProperties {
let props: Vec<NodeProperty> = props
.0
.iter()
.filter_map(|prop| {
if let MoveProperty::NodeProperty(p) = prop {
Some(p.clone())
} else {
None
}
})
.collect();
NodeProperties(props)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TimingProperty {
BlackTimeLeft(f64),
BlackMovesLeft(f64),
WhiteMovesLeft(f64),
WhiteTimeLeft(f64),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MoveAnnotationProperty {
BadMove(f64),
DoubtfulMove(f64),
InterestingMove(f64),
Tesuji(f64),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MoveProperty {
NodeProperty(NodeProperty),
TimingProperty(TimingProperty),
MoveAnnotationProperty(MoveAnnotationProperty),
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct MoveProperties(Vec<MoveProperty>);
impl MoveProperties {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn add_property(&mut self, prop: MoveProperty) -> Result<(), PropertyError> {
match prop {
MoveProperty::NodeProperty(node_prop) => {
if NodeProperties::from(self).property_conflicts(&node_prop) {
self.0.push(prop);
} else {
return Err(PropertyError::ConflictingProperty);
}
}
MoveProperty::TimingProperty(_) => {}
MoveProperty::MoveAnnotationProperty(_) => {
if self
.0
.iter()
.filter(|prop| match prop {
MoveProperty::MoveAnnotationProperty(_) => true,
_ => false,
})
.count()
> 0
{
return Err(PropertyError::ConflictingProperty);
}
self.0.push(prop);
}
}
return Ok(());
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SetupProperty {
NodeProperty(NodeProperty),
PlayerTurn(Color),
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct SetupProperties(Vec<SetupProperty>);
impl SetupProperties {
pub fn add_property(&mut self, prop: SetupProperty) {
match prop {
SetupProperty::NodeProperty(_) => {}
SetupProperty::PlayerTurn(_) => self.0.push(prop),
}
}
}
*/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum GameNode { pub enum GameNode {
MoveNode(MoveNode), MoveNode(MoveNode),
@ -228,9 +87,12 @@ impl Node for GameNode {
} }
impl TryFrom<&parser::Node> for GameNode { impl TryFrom<&parser::Node> for GameNode {
type Error = ConversionError; type Error = GameNodeError;
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> { fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
unimplemented!() MoveNode::try_from(n)
.map(GameNode::MoveNode)
.or_else(|_| SetupNode::try_from(n).map(GameNode::SetupNode))
.map_err(|_| Self::Error::UnsupportedGameNode)
} }
} }
@ -305,19 +167,57 @@ impl Node for MoveNode {
} }
impl TryFrom<&parser::Node> for MoveNode { impl TryFrom<&parser::Node> for MoveNode {
type Error = ConversionError; type Error = MoveNodeError;
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> { fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
match n.move_() { match n.mv() {
Some((color, position)) => { Some((color, position)) => {
let mut s = Self::new(color, position); let mut s = Self::new(color, position);
s.time_left = n.time_left(); for prop in n.properties.iter() {
s.comments = n.comments(); match prop {
parser::Property::Move((color, position)) => {
if s.color != *color || s.position != *position {
return Err(Self::Error::ConflictingProperty);
}
}
parser::Property::TimeLeft((color, duration)) => {
if s.color != *color {
return Err(Self::Error::ConflictingProperty);
}
if s.time_left.is_some() {
return Err(Self::Error::ConflictingProperty);
}
s.time_left = Some(duration.clone());
}
parser::Property::Comment(cmt) => {
if s.comments.is_some() {
return Err(Self::Error::ConflictingProperty);
}
s.comments = Some(cmt.clone());
}
parser::Property::Evaluation(evaluation) => {
if s.evaluation.is_some() {
return Err(Self::Error::ConflictingProperty);
}
s.evaluation = Some(*evaluation)
}
parser::Property::Annotation(annotation) => {
if s.annotation.is_some() {
return Err(Self::Error::ConflictingProperty);
}
s.annotation = Some(*annotation)
}
parser::Property::Unknown(UnknownProperty { ident, value }) => {
s.unknown_props.push((ident.clone(), value.clone()));
}
_ => return Err(Self::Error::IncompatibleProperty),
}
}
Ok(s) Ok(s)
} }
None => Err(ConversionError::IncompatibleNodeType), None => Err(MoveNodeError::IncompatibleProperty),
} }
} }
} }
@ -339,20 +239,12 @@ fn parse_position(s: &str) -> Result<Position, ConversionError> {
pub struct SetupNode { pub struct SetupNode {
id: Uuid, id: Uuid,
positions: Vec<(Option<Color>, String)>, positions: Vec<parser::SetupInstr>,
children: Vec<GameNode>, children: Vec<GameNode>,
} }
impl SetupNode { impl SetupNode {
pub fn new(positions: Vec<(Option<Color>, String)>) -> Result<Self, SetupError> { pub fn new(positions: Vec<parser::SetupInstr>) -> Result<Self, SetupNodeError> {
let mut coords: HashSet<String> = HashSet::new();
for coord in positions.iter().map(|p| p.1.clone()) {
if coords.contains(&coord) {
return Err(SetupError::ConflictingPosition);
}
coords.insert(coord);
}
Ok(Self { Ok(Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
positions, positions,
@ -371,6 +263,17 @@ impl Node for SetupNode {
} }
} }
impl TryFrom<&parser::Node> for SetupNode {
type Error = SetupNodeError;
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
match n.setup() {
Some(elements) => Self::new(elements),
None => Err(Self::Error::IncompatibleProperty),
}
}
}
pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> { pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> {
if node.id() == id { if node.id() == id {
return vec![node]; return vec![node];
@ -452,6 +355,8 @@ mod root_node_tests {
#[cfg(test)] #[cfg(test)]
mod move_node_tests { mod move_node_tests {
use crate::parser::PositionList;
use super::*; use super::*;
use cool_asserts::assert_matches; use cool_asserts::assert_matches;
@ -476,12 +381,28 @@ mod move_node_tests {
#[test] #[test]
fn it_rejects_an_sgf_setup_node() { fn it_rejects_an_sgf_setup_node() {
unimplemented!() let n = parser::Node {
properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())),
parser::Property::TimeLeft((Color::White, Duration::from_secs(176))),
parser::Property::SetupBlackStones(PositionList(vec![
"dd".to_owned(),
"de".to_owned(),
])),
],
next: vec![],
};
assert_eq!(
MoveNode::try_from(&n),
Err(MoveNodeError::IncompatibleProperty)
);
} }
} }
#[cfg(test)] #[cfg(test)]
mod setup_node_tests { mod setup_node_tests {
use crate::parser::SetupInstr;
use super::*; use super::*;
use cool_asserts::assert_matches; use cool_asserts::assert_matches;
@ -493,18 +414,18 @@ mod setup_node_tests {
fn it_rejects_conflicting_placement_properties() { fn it_rejects_conflicting_placement_properties() {
assert_matches!( assert_matches!(
SetupNode::new(vec![ SetupNode::new(vec![
(Some(Color::Black), "dd".to_owned(),), SetupInstr::Piece((Color::Black, "dd".to_owned())),
(Some(Color::Black), "dd".to_owned(),), SetupInstr::Piece((Color::Black, "dd".to_owned())),
]), ]),
Err(SetupError::ConflictingPosition) Err(SetupNodeError::ConflictingPosition)
); );
assert_matches!( assert_matches!(
SetupNode::new(vec![ SetupNode::new(vec![
(Some(Color::Black), "dd".to_owned(),), SetupInstr::Piece((Color::Black, "dd".to_owned())),
(Some(Color::Black), "ee".to_owned(),), SetupInstr::Piece((Color::Black, "ee".to_owned())),
(Some(Color::White), "ee".to_owned(),), SetupInstr::Piece((Color::White, "ee".to_owned())),
]), ]),
Err(SetupError::ConflictingPosition) Err(SetupNodeError::ConflictingPosition)
); );
} }
} }

View File

@ -28,12 +28,28 @@ impl From<ParseIntError> for ParseSizeError {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Double { pub enum Double {
Normal, Normal,
Emphasized, Emphasized,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Annotation {
BadMove,
DoubtfulMove,
InterestingMove,
Tesuji,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Evaluation {
Even,
GoodForBlack,
GoodForWhite,
Unclear,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum GameType { pub enum GameType {
Go, Go,
@ -209,13 +225,13 @@ impl TryFrom<&str> for Size {
pub struct Position(String); pub struct Position(String);
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PositionList(Vec<Position>); pub struct PositionList(pub Vec<String>);
impl PositionList { impl PositionList {
pub fn compressed_list(&self) -> String { pub fn compressed_list(&self) -> String {
self.0 self.0
.iter() .iter()
.map(|v| v.0.clone()) .map(|v| v.clone())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(":") .join(":")
} }
@ -238,26 +254,59 @@ pub struct Node {
pub next: Vec<Node>, pub next: Vec<Node>,
} }
#[derive(Clone, Debug, PartialEq)]
pub enum SetupInstr {
Piece((Color, String)),
Clear(String),
}
impl Node { impl Node {
pub fn move_(&self) -> Option<(Color, String)> { pub fn mv(&self) -> Option<(Color, String)> {
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,
}) })
} }
pub fn time_left(&self) -> Option<Duration> { pub fn setup(&self) -> Option<Vec<SetupInstr>> {
self.find_by(|prop| match prop { let mut setup = Vec::new();
Property::TimeLeft((_, duration)) => Some(duration.clone()), for prop in self.properties.iter() {
_ => None, match prop {
}) Property::SetupBlackStones(positions) => {
setup.append(
&mut positions
.0
.iter()
.map(|pos| SetupInstr::Piece((Color::Black, pos.clone())))
.collect::<Vec<SetupInstr>>(),
);
}
Property::SetupWhiteStones(positions) => {
setup.append(
&mut positions
.0
.iter()
.map(|pos| SetupInstr::Piece((Color::White, pos.clone())))
.collect::<Vec<SetupInstr>>(),
);
}
Property::ClearStones(positions) => {
setup.append(
&mut positions
.0
.iter()
.map(|pos| SetupInstr::Clear(pos.clone()))
.collect::<Vec<SetupInstr>>(),
);
}
_ => unimplemented!(),
}
}
if setup.len() > 0 {
Some(setup)
} else {
None
} }
pub fn comments(&self) -> Option<String> {
self.find_by(|prop| match prop {
Property::Comment(c) => Some(c.clone()),
_ => None,
})
} }
fn find_by<F, R>(&self, f: F) -> Option<R> fn find_by<F, R>(&self, f: F) -> Option<R>
@ -318,17 +367,8 @@ pub enum Property {
// C // C
Comment(String), Comment(String),
// BM // BM, DO, IT, TE
BadMove, Annotation(Annotation),
// DO
DoubtfulMove,
// IT
InterestingMove,
// TE
Tesuji,
// AP // AP
Application(String), Application(String),
@ -360,17 +400,8 @@ pub enum Property {
// PL // PL
NextPlayer(Color), NextPlayer(Color),
// DM // DM, GB, GW, UC
EvenResult, Evaluation(Evaluation),
// GB
GoodForBlack,
// GW
GoodForWhite,
// UC
UnclearResult,
// HO // HO
Hotspot, Hotspot,
@ -449,8 +480,8 @@ pub enum Property {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct UnknownProperty { pub struct UnknownProperty {
ident: String, pub ident: String,
value: String, pub value: String,
} }
impl ToString for Property { impl ToString for Property {
@ -463,10 +494,10 @@ impl ToString for Property {
format!("{}[{}]", color.abbreviation(), time.as_secs()) format!("{}[{}]", color.abbreviation(), time.as_secs())
} }
Property::Comment(value) => format!("C[{}]", value), Property::Comment(value) => format!("C[{}]", value),
Property::BadMove => "BM[]".to_owned(), Property::Annotation(Annotation::BadMove) => "BM[]".to_owned(),
Property::DoubtfulMove => "DO[]".to_owned(), Property::Annotation(Annotation::DoubtfulMove) => "DO[]".to_owned(),
Property::InterestingMove => "IT[]".to_owned(), Property::Annotation(Annotation::InterestingMove) => "IT[]".to_owned(),
Property::Tesuji => "TE[]".to_owned(), Property::Annotation(Annotation::Tesuji) => "TE[]".to_owned(),
Property::Application(app) => format!("AP[{}]", app), Property::Application(app) => format!("AP[{}]", app),
Property::Charset(set) => format!("CA[{}]", set), Property::Charset(set) => format!("CA[{}]", set),
Property::FileFormat(ff) => format!("FF[{}]", ff), Property::FileFormat(ff) => format!("FF[{}]", ff),
@ -489,10 +520,10 @@ impl ToString for Property {
format!("AW[{}]", positions.compressed_list(),) format!("AW[{}]", positions.compressed_list(),)
} }
Property::NextPlayer(color) => format!("PL[{}]", color.abbreviation()), Property::NextPlayer(color) => format!("PL[{}]", color.abbreviation()),
Property::EvenResult => "DM[]".to_owned(), Property::Evaluation(Evaluation::Even) => "DM[]".to_owned(),
Property::GoodForBlack => "GB[]".to_owned(), Property::Evaluation(Evaluation::GoodForBlack) => "GB[]".to_owned(),
Property::GoodForWhite => "GW[]".to_owned(), Property::Evaluation(Evaluation::GoodForWhite) => "GW[]".to_owned(),
Property::UnclearResult => "UC[]".to_owned(), Property::Evaluation(Evaluation::Unclear) => "UC[]".to_owned(),
Property::Hotspot => "HO[]".to_owned(), Property::Hotspot => "HO[]".to_owned(),
Property::Value(value) => format!("V[{}]", value), Property::Value(value) => format!("V[{}]", value),
Property::Annotator(value) => format!("AN[{}]", value), Property::Annotator(value) => format!("AN[{}]", value),
@ -574,14 +605,18 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
"C" => parse_propval(parse_comment())(input)?, "C" => parse_propval(parse_comment())(input)?,
"WL" => parse_propval(parse_time_left(Color::White))(input)?, "WL" => parse_propval(parse_time_left(Color::White))(input)?,
"BL" => parse_propval(parse_time_left(Color::Black))(input)?, "BL" => parse_propval(parse_time_left(Color::Black))(input)?,
"BM" => discard_propval().map(|_| Property::BadMove).parse(input)?, "BM" => discard_propval()
.map(|_| Property::Annotation(Annotation::BadMove))
.parse(input)?,
"DO" => discard_propval() "DO" => discard_propval()
.map(|_| Property::DoubtfulMove) .map(|_| Property::Annotation(Annotation::DoubtfulMove))
.parse(input)?, .parse(input)?,
"IT" => discard_propval() "IT" => discard_propval()
.map(|_| Property::InterestingMove) .map(|_| Property::Annotation(Annotation::InterestingMove))
.parse(input)?,
"TE" => discard_propval()
.map(|_| Property::Annotation(Annotation::Tesuji))
.parse(input)?, .parse(input)?,
"TE" => discard_propval().map(|_| Property::Tesuji).parse(input)?,
"AP" => parse_propval(parse_simple_text().map(Property::Application))(input)?, "AP" => parse_propval(parse_simple_text().map(Property::Application))(input)?,
"CA" => parse_propval(parse_simple_text().map(Property::Charset))(input)?, "CA" => parse_propval(parse_simple_text().map(Property::Charset))(input)?,
"FF" => parse_propval(parse_number().map(Property::FileFormat))(input)?, "FF" => parse_propval(parse_number().map(Property::FileFormat))(input)?,
@ -589,16 +624,16 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
"ST" => unimplemented!(), "ST" => unimplemented!(),
"SZ" => unimplemented!(), "SZ" => unimplemented!(),
"DM" => discard_propval() "DM" => discard_propval()
.map(|_| Property::EvenResult) .map(|_| Property::Evaluation(Evaluation::Even))
.parse(input)?, .parse(input)?,
"GB" => discard_propval() "GB" => discard_propval()
.map(|_| Property::GoodForBlack) .map(|_| Property::Evaluation(Evaluation::GoodForBlack))
.parse(input)?, .parse(input)?,
"GW" => discard_propval() "GW" => discard_propval()
.map(|_| Property::GoodForWhite) .map(|_| Property::Evaluation(Evaluation::GoodForWhite))
.parse(input)?, .parse(input)?,
"UC" => discard_propval() "UC" => discard_propval()
.map(|_| Property::UnclearResult) .map(|_| Property::Evaluation(Evaluation::Unclear))
.parse(input)?, .parse(input)?,
"V" => unimplemented!(), "V" => unimplemented!(),
"AN" => parse_propval(parse_simple_text().map(Property::Annotator))(input)?, "AN" => parse_propval(parse_simple_text().map(Property::Annotator))(input)?,