From 46b25cc6c519bc6a800ac126aeee2e2bd207cecb Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 31 Mar 2024 13:35:23 -0400 Subject: [PATCH] Set up a BFS iterator over the node tree --- otg/core/src/types.rs | 99 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/otg/core/src/types.rs b/otg/core/src/types.rs index e659237..0886a04 100644 --- a/otg/core/src/types.rs +++ b/otg/core/src/types.rs @@ -3,9 +3,9 @@ use config::define_config; use config_derive::ConfigOption; use serde::{Deserialize, Serialize}; use sgf::GameNode; -use uuid::Uuid; -use std::{cell::RefCell, fmt, path::PathBuf, time::Duration}; +use std::{cell::RefCell, collections::VecDeque, fmt, path::PathBuf, time::Duration}; use thiserror::Error; +use uuid::Uuid; define_config! { LibraryPath(LibraryPath), @@ -235,12 +235,12 @@ impl GameState { // // So, what is the maximum depth of the tree? Follow all paths and see how far I get in every case. // I could do this by just generating an intermediate tree and numbering each level. - pub struct Tree { nodes: Vec>, } -struct Node { +#[derive(Debug)] +pub struct Node { id: usize, node: T, parent: Option, @@ -294,6 +294,17 @@ impl Tree { ) } + // Since I know the width of a node, now I want to figure out its placement in the larger + // scheme of things. + // + // One thought I have is that I could just develop a grid virtually and start placing nodes. + // Whenever I notice a collision, I can just move the node over. But I'd like to see if I can + // be a bit smarter than doing it as just a vec into which I place things, as though it's a + // game board. So, given a game node, I want to figure out it's position along the X axis. + // + // Just having the node is greatly insufficient. I can get better results if I'm calculating + // the position of its children. + // // indent represents the indentation that should be applied to all children in this tree. It // amounts to the position of the parent node. pub fn position(&self, indent: usize, idx: usize) -> (usize, usize) { @@ -310,12 +321,22 @@ impl Tree { println!("[{}] sibling width {}", idx, sibling_width); (node.depth, indent + sibling_width) } - + // 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. + // + // There are some collapse rules that I could take into account here, but that I haven't + // figured out yet. If two nodes are side by side, and one of them has some wide children but + // the other has no children, then they are effectively the same width. The second node only + // needs to be moved out if it has children that would overlap the children of the first node. + // + // My algorithm right now is likely to generate unnecessarily wide trees in a complex game + // review. fn width(&self, id: usize) -> usize { println!("[{}] calculating width", id); let node = &self.nodes[id]; @@ -333,6 +354,12 @@ impl Tree { width } + + fn bfs_iter<'a>(&'a self) -> BFSIter { + let mut queue = VecDeque::new(); + queue.push_back(&self.nodes[0]); + BFSIter { tree: self, queue } + } } impl<'a> From<&'a GameNode> for Tree { @@ -365,9 +392,30 @@ impl<'a> From<&'a GameNode> for Tree { } } +pub struct BFSIter<'a, T> { + tree: &'a Tree, + queue: VecDeque<&'a Node>, +} + +impl<'a, T> Iterator for BFSIter<'a, T> { + type Item = &'a Node; + + fn next(&mut self) -> Option { + let retval = self.queue.pop_front(); + if let Some(ref retval) = retval { + retval + .children + .iter() + .for_each(|idx| self.queue.push_back(&self.tree.nodes[*idx])); + } + retval + } +} + #[cfg(test)] mod test { use super::*; + use cool_asserts::assert_matches; use sgf::{Move, MoveNode}; #[test] @@ -498,4 +546,45 @@ mod test { assert_eq!(tree.position(0, 6), (1, 3)); assert_eq!(tree.position(0, 7), (1, 4)); } + + #[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())); + + 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_b.children.push(GameNode::MoveNode(node_c.clone())); + + node_h.children.push(GameNode::MoveNode(node_i.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 game_tree = GameNode::MoveNode(node_a.clone()); + + let tree = Tree::from(&game_tree); + + 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)); + } }