Set up the game review page along with #229

Merged
savanni merged 24 commits from otg/game-review into main 2024-03-31 23:37:51 +00:00
3 changed files with 233 additions and 13 deletions
Showing only changes of commit 7a06b8cf39 - Show all commits

View File

@ -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};

View File

@ -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);
}
}

View File

@ -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);
}
}