diff --git a/sgf/Cargo.toml b/sgf/Cargo.toml index a3d27bf..0b0445f 100644 --- a/sgf/Cargo.toml +++ b/sgf/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = { version = "0.4", features = [ "serde" ] } -nom = { version = "7" } -serde = { version = "1", features = [ "derive" ] } -thiserror = { version = "1"} -typeshare = { version = "1" } -uuid = { version = "1.4", features = ["v4", "serde"] } +cool_asserts = { version = "*" } +chrono = { version = "0.4", features = [ "serde" ] } +nom = { version = "7" } +serde = { version = "1", features = [ "derive" ] } +thiserror = { version = "1"} +typeshare = { version = "1" } +uuid = { version = "1.4", features = ["v4", "serde"] } [dev-dependencies] cool_asserts = { version = "2" } diff --git a/sgf/src/game.rs b/sgf/src/game.rs index afda856..c1f9d42 100644 --- a/sgf/src/game.rs +++ b/sgf/src/game.rs @@ -1,6 +1,90 @@ +use std::collections::HashSet; + use crate::{Color, Position}; use uuid::Uuid; +#[derive(Clone, Debug, PartialEq)] +pub enum PropertyError { + ConflictingProperty, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SetupError { + ConflictingPosition, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum NodeProperty { + Comment(String), + EvenResult(f64), + GoodForBlack(f64), + GoodForWhite(f64), + Hotspot(f64), + NodeName(String), + Unclear(f64), + Value(f64), +} + +#[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); + +impl MoveProperties { + pub fn add_property(&mut self, prop: MoveProperty) -> Result<(), PropertyError> { + match prop { + MoveProperty::NodeProperty(_) => {} + MoveProperty::TimingProperty(_) => {} + MoveProperty::MoveAnnotationProperty(_) => { + if contains_move_annotation_property(&self.0) { + return Err(PropertyError::ConflictingProperty); + } + self.0.push(prop); + } + } + return Ok(()); + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SetupProperty { + NodeProperty(NodeProperty), + AddBlack(Vec), + AddWhite(Vec), + ClearPoints(Vec), + PlayerTurn(Color), +} + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct SetupProperties(Vec); + +impl SetupProperties { + pub fn add_property(&mut self, prop: SetupProperty) { + unimplemented!() + } +} + #[derive(Clone, Debug, PartialEq)] pub enum GameNode { MoveNode(MoveNode), @@ -8,8 +92,6 @@ pub enum GameNode { } pub trait Node { - fn children<'a>(&'a self) -> Vec<&'a GameNode>; - /// Provide a pre-order traversal of all of the nodes in the game tree. fn nodes<'a>(&'a self) -> Vec<&'a GameNode> { self.children() @@ -24,8 +106,8 @@ pub trait Node { .collect::>() } + fn children<'a>(&'a self) -> Vec<&'a GameNode>; fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode; - fn add_annotation(&mut self, label: String, note: String); } impl GameNode { @@ -35,10 +117,6 @@ impl GameNode { GameNode::SetupNode(node) => node.id, } } - - fn children<'a>(&'a self) -> Vec<&'a GameNode> { - unimplemented!("GameNode::children") - } } impl Node for GameNode { @@ -62,10 +140,6 @@ impl Node for GameNode { GameNode::SetupNode(node) => node.add_child(new_node), } } - - fn add_annotation(&mut self, label: String, note: String) { - unimplemented!() - } } // Root node @@ -77,11 +151,6 @@ impl GameTree { fn new() -> Self { Self { children: vec![] } } - - fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode { - self.children.push(node); - self.children.last_mut().unwrap() - } } impl Node for GameTree { @@ -90,11 +159,8 @@ impl Node for GameTree { } fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode { - unimplemented!() - } - - fn add_annotation(&mut self, label: String, note: String) { - unimplemented!() + self.children.push(node); + self.children.last_mut().unwrap() } } @@ -104,7 +170,7 @@ pub struct MoveNode { color: Color, position: Position, - annotations: Vec<(String, String)>, + properties: MoveProperties, children: Vec, } @@ -115,10 +181,14 @@ impl MoveNode { color, position, - annotations: Vec::new(), + properties: MoveProperties::default(), children: Vec::new(), } } + + fn add_property(&mut self, prop: MoveProperty) -> Result<(), PropertyError> { + self.properties.add_property(prop) + } } impl Node for MoveNode { @@ -130,29 +200,31 @@ impl Node for MoveNode { self.children.push(node); self.children.last_mut().unwrap() } - - fn add_annotation(&mut self, label: String, note: String) { - unimplemented!() - } } #[derive(Clone, Debug, PartialEq)] pub struct SetupNode { id: Uuid, - positions: Vec<(Color, Position)>, - annotations: Vec<(String, String)>, + positions: Vec<(Option, Position)>, children: Vec, } impl SetupNode { - pub fn new(positions: Vec<(Color, Position)>) -> Self { - Self { + pub fn new(positions: Vec<(Option, Position)>) -> Result { + let mut coords: HashSet = 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 { id: Uuid::new_v4(), positions, - annotations: Vec::new(), children: Vec::new(), - } + }) } } @@ -164,10 +236,6 @@ impl Node for SetupNode { fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode { unimplemented!() } - - fn add_annotation(&mut self, label: String, note: String) { - unimplemented!() - } } pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> { @@ -200,9 +268,21 @@ mod test { fn it_can_add_moves_to_a_game() { let mut tree = GameTree::new(); - let first_move = MoveNode::new(Color::Black, Position {}); + let first_move = MoveNode::new( + Color::Black, + Position { + row: 'd', + column: 'd', + }, + ); let first_ = tree.add_child(GameNode::MoveNode(first_move.clone())); - let second_move = MoveNode::new(Color::White, Position {}); + let second_move = MoveNode::new( + Color::White, + Position { + row: 'q', + column: 'q', + }, + ); first_.add_child(GameNode::MoveNode(second_move.clone())); let nodes = tree.nodes(); @@ -222,6 +302,13 @@ mod test { } } +fn contains_move_annotation_property(lst: &Vec) -> bool { + lst.iter().any(|item| match item { + MoveProperty::MoveAnnotationProperty(_) => true, + _ => false, + }) +} + #[cfg(test)] mod root_node_tests { #[test] @@ -242,6 +329,9 @@ mod root_node_tests { #[cfg(test)] mod move_node_tests { + use super::*; + use cool_asserts::assert_matches; + #[test] fn it_rejects_setup_properties() { unimplemented!() @@ -258,22 +348,38 @@ mod move_node_tests { } #[test] - fn it_rejects_an_sgf_setup_node() { + fn it_rejects_an_sgf_setup_property() { unimplemented!() } + + #[test] + fn it_prevents_multiple_move_annotation_properties() { + let mut node = MoveNode::new( + Color::Black, + Position { + row: 'a', + column: 'b', + }, + ); + assert_matches!( + node.add_property(MoveProperty::MoveAnnotationProperty( + MoveAnnotationProperty::BadMove(5.), + )), + Ok(_) + ); + assert_matches!( + node.add_property(MoveProperty::MoveAnnotationProperty( + MoveAnnotationProperty::Tesuji(5.), + )), + Err(PropertyError::ConflictingProperty) + ); + } } #[cfg(test)] mod setup_node_tests { - #[test] - fn it_rejects_move_properties() { - unimplemented!() - } - - #[test] - fn it_rejects_root_properties() { - unimplemented!() - } + use super::*; + use cool_asserts::assert_matches; #[test] fn it_can_parse_an_sgf_setup_node() { @@ -281,8 +387,52 @@ mod setup_node_tests { } #[test] - fn it_rejects_an_sgf_move_node() { - unimplemented!() + fn it_rejects_conflicting_placement_properties() { + assert_matches!( + SetupNode::new(vec![ + ( + Some(Color::Black), + Position { + row: 'd', + column: 'd', + }, + ), + ( + Some(Color::Black), + Position { + row: 'd', + column: 'd', + }, + ), + ]), + Err(SetupError::ConflictingPosition) + ); + assert_matches!( + SetupNode::new(vec![ + ( + Some(Color::Black), + Position { + row: 'd', + column: 'd', + }, + ), + ( + Some(Color::Black), + Position { + row: 'e', + column: 'e', + }, + ), + ( + Some(Color::White), + Position { + row: 'e', + column: 'e', + }, + ), + ]), + Err(SetupError::ConflictingPosition) + ); } } diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index fd5a72d..1ea6d95 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -24,8 +24,11 @@ pub enum Color { White, } -#[derive(Clone, Debug, PartialEq)] -pub struct Position {} +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Position { + row: char, + column: char, +} #[derive(Debug)] pub struct VerboseNomError(nom::error::VerboseError);