diff --git a/Cargo.lock b/Cargo.lock index 7137afc..fc70de2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2309,6 +2309,7 @@ dependencies = [ "serde", "thiserror", "typeshare", + "uuid 1.4.1", ] [[package]] @@ -2726,6 +2727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", + "serde", ] [[package]] diff --git a/sgf/Cargo.toml b/sgf/Cargo.toml index 2dbd6bf..a3d27bf 100644 --- a/sgf/Cargo.toml +++ b/sgf/Cargo.toml @@ -11,6 +11,7 @@ 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 new file mode 100644 index 0000000..4579ed4 --- /dev/null +++ b/sgf/src/game.rs @@ -0,0 +1,272 @@ +use crate::{Color, Position}; +use uuid::Uuid; + +pub trait Node { + fn path_to_node<'a>(&'a self, id: Uuid) -> Vec<&'a GameNode>; + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a GameNode; + fn add_annotation(&mut self, label: String, note: String); +} + +pub enum GameNode { + MoveNode(MoveNode), + SetupNode(SetupNode), +} + +impl GameNode { + fn id(&self) -> Uuid { + match self { + GameNode::MoveNode(node) => node.id, + GameNode::SetupNode(node) => node.id, + } + } + + fn children<'a>(&'a self) -> Vec<&'a GameNode> { + unimplemented!("GameNode::children") + } +} + +impl Node for GameNode { + fn path_to_node<'a>(&'a self, id: Uuid) -> Vec<&'a GameNode> { + unimplemented!() + } + + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a GameNode { + unimplemented!() + } + + fn add_annotation(&mut self, label: String, note: String) { + unimplemented!() + } +} + +// Root node +pub struct GameTree { + children: Vec, +} + +impl GameTree { + fn new() -> Self { + Self { children: vec![] } + } + + fn nodes<'a>(&'a self) -> impl Iterator { + vec![].into_iter() + } + + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a GameNode { + // self.children.push(node); + unimplemented!() + } +} + +impl Node for GameTree { + fn path_to_node<'a>(&'a self, id: Uuid) -> Vec<&'a GameNode> { + unimplemented!() + } + + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a GameNode { + unimplemented!() + } + + fn add_annotation(&mut self, label: String, note: String) { + unimplemented!() + } +} + +pub struct MoveNode { + id: Uuid, + + color: Color, + position: Position, + annotations: Vec<(String, String)>, + children: Vec, +} + +impl MoveNode { + pub fn new(color: Color, position: Position) -> Self { + Self { + id: Uuid::new_v4(), + + color, + position, + annotations: Vec::new(), + children: Vec::new(), + } + } +} + +impl Node for MoveNode { + fn path_to_node<'a>(&'a self, id: Uuid) -> Vec<&'a GameNode> { + unimplemented!() + } + + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a GameNode { + unimplemented!() + } + + fn add_annotation(&mut self, label: String, note: String) { + unimplemented!() + } +} + +pub struct SetupNode { + id: Uuid, + + positions: Vec<(Color, Position)>, + annotations: Vec<(String, String)>, + children: Vec, +} + +impl SetupNode { + pub fn new(positions: Vec<(Color, Position)>) -> Self { + Self { + id: Uuid::new_v4(), + positions, + annotations: Vec::new(), + children: Vec::new(), + } + } +} + +impl Node for SetupNode { + fn path_to_node<'a>(&'a self, id: Uuid) -> Vec<&'a GameNode> { + unimplemented!() + } + + fn add_child<'a>(&'a mut self, node: GameNode) -> &'a 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> { + if node.id() == id { + return vec![node]; + } + + for child in node.children() { + let mut path = path_to_node(child, id); + if path.len() > 1 { + path.push(child); + return path; + } + } + + Vec::new() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn it_can_create_an_empty_game_tree() { + let tree = GameTree::new(); + assert_eq!(tree.nodes().count(), 0); + } + + #[test] + fn it_can_add_moves_to_a_game() { + let mut tree = GameTree::new(); + + let node = MoveNode::new(Color::Black, Position {}); + let mut node = tree.add_child(GameNode::MoveNode(node)); + let new_node = MoveNode::new(Color::White, Position {}); + node.add_child(new_node); + } + + #[test] + fn it_can_set_up_a_game() { + unimplemented!() + } + + #[test] + fn it_can_load_tree_from_sgf() { + unimplemented!() + } +} + +#[cfg(test)] +mod root_node_tests { + #[test] + fn it_rejects_move_properties() { + unimplemented!() + } + + #[test] + fn it_rejects_setup_properties() { + unimplemented!() + } + + #[test] + fn it_can_parse_a_root_sgf() { + unimplemented!() + } +} + +#[cfg(test)] +mod move_node_tests { + #[test] + fn it_rejects_setup_properties() { + unimplemented!() + } + + #[test] + fn it_rejects_root_properties() { + unimplemented!() + } + + #[test] + fn it_can_parse_an_sgf_move_node() { + unimplemented!() + } + + #[test] + fn it_rejects_an_sgf_setup_node() { + unimplemented!() + } +} + +#[cfg(test)] +mod setup_node_tests { + #[test] + fn it_rejects_move_properties() { + unimplemented!() + } + + #[test] + fn it_rejects_root_properties() { + unimplemented!() + } + + #[test] + fn it_can_parse_an_sgf_setup_node() { + unimplemented!() + } + + #[test] + fn it_rejects_an_sgf_move_node() { + unimplemented!() + } +} + +#[cfg(test)] +mod path_test { + #[test] + fn returns_empty_list_if_no_game_nodes() { + unimplemented!() + } + + #[test] + fn returns_empty_list_if_node_not_found() { + unimplemented!() + } + + #[test] + fn path_excludes_root_node() { + unimplemented!() + } +} diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index bd2937c..b2aecb5 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -6,6 +6,8 @@ pub mod go; mod tree; use tree::parse_collection; +mod game; + use thiserror::Error; #[derive(Debug)] @@ -16,6 +18,13 @@ pub enum Error { InvalidSgf(VerboseNomError), } +pub enum Color { + Black, + White, +} + +pub struct Position {} + #[derive(Debug)] pub struct VerboseNomError(nom::error::VerboseError);