Compare commits

...

3 Commits

6 changed files with 253 additions and 21 deletions

2
Cargo.lock generated
View File

@ -2709,6 +2709,7 @@ dependencies = [
"serde_json", "serde_json",
"sgf", "sgf",
"thiserror", "thiserror",
"uuid 0.8.2",
] ]
[[package]] [[package]]
@ -2728,6 +2729,7 @@ dependencies = [
"pango", "pango",
"sgf", "sgf",
"tokio", "tokio",
"uuid 0.8.2",
] ]
[[package]] [[package]]

View File

@ -15,6 +15,7 @@ grid = { version = "0.9" }
serde_json = { version = "1" } serde_json = { version = "1" }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
thiserror = { version = "1" } thiserror = { version = "1" }
uuid = { version = "0.8", features = ["v4", "serde"] }
[dev-dependencies] [dev-dependencies]
cool_asserts = { version = "2" } cool_asserts = { version = "2" }

View File

@ -26,7 +26,8 @@ mod database;
pub mod library; pub mod library;
mod types;
pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size};
pub mod settings; pub mod settings;
mod types;
pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size, Tree};

View File

@ -2,7 +2,9 @@ use crate::goban::{Coordinate, Goban};
use config::define_config; use config::define_config;
use config_derive::ConfigOption; use config_derive::ConfigOption;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{path::PathBuf, time::Duration, fmt}; use sgf::GameNode;
use uuid::Uuid;
use std::{cell::RefCell, fmt, path::PathBuf, time::Duration};
use thiserror::Error; use thiserror::Error;
define_config! { define_config! {
@ -140,7 +142,6 @@ impl AppState {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Rank { pub enum Rank {
Kyu(u8), Kyu(u8),
Dan(u8), Dan(u8),
Pro(u8), Pro(u8),
@ -228,9 +229,146 @@ 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<T> {
nodes: Vec<Node<T>>,
}
struct Node<T> {
id: usize,
node: T,
parent: Option<usize>,
depth: usize,
width: RefCell<Option<usize>>,
children: Vec<usize>,
}
impl<T> Tree<T> {
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 max_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, usize) {
println!("[{}]", idx);
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));
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),
}
}
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<Uuid> {
fn from(root: &'a GameNode) -> Self {
fn add_subtree<'a>(tree: &mut Tree<Uuid>, parent_idx: usize, node: &'a GameNode) {
let idx = tree.add_node(parent_idx, node.id());
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.id());
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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use sgf::{Move, MoveNode};
#[test] #[test]
fn current_player_changes_after_move() { fn current_player_changes_after_move() {
@ -284,4 +422,80 @@ mod test {
Err(BoardError::Ko) 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.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()));
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), (2, 0));
assert_eq!(tree.position(0, 1), (1, 0));
assert_eq!(tree.position(0, 0), (0, 0));
assert_eq!(tree.position(0, 4), (3, 1));
assert_eq!(tree.position(0, 5), (3, 2));
assert_eq!(tree.position(0, 6), (1, 3));
assert_eq!(tree.position(0, 7), (1, 4));
}
} }

View File

@ -21,6 +21,7 @@ otg-core = { path = "../core" }
pango = { version = "*" } pango = { version = "*" }
sgf = { path = "../../sgf" } sgf = { path = "../../sgf" }
tokio = { version = "1.26", features = [ "full" ] } tokio = { version = "1.26", features = [ "full" ] }
uuid = { version = "0.8", features = ["v4", "serde"] }
[build-dependencies] [build-dependencies]
glib-build-tools = "0.17" glib-build-tools = "0.17"

View File

@ -17,14 +17,18 @@ You should have received a copy of the GNU General Public License along with On
use cairo::Context; use cairo::Context;
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use otg_core::Tree;
use sgf::{GameNode, GameRecord}; use sgf::{GameNode, GameRecord};
use std::{cell::RefCell, rc::Rc};
use uuid::Uuid;
const WIDTH: i32 = 200; const WIDTH: i32 = 200;
const HEIGHT: i32 = 800; const HEIGHT: i32 = 800;
#[derive(Default)] #[derive(Default)]
pub struct ReviewTreePrivate { pub struct ReviewTreePrivate {
// record: Rc<RefCell<Option<GameRecord>>>, record: Rc<RefCell<Option<GameRecord>>>,
tree: Rc<RefCell<Option<Tree<Uuid>>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -43,9 +47,12 @@ glib::wrapper! {
} }
impl ReviewTree { impl ReviewTree {
pub fn new(_record: GameRecord) -> Self { pub fn new(record: GameRecord) -> Self {
let s: Self = Object::new(); let s: Self = Object::new();
*s.imp().tree.borrow_mut() = Some(Tree::from(&record.children[0]));
*s.imp().record.borrow_mut() = Some(record);
s.set_width_request(WIDTH); s.set_width_request(WIDTH);
s.set_height_request(HEIGHT); s.set_height_request(HEIGHT);
@ -124,7 +131,9 @@ fn node_width(node: &GameNode) -> usize {
} }
// If there is more than one child, run node_width on each one and add them together. // If there is more than one child, run node_width on each one and add them together.
children.iter().fold(0, |acc, child| acc + node_width(child)) children
.iter()
.fold(0, |acc, child| acc + node_width(child))
} }
// Since I know the width of a node, now I want to figure out its placement in the larger scheme of // Since I know the width of a node, now I want to figure out its placement in the larger scheme of
@ -137,10 +146,6 @@ fn node_width(node: &GameNode) -> usize {
// //
// Just having the node is greatly insufficient. I can get better results if I'm calculating the // Just having the node is greatly insufficient. I can get better results if I'm calculating the
// position of its children. // position of its children.
#[allow(dead_code)]
fn node_children_columns(_node: &GameNode) -> Vec<usize> {
vec![0, 1, 2]
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -175,9 +180,9 @@ mod test {
fn it_calculates_width_with_one_deep_child() { fn it_calculates_width_with_one_deep_child() {
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); 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_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let 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 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_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_c));
node_b.children.push(GameNode::MoveNode(node_d)); node_b.children.push(GameNode::MoveNode(node_d));
@ -197,12 +202,12 @@ mod test {
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); 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_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_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
let mut node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let 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_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
let mut node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned())); let 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_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_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_d));
node_c.children.push(GameNode::MoveNode(node_e)); node_c.children.push(GameNode::MoveNode(node_e));
@ -233,6 +238,14 @@ mod test {
node_a.children.push(node_c.clone()); node_a.children.push(node_c.clone());
node_a.children.push(node_d.clone()); node_a.children.push(node_d.clone());
assert_eq!(node_children_columns(&GameNode::MoveNode(node_a)), vec![0, 1, 2]); assert_eq!(
node_children_columns(&GameNode::MoveNode(node_a)),
vec![0, 1, 2]
);
}
#[test]
fn text_renderer() {
assert!(false);
} }
} }