From 7a06b8cf39933181fa0df98ad4b5ca3e362bad88 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 29 Mar 2024 09:10:38 -0400 Subject: [PATCH] Work out how to calculate the horizontal position of each node --- otg/core/src/lib.rs | 7 +- otg/core/src/types.rs | 213 +++++++++++++++++++++++++- otg/gtk/src/components/review_tree.rs | 26 +++- 3 files changed, 233 insertions(+), 13 deletions(-) diff --git a/otg/core/src/lib.rs b/otg/core/src/lib.rs index b0ed9fa..3168a04 100644 --- a/otg/core/src/lib.rs +++ b/otg/core/src/lib.rs @@ -26,7 +26,8 @@ mod database; pub mod library; -mod types; -pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size}; - pub mod settings; + +mod types; +pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size, Tree}; + diff --git a/otg/core/src/types.rs b/otg/core/src/types.rs index 36979ce..77a21a9 100644 --- a/otg/core/src/types.rs +++ b/otg/core/src/types.rs @@ -2,7 +2,8 @@ use crate::goban::{Coordinate, Goban}; use config::define_config; use config_derive::ConfigOption; use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, time::Duration, fmt}; +use sgf::GameNode; +use std::{cell::RefCell, fmt, path::PathBuf, time::Duration}; use thiserror::Error; define_config! { @@ -140,7 +141,6 @@ impl AppState { #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Rank { - Kyu(u8), Dan(u8), Pro(u8), @@ -228,9 +228,144 @@ 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. +// +// 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 { + id: usize, + node: T, + parent: Option, + depth: usize, + width: RefCell>, + children: Vec, +} + +impl Tree { + fn new(root: T) -> Self { + Tree { + nodes: vec![Node { + id: 0, + node: root, + parent: None, + depth: 0, + width: RefCell::new(None), + children: vec![], + }], + } + } + + pub fn node(&self, idx: usize) -> &T { + &self.nodes[idx].node + } + + // Add a node to the parent specified by parent_idx. Return the new index. This cannot be used + // to add the root node, but the constructor should handle that, anyway. + fn add_node(&mut self, parent_idx: usize, node: T) -> usize { + let next_idx = self.nodes.len(); + let parent = &self.nodes[parent_idx]; + + self.nodes.push(Node { + id: next_idx, + node, + parent: Some(parent_idx), + depth: parent.depth + 1, + width: RefCell::new(None), + children: vec![], + }); + + let parent = &mut self.nodes[parent_idx]; + parent.children.push(next_idx); + next_idx + } + + pub fn depth(&self) -> usize { + self.nodes.iter().fold( + 0, + |max, node| if node.depth > max { node.depth } else { max }, + ) + } + + // 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 { + let node = &self.nodes[idx]; + match node.parent { + Some(parent_idx) => { + let parent = &self.nodes[parent_idx]; + let sibling_width = parent + .children + .iter() + .take_while(|n| **n != node.id) + .fold(0, |acc, n| acc + self.width(*n)); + indent + sibling_width + 1 + } + + // Root nodes won't have a parent, so just put them in column 1 + None => 1, + } + } + + fn width(&self, id: usize) -> usize { + println!("[{}] calculating width", id); + let node = &self.nodes[id]; + if let Some(width) = *node.width.borrow() { + return width; + } + + let width = node + .children + .iter() + .fold(0, |acc, child| acc + self.width(*child)); + let width = if width == 0 { 1 } else { width }; + println!("[{}] width: {}", id, width); + *node.width.borrow_mut() = Some(width); + + width + } +} + +impl<'a> From<&'a GameNode> for Tree<&'a GameNode> { + fn from(root: &'a GameNode) -> Self { + fn add_subtree<'a>(tree: &mut Tree<&'a GameNode>, parent_idx: usize, node: &'a GameNode) { + let idx = tree.add_node(parent_idx, node); + + let children = match node { + GameNode::MoveNode(node) => &node.children, + GameNode::SetupNode(node) => &node.children, + }; + + for child in children { + add_subtree(tree, idx, child); + } + } + + let mut tree = Tree::new(root); + + let children = match root { + GameNode::MoveNode(node) => &node.children, + GameNode::SetupNode(node) => &node.children, + }; + + for node in children { + add_subtree(&mut tree, 0, node); + } + + tree + } +} + #[cfg(test)] mod test { use super::*; + use sgf::{Move, MoveNode}; #[test] fn current_player_changes_after_move() { @@ -284,4 +419,78 @@ mod test { Err(BoardError::Ko) ); } + + // A + // B G H + // C I + // D E F + #[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); + + assert_eq!(tree.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())); + + 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); + + assert_eq!(tree.position(0, 2), 1); + assert_eq!(tree.position(0, 1), 1); + assert_eq!(tree.position(0, 0), 1); + assert_eq!(tree.position(0, 4), 2); + assert_eq!(tree.position(0, 5), 3); + } } diff --git a/otg/gtk/src/components/review_tree.rs b/otg/gtk/src/components/review_tree.rs index 5d7f878..e371791 100644 --- a/otg/gtk/src/components/review_tree.rs +++ b/otg/gtk/src/components/review_tree.rs @@ -142,6 +142,10 @@ fn node_children_columns(_node: &GameNode) -> Vec { vec![0, 1, 2] } +#[allow(dead_code)] +fn print_node_tree(node: &GameNode) { +} + #[cfg(test)] mod test { use super::*; @@ -175,9 +179,9 @@ mod test { fn it_calculates_width_with_one_deep_child() { let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); node_b.children.push(GameNode::MoveNode(node_c)); node_b.children.push(GameNode::MoveNode(node_d)); @@ -197,12 +201,12 @@ mod test { let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_g = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_g = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let mut node_h = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); - let mut node_i = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); + let node_i = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); node_c.children.push(GameNode::MoveNode(node_d)); node_c.children.push(GameNode::MoveNode(node_e)); @@ -235,4 +239,10 @@ mod test { assert_eq!(node_children_columns(&GameNode::MoveNode(node_a)), vec![0, 1, 2]); } + + #[test] + fn text_renderer() { + + assert!(false); + } }