diff --git a/Cargo.lock b/Cargo.lock index 60ca499..4118f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2503,6 +2503,16 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "nary_tree" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb86edb8951cb3852cbb33ef558650e9f18c9d2e7fd79a6849c984a3825719c7" +dependencies = [ + "slab", + "snowflake", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -2705,6 +2715,7 @@ dependencies = [ "config-derive", "cool_asserts", "grid", + "nary_tree", "serde 1.0.193", "serde_json", "sgf", @@ -3685,6 +3696,7 @@ version = "0.1.0" dependencies = [ "chrono", "cool_asserts", + "nary_tree", "nom", "serde 1.0.193", "thiserror", @@ -3766,6 +3778,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "snowflake" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1" + [[package]] name = "socket2" version = "0.4.10" diff --git a/Cargo.nix b/Cargo.nix index cb15d53..3778355 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -7917,6 +7917,28 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "nary_tree" = rec { + crateName = "nary_tree"; + version = "0.4.3"; + edition = "2021"; + sha256 = "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv"; + authors = [ + "Ian " + "David Cohen " + ]; + dependencies = [ + { + name = "slab"; + packageId = "slab"; + } + { + name = "snowflake"; + packageId = "snowflake"; + } + ]; + features = { + }; + }; "native-tls" = rec { crateName = "native-tls"; version = "0.2.11"; @@ -8551,6 +8573,10 @@ rec { name = "grid"; packageId = "grid"; } + { + name = "nary_tree"; + packageId = "nary_tree"; + } { name = "serde"; packageId = "serde 1.0.193"; @@ -11570,6 +11596,10 @@ rec { packageId = "chrono"; features = [ "serde" ]; } + { + name = "nary_tree"; + packageId = "nary_tree"; + } { name = "nom"; packageId = "nom"; @@ -11803,6 +11833,21 @@ rec { }; resolvedDefaultFeatures = [ "const_generics" "const_new" "union" ]; }; + "snowflake" = rec { + crateName = "snowflake"; + version = "1.3.0"; + edition = "2015"; + sha256 = "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817"; + authors = [ + "Steven Allen " + ]; + features = { + "serde" = [ "dep:serde" ]; + "serde_derive" = [ "dep:serde_derive" ]; + "serde_support" = [ "serde" "serde_derive" ]; + }; + resolvedDefaultFeatures = [ "default" ]; + }; "socket2 0.4.10" = rec { crateName = "socket2"; version = "0.4.10"; diff --git a/crate-hashes.json b/crate-hashes.json index 9b7b532..12766b9 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -223,6 +223,7 @@ "registry+https://github.com/rust-lang/crates.io-index#mio@0.8.10": "02gyaxvaia9zzi4drrw59k9s0j6pa5d1y2kv7iplwjipdqlhngcg", "registry+https://github.com/rust-lang/crates.io-index#modifier@0.1.0": "0n3fmgli1nsskl0whrfzm1gk0rmwwl6pw1q4nb9sqqmn5h8wkxa1", "registry+https://github.com/rust-lang/crates.io-index#multer@2.1.0": "1hjiphaypj3phqaj5igrzcia9xfmf4rr4ddigbh8zzb96k1bvb01", + "registry+https://github.com/rust-lang/crates.io-index#nary_tree@0.4.3": "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv", "registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.11": "0bmrlg0fmzxaycjpkgkchi93av07v2yf9k33gc12ca9gqdrn28h7", "registry+https://github.com/rust-lang/crates.io-index#nix@0.27.1": "0ly0kkmij5f0sqz35lx9czlbk6zpihb7yh1bsy4irzwfd2f4xc1f", "registry+https://github.com/rust-lang/crates.io-index#no-std-compat@0.4.1": "132vrf710zsdp40yp1z3kgc2ss8pi0z4gmihsz3y7hl4dpd56f5r", @@ -342,6 +343,7 @@ "registry+https://github.com/rust-lang/crates.io-index#siphasher@0.3.11": "03axamhmwsrmh0psdw3gf7c0zc4fyl5yjxfifz9qfka6yhkqid9q", "registry+https://github.com/rust-lang/crates.io-index#slab@0.4.9": "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg", "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.11.2": "0w79x38f7c0np7hqfmzrif9zmn0avjvvm31b166zdk9d1aad1k2d", + "registry+https://github.com/rust-lang/crates.io-index#snowflake@1.3.0": "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817", "registry+https://github.com/rust-lang/crates.io-index#socket2@0.4.10": "03ack54dxhgfifzsj14k7qa3r5c9wqy3v6mqhlim99cc03y1cycz", "registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.5": "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv", "registry+https://github.com/rust-lang/crates.io-index#spin@0.5.2": "0b84m6dbzrwf2kxylnw82d3dr8w06av7rfkr8s85fb5f43rwyqvf", @@ -469,4 +471,4 @@ "registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.31": "0gcfyrmlrhmsz16qxjp2qzr6vixyaw1p04zl28f08lxkvfz62h0w", "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.7.0": "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj", "registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54": "00kg24jh3zqa3i6rg6yksnb71bch9yi1casqydl00s7nw8pk7avk" -} \ No newline at end of file +} diff --git a/otg/core/Cargo.toml b/otg/core/Cargo.toml index dc8954d..42af9aa 100644 --- a/otg/core/Cargo.toml +++ b/otg/core/Cargo.toml @@ -14,6 +14,7 @@ sgf = { path = "../../sgf" } grid = { version = "0.9" } serde_json = { version = "1" } serde = { version = "1", features = [ "derive" ] } +nary_tree = { version = "0.4" } thiserror = { version = "1" } uuid = { version = "0.8", features = ["v4", "serde"] } diff --git a/otg/core/src/api.rs b/otg/core/src/api.rs index 0f5d914..153984b 100644 --- a/otg/core/src/api.rs +++ b/otg/core/src/api.rs @@ -81,7 +81,7 @@ impl From for Player { } */ -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub enum CoreResponse { Library(library::LibraryResponse), Settings(settings::SettingsResponse), diff --git a/otg/core/src/database.rs b/otg/core/src/database.rs index 035cd61..e144b57 100644 --- a/otg/core/src/database.rs +++ b/otg/core/src/database.rs @@ -42,7 +42,8 @@ impl Database { .unwrap(); match parse_sgf(&buffer) { Ok(sgfs) => { - let mut sgfs = sgfs.into_iter().flatten().collect::>(); + let mut sgfs = + sgfs.into_iter().flatten().collect::>(); games.append(&mut sgfs); } Err(err) => println!("Error parsing {:?}: {:?}", entry.path(), err), diff --git a/otg/core/src/lib.rs b/otg/core/src/lib.rs index 3168a04..5964ce9 100644 --- a/otg/core/src/lib.rs +++ b/otg/core/src/lib.rs @@ -29,5 +29,6 @@ pub mod library; pub mod settings; mod types; -pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size, Tree}; - +pub use types::{ + BoardError, Color, Config, ConfigOption, DepthTree, LibraryPath, Player, Rank, Size, +}; diff --git a/otg/core/src/library.rs b/otg/core/src/library.rs index a18d454..4fe5241 100644 --- a/otg/core/src/library.rs +++ b/otg/core/src/library.rs @@ -14,18 +14,18 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with On the Grid. If not, see . */ -use crate::{Core}; +use crate::Core; use serde::{Deserialize, Serialize}; use sgf::GameRecord; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum LibraryRequest { - ListGames + ListGames, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub enum LibraryResponse { - Games(Vec) + Games(Vec), } async fn handle_list_games(model: &Core) -> LibraryResponse { @@ -39,10 +39,8 @@ async fn handle_list_games(model: &Core) -> LibraryResponse { } } - pub async fn handle(model: &Core, request: LibraryRequest) -> LibraryResponse { match request { LibraryRequest::ListGames => handle_list_games(model).await, } } - diff --git a/otg/core/src/types.rs b/otg/core/src/types.rs index 09a0a0f..c89b55c 100644 --- a/otg/core/src/types.rs +++ b/otg/core/src/types.rs @@ -2,10 +2,11 @@ use crate::goban::{Coordinate, Goban}; use config::define_config; use config_derive::ConfigOption; use serde::{Deserialize, Serialize}; -use sgf::GameNode; -use std::{cell::RefCell, collections::VecDeque, fmt, path::PathBuf, time::Duration}; +use sgf::GameTree; +use std::{ + collections::{HashMap, VecDeque}, fmt, ops::Deref, path::PathBuf, time::Duration +}; use thiserror::Error; -use uuid::Uuid; define_config! { LibraryPath(LibraryPath), @@ -229,6 +230,7 @@ impl GameState { } } +/* // To properly generate a tree, I need to know how deep to go. Then I can backtrace. Each node // needs to have a depth. Given a tree, the depth of the node is just the distance from the root. // This seems obvious, but I had to write it to discover how important that fact was. @@ -238,19 +240,49 @@ impl GameState { pub struct Tree { nodes: Vec>, } +*/ -#[derive(Debug)] -pub struct Node { - pub id: usize, - node: T, - parent: Option, - depth: usize, - width: RefCell>, - children: Vec, +pub struct DepthTree(nary_tree::Tree); + +impl Deref for DepthTree { + type Target = nary_tree::Tree; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -impl Tree { - fn new(root: T) -> Self { +#[derive(Debug)] +pub struct SizeNode { + /// Use this to map back to the node in the original game tree. This way we know how to + /// correspond from a node in the review tree back to there. + #[allow(dead_code)] + game_node_id: nary_tree::NodeId, + + /// How deep into the tree is this node? + depth: usize, + + /// How far from the leftmost margin is this node? + width: usize, +} + +impl SizeNode { + pub fn position(&self) -> (usize, usize) { + (self.depth, self.width) + } +} + +impl DepthTree { + // My previous work to convert from a node tree to this tree-with-width dependend on the node tree + // being a recursive data structure. Now I need to find a way to convert a slab tree to this width + // tree. + // + // It all feels like a lot of custom weirdness. I shouldn't need a bunch of custom data structures, + // so I want to eliminate the "Tree" above and keep using the slab tree. I think I should be able + // to build these Node objects without needing a custom data structure. + fn new() -> Self { + Self(nary_tree::Tree::new()) + /* Tree { nodes: vec![Node { id: 0, @@ -261,8 +293,10 @@ impl Tree { children: vec![], }], } + */ } + /* pub fn node(&self, idx: usize) -> &T { &self.nodes[idx].node } @@ -286,12 +320,21 @@ impl Tree { parent.children.push(next_idx); next_idx } + */ pub fn max_depth(&self) -> usize { - self.nodes.iter().fold( - 0, - |max, node| if node.depth > max { node.depth } else { max }, - ) + self.0 + .root() + .unwrap() + .traverse_pre_order() + .fold(0, |max, node| { + println!("node depth: {}", node.data().depth); + if node.data().depth > max { + node.data().depth + } else { + max + } + }) } // Since I know the width of a node, now I want to figure out its placement in the larger @@ -309,7 +352,9 @@ impl Tree { // amounts to the position of the parent node. // // When drawing nodes, I don't know how to persist the level of indent. - pub fn position(&self, idx: usize) -> (usize, usize) { + + // unimplemented!() + /* let node = &self.nodes[idx]; match node.parent { Some(parent_idx) => { @@ -326,8 +371,9 @@ impl Tree { // Root nodes won't have a parent, so just put them in the first column None => (0, 0), } - } + */ + /* // Given a node, do a postorder traversal to figure out the width of the node based on all of // its children. This is equivalent to the widest of all of its children at all depths. // @@ -353,14 +399,100 @@ impl Tree { width } + */ - pub fn bfs_iter(&self) -> BFSIter { + pub fn bfs_iter(&self) -> BFSIter<'_, SizeNode> { let mut queue = VecDeque::new(); - queue.push_back(&self.nodes[0]); - BFSIter { tree: self, queue } + queue.push_back(self.0.root().unwrap()); + BFSIter { queue } } } +impl<'a> From<&'a GameTree> for DepthTree { + fn from(tree: &'a GameTree) -> Self { + // Like in the conversion from SGF to GameTree, I need to traverse the entire tree one node + // at a time, keeping track of node ids as we go. I'm going to go with a depth-first + // traversal. When generating each node, I think I want to generate all of the details of + // the node as we go. + let source_root_node = tree.root(); + match source_root_node { + Some(source_root_node) => { + // Do the real work + // The id_map indexes from the source tree to the destination tree. Reverse + // indexing is accomplished by looking at the node_id in a node in the destination + // tree. + let mut id_map: HashMap = HashMap::new(); + let mut tree = nary_tree::Tree::new(); + + let mut iter = source_root_node.traverse_pre_order(); + let _ = iter.next().unwrap(); // we already know that the first element to be + // returned is the root node, and that the root node + // already exists. Otherwise we wouldn't even be in + // this branch. + + let dest_root_id = tree.set_root(SizeNode { + game_node_id: source_root_node.node_id(), + depth: 0, + width: 0, + }); + + id_map.insert(source_root_node.node_id(), dest_root_id); + + for source_node in iter { + let dest_parent_id = id_map + .get(&source_node.parent().unwrap().node_id()) + .unwrap(); + + let mut dest_parent = tree.get_mut(*dest_parent_id).unwrap(); + + let new_depth_node = SizeNode { + game_node_id: source_node.node_id(), + depth: 1 + dest_parent.data().depth, + width: dest_parent.data().width, + }; + + let new_node_id = dest_parent.append(new_depth_node).node_id(); + + match tree + .get(new_node_id) + .unwrap() + .prev_sibling() + .map(|node| node.data().width) + { + None => {} + Some(previous_width) => { + let mut new_node = tree.get_mut(new_node_id).unwrap(); + new_node.data().width = previous_width + 1; + } + } + + /* + let new_node = tree.get_mut(*dest_parent_id).unwrap().append(new_depth_node); + let previous_node = new_node.prev_sibling(); + + match previous_node { + None => {} + } + */ + + /* + match dest_noderef.prev_sibling() { + None => {} + Some(mut node) => { dest_noderef.data().width = node.data().width + 1 } + } + */ + + id_map.insert(source_node.node_id(), new_node_id); + } + + Self(tree) + } + None => Self::new(), + } + } +} + +/* impl<'a> From<&'a GameNode> for Tree { fn from(root: &'a GameNode) -> Self { fn add_subtree(tree: &mut Tree, parent_idx: usize, node: &GameNode) { @@ -390,32 +522,31 @@ impl<'a> From<&'a GameNode> for Tree { tree } } +*/ pub struct BFSIter<'a, T> { - tree: &'a Tree, - queue: VecDeque<&'a Node>, + queue: VecDeque>, } impl<'a, T> Iterator for BFSIter<'a, T> { - type Item = &'a Node; + type Item = &'a T; fn next(&mut self) -> Option { let retval = self.queue.pop_front(); - if let Some(retval) = retval { + if let Some(ref retval) = retval { retval - .children - .iter() - .for_each(|idx| self.queue.push_back(&self.tree.nodes[*idx])); + .children() + .for_each(|noderef| self.queue.push_back(noderef)); } - retval + retval.map(|retval| retval.data()) } } #[cfg(test)] mod test { use super::*; - use cool_asserts::assert_matches; - use sgf::{Move, MoveNode}; + // use sgf::{GameRecord, GameTree, GameType, Move, MoveNode}; + use sgf::{GameNode, GameTree, Move, MoveNode}; #[test] fn current_player_changes_after_move() { @@ -474,116 +605,193 @@ mod test { // B G H // C I // D E F + fn branching_tree() -> GameTree { + let mut game_tree = GameTree::default(); + let node_a = game_tree.set_root(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))); + + let node_b = game_tree + .get_mut(node_a) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_c = game_tree + .get_mut(node_b) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_d = game_tree + .get_mut(node_c) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_e = game_tree + .get_mut(node_c) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_f = game_tree + .get_mut(node_c) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_g = game_tree + .get_mut(node_a) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let node_h = game_tree + .get_mut(node_a) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + let _ = game_tree + .get_mut(node_h) + .unwrap() + .append(GameNode::MoveNode(MoveNode::new( + sgf::Color::Black, + Move::Move("dp".to_owned()), + ))) + .node_id(); + + game_tree + } + #[test] fn it_can_calculate_depth_from_game_tree() { - let mut node_a = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_b = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_c = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_d = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_e = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_f = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_g = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_h = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_i = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - - node_c.children.push(GameNode::MoveNode(node_d)); - node_c.children.push(GameNode::MoveNode(node_e)); - node_c.children.push(GameNode::MoveNode(node_f)); - - node_b.children.push(GameNode::MoveNode(node_c)); - - node_h.children.push(GameNode::MoveNode(node_i)); - - node_a.children.push(GameNode::MoveNode(node_b)); - node_a.children.push(GameNode::MoveNode(node_g)); - node_a.children.push(GameNode::MoveNode(node_h)); - - let game_tree = GameNode::MoveNode(node_a); - - let tree = Tree::from(&game_tree); - + let game_tree = branching_tree(); + let tree = DepthTree::from(&game_tree); + assert_eq!( + game_tree.root().unwrap().traverse_pre_order().count(), + tree.0.root().unwrap().traverse_pre_order().count() + ); assert_eq!(tree.max_depth(), 3); } - // A - // B G H - // C I - // D E F #[test] fn it_calculates_horizontal_position_of_nodes() { - let mut node_a = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_b = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_c = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_d = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_e = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_f = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_g = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_h = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_i = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let game_tree = branching_tree(); + let tree = DepthTree::from(&game_tree); - node_c.children.push(GameNode::MoveNode(node_d)); - node_c.children.push(GameNode::MoveNode(node_e)); - node_c.children.push(GameNode::MoveNode(node_f)); + let node_a = tree.root().unwrap(); + assert_eq!(node_a.data().position(), (0, 0)); - node_b.children.push(GameNode::MoveNode(node_c)); + let node_b = node_a.first_child().unwrap(); + assert_eq!(node_b.data().position(), (1, 0)); + let node_g = node_b.next_sibling().unwrap(); + assert_eq!(node_g.data().position(), (1, 1)); + let node_h = node_g.next_sibling().unwrap(); + assert_eq!(node_h.data().position(), (1, 2)); - node_h.children.push(GameNode::MoveNode(node_i)); + let node_c = node_b.first_child().unwrap(); + assert_eq!(node_c.data().position(), (2, 0)); - node_a.children.push(GameNode::MoveNode(node_b)); - node_a.children.push(GameNode::MoveNode(node_g)); - node_a.children.push(GameNode::MoveNode(node_h)); + let node_d = node_c.first_child().unwrap(); + assert_eq!(node_d.data().position(), (3, 0)); - let game_tree = GameNode::MoveNode(node_a); + let node_i = node_h.first_child().unwrap(); + assert_eq!(node_i.data().position(), (2, 2)); - let tree = Tree::from(&game_tree); - - assert_eq!(tree.position(2), (2, 0)); - assert_eq!(tree.position(1), (1, 0)); - assert_eq!(tree.position(0), (0, 0)); - assert_eq!(tree.position(4), (3, 1)); - assert_eq!(tree.position(5), (3, 2)); - assert_eq!(tree.position(6), (1, 3)); - assert_eq!(tree.position(7), (1, 4)); + /* + assert_eq!(tree.position(test_tree.node_c), (2, 0)); + assert_eq!(tree.position(test_tree.node_b), (1, 0)); + assert_eq!(tree.position(test_tree.node_a), (0, 0)); + assert_eq!(tree.position(test_tree.node_d), (3, 1)); + assert_eq!(tree.position(test_tree.node_e), (3, 2)); + assert_eq!(tree.position(test_tree.node_f), (1, 3)); + assert_eq!(tree.position(test_tree.node_g), (1, 4)); + */ } + #[ignore] #[test] fn breadth_first_iter() { - let mut node_a = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_b = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_c = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_d = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_e = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_f = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_g = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let mut node_h = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - let node_i = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + /* + let mut node_a = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let mut node_b = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let mut node_c = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let node_d = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let node_e = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let node_f = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let node_g = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let mut node_h = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); + let node_i = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned())); - node_c.children.push(GameNode::MoveNode(node_d.clone())); - node_c.children.push(GameNode::MoveNode(node_e.clone())); - node_c.children.push(GameNode::MoveNode(node_f.clone())); + let game = GameRecord::new( + GameType::Go, + Size { + width: 19, + height: 19, + }, + Player { + name: Some("Black".to_owned()), + rank: None, + team: None, + }, + Player { + name: Some("White".to_owned()), + rank: None, + team: None, + }, + ); - node_b.children.push(GameNode::MoveNode(node_c.clone())); + node_c.children.push(GameNode::MoveNode(node_d.clone())); + node_c.children.push(GameNode::MoveNode(node_e.clone())); + node_c.children.push(GameNode::MoveNode(node_f.clone())); - node_h.children.push(GameNode::MoveNode(node_i.clone())); + node_b.children.push(GameNode::MoveNode(node_c.clone())); - node_a.children.push(GameNode::MoveNode(node_b.clone())); - node_a.children.push(GameNode::MoveNode(node_g.clone())); - node_a.children.push(GameNode::MoveNode(node_h.clone())); + node_h.children.push(GameNode::MoveNode(node_i.clone())); - let game_tree = GameNode::MoveNode(node_a.clone()); + node_a.children.push(GameNode::MoveNode(node_b.clone())); + node_a.children.push(GameNode::MoveNode(node_g.clone())); + node_a.children.push(GameNode::MoveNode(node_h.clone())); - let tree = Tree::from(&game_tree); + let game_tree = GameNode::MoveNode(node_a.clone()); - let mut iter = tree.bfs_iter(); + let tree = Tree::from(&game_tree); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_a.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_b.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_g.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_h.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_c.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_i.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_d.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_e.id)); - assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_f.id)); + let mut iter = tree.bfs_iter(); + + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_a.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_b.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_g.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_h.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_c.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_i.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_d.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_e.id)); + assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_f.id)); + */ } } diff --git a/otg/gtk/src/components/review_tree.rs b/otg/gtk/src/components/review_tree.rs index f759a34..c2297ff 100644 --- a/otg/gtk/src/components/review_tree.rs +++ b/otg/gtk/src/components/review_tree.rs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with On use cairo::Context; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use otg_core::Tree; +use otg_core::DepthTree; use sgf::GameRecord; use std::{cell::RefCell, rc::Rc}; use uuid::Uuid; @@ -28,7 +28,7 @@ const HEIGHT: i32 = 800; #[derive(Default)] pub struct ReviewTreePrivate { record: Rc>>, - tree: Rc>>>, + tree: Rc>>, } #[glib::object_subclass] @@ -50,7 +50,9 @@ impl ReviewTree { pub fn new(record: GameRecord) -> Self { let s: Self = Object::new(); - *s.imp().tree.borrow_mut() = Some(Tree::from(&record.children[0])); + // TODO: there can be more than one tree, especially in instructional files. Either unify + // them into a single tree in the GameTree, or draw all of them here. + *s.imp().tree.borrow_mut() = Some(DepthTree::from(&record.trees[0])); *s.imp().record.borrow_mut() = Some(record); s.set_width_request(WIDTH); @@ -67,7 +69,7 @@ impl ReviewTree { } pub fn redraw(&self, ctx: &Context, _width: i32, _height: i32) { - let tree: &Option> = &self.imp().tree.borrow(); + let tree: &Option = &self.imp().tree.borrow(); match tree { Some(ref tree) => { for node in tree.bfs_iter() { @@ -76,7 +78,7 @@ impl ReviewTree { // the parent? do I need to just make it more intrinsically a part of the position // code? ctx.set_source_rgb(0.7, 0.7, 0.7); - let (row, column) = tree.position(node.id); + let (row, column) = node.position(); let y = (row as f64) * 20. + 10.; let x = (column as f64) * 20. + 10.; ctx.arc(x, y, 5., 0., 2. * std::f64::consts::PI); diff --git a/otg/gtk/src/views/game_review.rs b/otg/gtk/src/views/game_review.rs index 721c5d9..101c53c 100644 --- a/otg/gtk/src/views/game_review.rs +++ b/otg/gtk/src/views/game_review.rs @@ -55,9 +55,10 @@ impl GameReview { // It's actually really bad to be just throwing away errors. Panics make everyone unhappy. // This is not a fatal error, so I'll replace this `unwrap` call with something that // renders the board and notifies the user of a problem that cannot be resolved. - let board_repr = otg_core::Goban::default() - .apply_moves(record.mainline()) - .unwrap(); + let board_repr = match record.mainline() { + Some(iter) => otg_core::Goban::default().apply_moves(iter).unwrap(), + None => otg_core::Goban::default(), + }; let board = Goban::new(board_repr, resources); /* diff --git a/sgf/Cargo.toml b/sgf/Cargo.toml index 355516d..0b83c35 100644 --- a/sgf/Cargo.toml +++ b/sgf/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" chrono = { version = "0.4", features = [ "serde" ] } nom = { version = "7" } serde = { version = "1", features = [ "derive" ] } +nary_tree = { version = "0.4" } thiserror = { version = "1"} typeshare = { version = "1" } uuid = { version = "0.8", features = ["v4", "serde"] } diff --git a/sgf/src/game.rs b/sgf/src/game.rs index 4c68c60..d21812a 100644 --- a/sgf/src/game.rs +++ b/sgf/src/game.rs @@ -3,7 +3,14 @@ use crate::{ Color, Date, GameResult, GameType, }; use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, time::Duration}; +use nary_tree::{NodeId, NodeMut, NodeRef, Tree}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + fmt, + fmt::Debug, + ops::{Deref, DerefMut}, + time::Duration, +}; use uuid::Uuid; #[derive(Clone, Debug, PartialEq)] @@ -32,7 +39,7 @@ pub enum SetupNodeError { #[derive(Clone, Debug, PartialEq)] pub enum GameNodeError { - UnsupportedGameNode(MoveNodeError, SetupNodeError), + UnsupportedGameNode(MoveNodeError, SetupNodeError, parser::Node), ConflictingProperty, ConflictingPosition, } @@ -52,7 +59,7 @@ pub struct Player { /// syntax issues, the result of the GameRecord is to have a fully-understood game. However, this /// doesn't (yet?) go quite to the level of apply the game type (i.e., this is Go, Chess, Yinsh, or /// whatever). -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq)] pub struct GameRecord { pub game_type: GameType, @@ -78,7 +85,7 @@ pub struct GameRecord { pub overtime: Option, pub transcriber: Option, - pub children: Vec, + pub trees: Vec, } impl GameRecord { @@ -111,55 +118,40 @@ impl GameRecord { overtime: None, transcriber: None, - children: vec![], + trees: vec![], } } + pub fn nodes(&self) -> Vec<&GameNode> { + self.iter().collect() + } + + pub fn iter(&self) -> impl Iterator { + self.trees + .iter() + .flat_map(|tree| tree.root().unwrap().traverse_pre_order()) + .map(|nr| nr.data()) + } + /// Generate a list of moves which constitute the main line of the game. This is the game as it /// was actually played out, and by convention consists of the first node in each list of /// children. - pub fn mainline(&self) -> Vec<&GameNode> { - let mut moves: Vec<&GameNode> = vec![]; - - let mut next = self.children.first(); - while let Some(node) = next { - // Given that I know that I have a node, and I know that I'm going to push a reference - // to it onto my final list, I want to get the first of its children. And I want to - // keep doing that until there are no more first children. - // - // Just going to push references onto the list. No need to copy the nodes for this. - // - // Pushing a reference onto the list implicitely clones the reference, but not the data - // it is pointing to. This means that each time through the loop, `next` points to - // something else. This isn't being described very well, though, so it's worth - // reviewing in the future. - moves.push(node); - - next = match node { - GameNode::MoveNode(node) => node.children.first(), - GameNode::SetupNode(node) => node.children.first(), - }; + pub fn mainline(&self) -> Option> { + if !self.trees.is_empty() { + Some(MainlineIter { + next: self.trees[0].root(), + tree: &self.trees[0], + }) + } else { + None } - - moves } } -impl Node for GameRecord { - fn children<'a>(&'a self) -> Vec<&'a GameNode> { - self.children.iter().collect::>() - } - - fn add_child(&mut self, node: GameNode) -> &mut GameNode { - self.children.push(node); - self.children.last_mut().unwrap() - } -} - -impl TryFrom<&parser::Tree> for GameRecord { +impl TryFrom for GameRecord { type Error = GameError; - fn try_from(tree: &parser::Tree) -> Result { + fn try_from(tree: parser::Tree) -> Result { let mut ty = None; let mut size = None; let mut black_player = Player { @@ -234,6 +226,7 @@ impl TryFrom<&parser::Tree> for GameRecord { } } + /* s.children = tree .root .next @@ -241,33 +234,205 @@ impl TryFrom<&parser::Tree> for GameRecord { .map(GameNode::try_from) .collect::, GameNodeError>>() .map_err(GameError::InvalidGameNode)?; + */ + + s.trees = tree + .root + .next + .into_iter() + .map(recursive_tree_to_slab_tree) + .collect::, GameError>>()?; Ok(s) } } +fn recursive_tree_to_slab_tree(node: parser::Node) -> Result { + let mut slab = Tree::new(); + let mut nodes: VecDeque<(NodeId, parser::Node)> = VecDeque::new(); + + let root_id = + slab.set_root(GameNode::try_from(node.clone()).map_err(GameError::InvalidGameNode)?); + nodes.push_back((root_id, node)); + + // I need to keep track of the current parent, and I need to keep on digging deeper into the + // tree. Given that I have the root, I can then easily find out all of the children. + // + // So, maybe I take the list of children. Assign each one of them to a place in the slab tree. + // Then push the child *and* its ID into a dequeue. So long as the dequeue is not empty, I want + // to pop a node and its ID from the dequeue. The retrieve the NodeMut for it and work on the + // node's children. + while let Some((node_id, node)) = nodes.pop_front() { + let mut game_node: NodeMut = slab + .get_mut(node_id) + .expect("invalid node_id when retrieving nodes from the game"); + // I have a node that is in the tree. Now run across all of its children, adding each one + // to the tree and pushing them into the deque along with their IDs. + for child in node.next { + let slab_child = game_node + .append(GameNode::try_from(child.clone()).map_err(GameError::InvalidGameNode)?); + nodes.push_back((slab_child.node_id(), child)); + } + } + + Ok(GameTree(slab)) +} + +#[derive(Default)] +pub struct TreeIter<'a> { + queue: VecDeque>, +} + +/* +impl<'a> Default for TreeIter<'a> { + fn default() -> Self { + TreeIter { + queue: VecDeque::default(), + } + } +} +*/ + +impl<'a> Iterator for TreeIter<'a> { + type Item = &'a GameNode; + + fn next(&mut self) -> Option { + let retval = self.queue.pop_front(); + if let Some(ref retval) = retval { + retval + .children() + .for_each(|node| self.queue.push_back(node)); + } + retval.map(|rv| *rv.data()) + } +} + +pub struct GameTree(Tree); + +impl Default for GameTree { + fn default() -> Self { + Self(Tree::new()) + } +} + +impl Clone for GameTree { + fn clone(&self) -> Self { + match self.0.root() { + None => Self(Tree::new()), + Some(source_root_node) => { + let mut dest = Tree::new(); + let dest_root_id = dest.set_root(source_root_node.data().clone()); + + // In order to add a node to the new tree, I need to know the ID of the parent in + // the source tree and the ID of the parent in the destination tree. So I want a + // lookup table that maps source IDs to destination IDs. But is that sufficient? + // Perhaps I can just keep a mapping from a source noderef to a destination ID. + // I don't think I can keep more than one mutable destination node. + + let mut mapping: HashMap = HashMap::new(); + mapping.insert(source_root_node.node_id(), dest_root_id); + + for source_node in source_root_node.traverse_level_order() { + match source_node.parent() { + None => {} + Some(parent) => { + let source_node_parent_id = parent.node_id(); + let target_node_parent_id = mapping.get(&source_node_parent_id).expect("node should have been added to the source to dest mapping when being cloned"); + + let mut parent = dest.get_mut(*target_node_parent_id).expect( + "destination parent node to exist before reaching potential children", + ); + let dest_id = parent.append(source_node.data().clone()).node_id(); + mapping.insert(source_node.node_id(), dest_id); + } + } + } + + Self(dest) + } + } + } +} + +impl Debug for GameTree { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.write_formatted(f) + } +} + +impl Deref for GameTree { + type Target = Tree; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for GameTree { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl PartialEq for GameTree { + fn eq(&self, other: &Self) -> bool { + // Get pre-order iterators over both trees, zip them, and ensure that the data contents are + // the same between them + let left_root = self.root(); + let right_root = other.root(); + + match (left_root, right_root) { + (Some(left_root), Some(right_root)) => { + for (left_node, right_node) in std::iter::zip( + left_root.traverse_pre_order(), + right_root.traverse_pre_order(), + ) { + if left_node.data() != right_node.data() { + return false; + } + } + } + (None, None) => return true, + _ => return false, + } + true + } +} + +pub struct MainlineIter<'a> { + next: Option>, + tree: &'a Tree, +} + +impl<'a> Iterator for MainlineIter<'a> { + type Item = &'a GameNode; + + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + let ret = self.tree.get(next.node_id())?; + self.next = next + .first_child() + .and_then(|child| self.tree.get(child.node_id())); + Some(ret.data()) + } else { + None + } + } +} + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum GameNode { MoveNode(MoveNode), SetupNode(SetupNode), } -pub trait Node { - /// Provide a pre-order traversal of all of the nodes in the game tree. - fn nodes<'a>(&'a self) -> Vec<&'a GameNode> { - self.children() - .iter() - .flat_map(|node| { - let mut children = node.nodes(); - let mut v = vec![*node]; - v.append(&mut children); - v - }) - .collect::>() +impl fmt::Display for GameNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + GameNode::MoveNode(_) => write!(f, "MoveNode"), + GameNode::SetupNode(_) => write!(f, "SetupNode"), + } } - - fn children(&self) -> Vec<&GameNode>; - fn add_child(&mut self, node: GameNode) -> &mut GameNode; } impl GameNode { @@ -279,70 +444,20 @@ impl GameNode { } } -impl Node for GameNode { - fn children(&self) -> Vec<&GameNode> { - match self { - GameNode::MoveNode(node) => node.children(), - GameNode::SetupNode(node) => node.children(), - } - } - - fn nodes(&self) -> Vec<&GameNode> { - match self { - GameNode::MoveNode(node) => node.nodes(), - GameNode::SetupNode(node) => node.nodes(), - } - } - - fn add_child(&mut self, new_node: GameNode) -> &mut GameNode { - match self { - GameNode::MoveNode(node) => node.add_child(new_node), - GameNode::SetupNode(node) => node.add_child(new_node), - } - } -} - -impl TryFrom<&parser::Node> for GameNode { +impl TryFrom for GameNode { type Error = GameNodeError; - fn try_from(n: &parser::Node) -> Result { - // I originally wrote this recursively. However, on an ordinary game of a couple hundred - // moves, that meant that I was recursing 500 functions, and that exceeded the stack limit. - // So, instead, I need to unroll everything to non-recursive form. - // - // So, I can treat each branch of the tree as a single line. Iterate over that line. I can - // only use the MoveNode::try_from and SetupNode::try_from if those functions don't - // recurse. Instead, I'm going to process just that node, then return to here and process - // the children. - let move_node = MoveNode::try_from(n); - let setup_node = SetupNode::try_from(n); + fn try_from(n: parser::Node) -> Result { + let move_node = MoveNode::try_from(n.clone()); + let setup_node = SetupNode::try_from(n.clone()); - // I'm much too tired when writing this. I'm still recursing, but I did cut the number of - // recursions in half. This helps, but it still doesn't guarantee that I'm going to be able - // to parse all possible games. So, still, treat each branch of the game as a single line. - // Iterate over that line, don't recurse. Create bookmarks at each branch point, and then - // come back to each one. - let children = n - .next - .iter() - .map(GameNode::try_from) - .collect::, Self::Error>>()?; - - let node = match (move_node, setup_node) { - (Ok(mut node), _) => { - node.children = children; - Ok(Self::MoveNode(node)) - } - (Err(_), Ok(mut node)) => { - node.children = children; - Ok(Self::SetupNode(node)) - } + match (move_node, setup_node) { + (Ok(node), _) => Ok(Self::MoveNode(node)), + (Err(_), Ok(node)) => Ok(Self::SetupNode(node)), (Err(move_err), Err(setup_err)) => { - Err(Self::Error::UnsupportedGameNode(move_err, setup_err)) + Err(Self::Error::UnsupportedGameNode(move_err, setup_err, n)) } - }?; - - Ok(node) + } } } @@ -351,7 +466,6 @@ pub struct MoveNode { pub id: Uuid, pub color: Color, pub mv: Move, - pub children: Vec, pub time_left: Option, pub moves_left: Option, @@ -369,7 +483,6 @@ impl MoveNode { id: Uuid::new_v4(), color, mv, - children: Vec::new(), time_left: None, moves_left: None, @@ -383,21 +496,10 @@ impl MoveNode { } } -impl Node for MoveNode { - fn children<'a>(&'a self) -> Vec<&'a GameNode> { - self.children.iter().collect::>() - } - - fn add_child(&mut self, node: GameNode) -> &mut GameNode { - self.children.push(node); - self.children.last_mut().unwrap() - } -} - -impl TryFrom<&parser::Node> for MoveNode { +impl TryFrom for MoveNode { type Error = MoveNodeError; - fn try_from(n: &parser::Node) -> Result { + fn try_from(n: parser::Node) -> Result { let s = match n.mv() { Some((color, mv)) => { let mut s = Self::new(color, mv); @@ -460,7 +562,6 @@ pub struct SetupNode { id: Uuid, pub positions: Vec, - pub children: Vec, } impl SetupNode { @@ -480,26 +581,14 @@ impl SetupNode { Ok(Self { id: Uuid::new_v4(), positions, - children: Vec::new(), }) } } -impl Node for SetupNode { - fn children<'a>(&'a self) -> Vec<&'a GameNode> { - self.children.iter().collect::>() - } - - #[allow(dead_code)] - fn add_child(&mut self, _node: GameNode) -> &mut GameNode { - unimplemented!() - } -} - -impl TryFrom<&parser::Node> for SetupNode { +impl TryFrom for SetupNode { type Error = SetupNodeError; - fn try_from(n: &parser::Node) -> Result { + fn try_from(n: parser::Node) -> Result { match n.setup() { Some(elements) => Self::new(elements), None => Err(Self::Error::NotASetupNode), @@ -507,6 +596,7 @@ impl TryFrom<&parser::Node> for SetupNode { } } +/* #[allow(dead_code)] pub fn path_to_node(node: &GameNode, id: Uuid) -> Vec<&GameNode> { if node.id() == id { @@ -523,6 +613,7 @@ pub fn path_to_node(node: &GameNode, id: Uuid) -> Vec<&GameNode> { Vec::new() } +*/ #[cfg(test)] mod test { @@ -555,15 +646,19 @@ mod test { Player::default(), ); + /* 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, Move::Move("qq".to_owned())); first_.add_child(GameNode::MoveNode(second_move.clone())); + */ + /* let nodes = game.nodes(); assert_eq!(nodes.len(), 2); assert_eq!(nodes[0].id(), first_move.id); assert_eq!(nodes[1].id(), second_move.id); + */ } #[ignore] @@ -588,7 +683,7 @@ mod test { ], next: vec![], }; - assert_matches!(GameNode::try_from(&n), Ok(GameNode::MoveNode(_))); + assert_matches!(GameNode::try_from(n), Ok(GameNode::MoveNode(_))); } } @@ -630,10 +725,10 @@ mod move_node_tests { ], next: vec![], }; - 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.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.comments, Some("Comments in the game".to_owned())); }); @@ -653,7 +748,7 @@ mod move_node_tests { next: vec![], }; assert_matches!( - MoveNode::try_from(&n), + MoveNode::try_from(n), Err(MoveNodeError::IncompatibleProperty(_)) ); } @@ -703,7 +798,7 @@ mod path_test { let (_, games) = parse_collection::>(text).unwrap(); let games = games .into_iter() - .map(|game| GameRecord::try_from(&game).expect("game to parse")) + .map(|game| GameRecord::try_from(game).expect("game to parse")) .collect::>(); f(games); } @@ -722,7 +817,10 @@ mod path_test { |games| { let game = &games[0]; - let moves = game.mainline(); + let moves = game + .mainline() + .expect("there should be a mainline in this file") + .collect::>(); assert_matches!(moves[0], GameNode::MoveNode(node) => { assert_eq!(node.color, Color::Black); assert_eq!(node.mv, Move::Move("pp".to_owned())); @@ -744,7 +842,10 @@ mod path_test { with_file(std::path::Path::new("test_data/branch_test.sgf"), |games| { let game = &games[0]; - let moves = game.mainline(); + let moves = game + .mainline() + .expect("there should be a mainline in this file") + .collect::>(); assert_matches!(moves[1], GameNode::MoveNode(node) => { assert_eq!(node.color, Color::White); assert_eq!(node.mv, Move::Move("dd".to_owned())); @@ -791,7 +892,7 @@ mod file_test { let (_, games) = parse_collection::>(text).unwrap(); let games = games .into_iter() - .map(|game| GameRecord::try_from(&game).expect("game to parse")) + .map(|game| GameRecord::try_from(game).expect("game to parse")) .collect::>(); f(games); } @@ -875,6 +976,7 @@ mod file_test { } */ + /* let children = game.children(); let node = children.first().unwrap(); assert_matches!(node, GameNode::MoveNode(node) => { @@ -892,6 +994,7 @@ mod file_test { assert_eq!(node.time_left, Some(Duration::from_secs(1765))); assert_eq!(node.comments, None); }); + */ /* let node = node.next().unwrap(); let expected_properties = vec![ @@ -911,4 +1014,39 @@ mod file_test { }, ); } + + #[test] + fn it_can_load_a_file_with_multiple_roots() { + with_file(std::path::Path::new("test_data/multi-tree.sgf"), |games| { + assert_eq!(games.len(), 1); + let game = &games[0]; + assert_eq!(game.game_type, GameType::Go); + assert_eq!( + game.board_size, + Size { + width: 19, + height: 19 + } + ); + assert_eq!(game.trees.len(), 2); + assert_matches!(game.trees[0].root().unwrap().data(), GameNode::MoveNode(node) => { + assert_eq!(node.color, Color::Black); + assert_eq!(node.mv, Move::Move("pd".to_owned())); + }); + assert_matches!(game.trees[1].root().unwrap().data(), GameNode::MoveNode(node) => { + assert_eq!(node.color, Color::Black); + assert_eq!(node.mv, Move::Move("pc".to_owned())); + }); + }); + } + + #[test] + fn it_can_copy_a_game_record() { + with_file(std::path::Path::new("test_data/multi-tree.sgf"), |games| { + let dest = games.clone(); + + assert_eq!(games.len(), dest.len()); + assert_eq!(games[0], dest[0]); + }); + } } diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index 6849499..9fc15ca 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -1,7 +1,7 @@ mod date; mod game; -pub use game::{GameNode, GameRecord, MoveNode, Player}; +pub use game::{GameNode, GameRecord, GameTree, MoveNode, Player}; mod parser; pub use parser::{parse_collection, Move}; @@ -22,6 +22,7 @@ pub enum Error { InvalidSgf(VerboseNomError), } +#[allow(dead_code)] #[derive(Debug)] pub struct VerboseNomError(nom::error::VerboseError); @@ -73,7 +74,7 @@ pub fn parse_sgf(input: &str) -> Result> let (_, games) = parse_collection::>(input)?; let games = games .into_iter() - .map(|game| GameRecord::try_from(&game)) + .map(GameRecord::try_from) .collect::>>(); Ok(games) diff --git a/sgf/src/types.rs b/sgf/src/types.rs index 3cf3d13..723164d 100644 --- a/sgf/src/types.rs +++ b/sgf/src/types.rs @@ -56,6 +56,7 @@ pub enum Error { InvalidSgf(VerboseNomError), } +#[allow(dead_code)] #[derive(Debug)] pub struct VerboseNomError(nom::error::VerboseError); diff --git a/sgf/test_data/multi-tree.sgf b/sgf/test_data/multi-tree.sgf new file mode 100644 index 0000000..3fb4dd2 --- /dev/null +++ b/sgf/test_data/multi-tree.sgf @@ -0,0 +1 @@ +(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[7.5]SZ[19]DT[2024-04-19](;B[pd](;W[qc];B[qd];W[pc];B[oc];W[ob];B[nc];W[nb];B[mc];W[rd];B[re];W[rc];B[qf])(;W[qf];B[nc];W[rd];B[qc];W[pi]))(;B[pc];W[qe];B[oe];W[pg];B[ld];W[qj])) \ No newline at end of file diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 4644876..337a97f 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -9,6 +9,10 @@ use std::{ rc::Rc, }; +// I need to take what I learned about linked lists and about the other Tree data structure, and +// apply it here with arena allocation. +// +// Also, smarter node allocation and pointer handling in order to avoid clones. #[derive(Clone, Debug, Default)] pub enum Tree { #[default] @@ -55,6 +59,16 @@ impl Tree { None } + // Do a depth-first-search in order to get the path to a node. Start with a naive recursive + // implementation, then switch to a stack-based implementation in order to avoid exceeding the + // stack. + pub fn path_to(&self, f: F) -> Vec> + where + F: FnOnce(&T) -> bool + Copy, + { + unimplemented!() + } + /// Convert each node of a tree from type T to type U pub fn map(&self, op: F) -> Tree where @@ -146,6 +160,13 @@ impl Node { } } +impl PartialEq for Node { + fn eq(&self, other: &Node) -> bool { + self.0.borrow().value == other.0.borrow().value + && self.0.borrow().children == other.0.borrow().children + } +} + #[cfg(test)] mod tests { use super::*; @@ -184,4 +205,31 @@ mod tests { assert!(tree2.find_bfs(|val| *val == "16").is_some()); assert!(tree2.find_bfs(|val| *val == "17").is_some()); } + + #[test] + fn path_to_on_empty_tree_returns_empty() { + let tree: Tree<&str> = Tree::default(); + + assert_eq!(tree.path_to(|val| *val == "i"), vec![]); + } + + // A + // B G H + // C I + // D E F + #[test] + fn it_can_find_a_path_to_a_node() { + let (tree, a) = Tree::new("A"); + let b = a.add_child_value("B"); + let c = b.add_child_value("C"); + let _d = c.add_child_value("D"); + let _e = c.add_child_value("D"); + let _f = c.add_child_value("D"); + let _g = a.add_child_value("G"); + let h = a.add_child_value("H"); + let i = a.add_child_value("I"); + + assert_eq!(tree.path_to(|val| *val == "z"), vec![]); + assert_eq!(tree.path_to(|val| *val == "i"), vec![a, h, i]); + } }