Set up the game review page along with #229
|
@ -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};
|
||||
|
||||
|
|
|
@ -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<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 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,6 +142,10 @@ fn node_children_columns(_node: &GameNode) -> Vec<usize> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue