Compare commits

...

3 Commits

11 changed files with 360 additions and 139 deletions

1
Cargo.lock generated
View File

@ -2708,6 +2708,7 @@ dependencies = [
"serde 1.0.193",
"serde_json",
"sgf",
"slab_tree",
"thiserror",
"uuid 0.8.2",
]

View File

@ -14,6 +14,7 @@ sgf = { path = "../../sgf" }
grid = { version = "0.9" }
serde_json = { version = "1" }
serde = { version = "1", features = [ "derive" ] }
slab_tree = { version = "0.3" }
thiserror = { version = "1" }
uuid = { version = "0.8", features = ["v4", "serde"] }

View File

@ -81,7 +81,7 @@ impl From<HotseatPlayerRequest> for Player {
}
*/
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug)]
pub enum CoreResponse {
Library(library::LibraryResponse),
Settings(settings::SettingsResponse),

View File

@ -42,7 +42,8 @@ impl Database {
.unwrap();
match parse_sgf(&buffer) {
Ok(sgfs) => {
let mut sgfs = sgfs.into_iter().flatten().collect::<Vec<sgf::GameRecord>>();
let mut sgfs =
sgfs.into_iter().flatten().collect::<Vec<sgf::GameRecord>>();
games.append(&mut sgfs);
}
Err(err) => println!("Error parsing {:?}: {:?}", entry.path(), err),

View File

@ -29,5 +29,6 @@ pub mod library;
pub mod settings;
mod types;
pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size, Tree};
pub use types::{
BoardError, Color, Config, ConfigOption, DepthTree, LibraryPath, Player, Rank, Size,
};

View File

@ -14,18 +14,18 @@ General Public License for more details.
You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::{Core};
use crate::Core;
use serde::{Deserialize, Serialize};
use sgf::GameRecord;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum LibraryRequest {
ListGames
ListGames,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug)]
pub enum LibraryResponse {
Games(Vec<GameRecord>)
Games(Vec<GameRecord>),
}
async fn handle_list_games(model: &Core) -> LibraryResponse {
@ -39,10 +39,8 @@ async fn handle_list_games(model: &Core) -> LibraryResponse {
}
}
pub async fn handle(model: &Core, request: LibraryRequest) -> LibraryResponse {
match request {
LibraryRequest::ListGames => handle_list_games(model).await,
}
}

View File

@ -2,10 +2,11 @@ use crate::goban::{Coordinate, Goban};
use config::define_config;
use config_derive::ConfigOption;
use serde::{Deserialize, Serialize};
use sgf::GameNode;
use std::{cell::RefCell, collections::VecDeque, fmt, path::PathBuf, time::Duration};
use sgf::GameTree;
use std::{
cell::RefCell, collections::{HashMap, VecDeque}, fmt, ops::Deref, path::PathBuf, time::Duration
};
use thiserror::Error;
use uuid::Uuid;
define_config! {
LibraryPath(LibraryPath),
@ -229,6 +230,7 @@ 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.
@ -238,19 +240,43 @@ impl GameState {
pub struct Tree<T> {
nodes: Vec<Node<T>>,
}
*/
#[derive(Debug)]
pub struct Node<T> {
pub id: usize,
node: T,
parent: Option<usize>,
depth: usize,
width: RefCell<Option<usize>>,
children: Vec<usize>,
pub struct DepthTree(slab_tree::Tree<SizeNode>);
impl Deref for DepthTree {
type Target = slab_tree::Tree<SizeNode>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Tree<T> {
fn new(root: T) -> Self {
#[derive(Debug)]
pub struct SizeNode {
node_id: slab_tree::NodeId,
parent: Option<slab_tree::NodeId>,
depth: usize,
width: usize,
}
impl SizeNode {
pub fn position(&self) -> (usize, usize) {
(self.depth, self.width)
}
}
impl DepthTree {
// My previous work to convert from a node tree to this tree-with-width dependend on the node tree
// being a recursive data structure. Now I need to find a way to convert a slab tree to this width
// tree.
//
// It all feels like a lot of custom weirdness. I shouldn't need a bunch of custom data structures,
// so I want to eliminate the "Tree" above and keep using the slab tree. I think I should be able
// to build these Node objects without needing a custom data structure.
fn new() -> Self {
Self(slab_tree::Tree::new())
/*
Tree {
nodes: vec![Node {
id: 0,
@ -261,8 +287,10 @@ impl<T> Tree<T> {
children: vec![],
}],
}
*/
}
/*
pub fn node(&self, idx: usize) -> &T {
&self.nodes[idx].node
}
@ -286,12 +314,21 @@ impl<T> Tree<T> {
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 },
)
self.0
.root()
.unwrap()
.traverse_pre_order()
.fold(0, |max, node| {
println!("node depth: {}", node.data().depth);
if node.data().depth > max {
node.data().depth
} else {
max
}
})
}
// Since I know the width of a node, now I want to figure out its placement in the larger
@ -309,7 +346,9 @@ impl<T> Tree<T> {
// amounts to the position of the parent node.
//
// When drawing nodes, I don't know how to persist the level of indent.
pub fn position(&self, idx: usize) -> (usize, usize) {
// unimplemented!()
/*
let node = &self.nodes[idx];
match node.parent {
Some(parent_idx) => {
@ -326,8 +365,9 @@ impl<T> Tree<T> {
// 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.
//
@ -353,14 +393,102 @@ impl<T> Tree<T> {
width
}
*/
pub fn bfs_iter(&self) -> BFSIter<T> {
pub fn bfs_iter(&self) -> BFSIter<'_, SizeNode> {
let mut queue = VecDeque::new();
queue.push_back(&self.nodes[0]);
queue.push_back(self.0.root().unwrap());
BFSIter { tree: self, queue }
}
}
impl<'a> From<&'a GameTree> for DepthTree {
fn from(tree: &'a GameTree) -> Self {
// Like in the conversion from SGF to GameTree, I need to traverse the entire tree one node
// at a time, keeping track of node ids as we go. I'm going to go with a depth-first
// traversal. When generating each node, I think I want to generate all of the details of
// the node as we go.
let source_root_node = tree.root();
match source_root_node {
Some(source_root_node) => {
// Do the real work
// The id_map indexes from the source tree to the destination tree. Reverse
// indexing is accomplished by looking at the node_id in a node in the destination
// tree.
let mut id_map: HashMap<slab_tree::NodeId, slab_tree::NodeId> = HashMap::new();
let mut tree = slab_tree::Tree::new();
let mut iter = source_root_node.traverse_pre_order();
let _ = iter.next().unwrap(); // we already know that the first element to be
// returned is the root node, and that the root node
// already exists. Otherwise we wouldn't even be in
// this branch.
let dest_root_id = tree.set_root(SizeNode {
node_id: source_root_node.node_id(),
parent: None,
depth: 0,
width: 0,
});
id_map.insert(source_root_node.node_id(), dest_root_id);
for source_node in iter {
let dest_parent_id = id_map
.get(&source_node.parent().unwrap().node_id())
.unwrap();
let mut dest_parent = tree.get_mut(*dest_parent_id).unwrap();
let new_depth_node = SizeNode {
node_id: source_node.node_id(),
parent: Some(*dest_parent_id),
depth: 1 + dest_parent.data().depth,
width: dest_parent.data().width,
};
let new_node_id = dest_parent.append(new_depth_node).node_id();
match tree
.get(new_node_id)
.unwrap()
.prev_sibling()
.map(|node| node.data().width)
{
None => {}
Some(previous_width) => {
let mut new_node = tree.get_mut(new_node_id).unwrap();
new_node.data().width = previous_width + 1;
}
}
/*
let new_node = tree.get_mut(*dest_parent_id).unwrap().append(new_depth_node);
let previous_node = new_node.prev_sibling();
match previous_node {
None => {}
}
*/
/*
match dest_noderef.prev_sibling() {
None => {}
Some(mut node) => { dest_noderef.data().width = node.data().width + 1 }
}
*/
id_map.insert(source_node.node_id(), new_node_id);
}
Self(tree)
}
None => Self::new(),
}
}
}
/*
impl<'a> From<&'a GameNode> for Tree<Uuid> {
fn from(root: &'a GameNode) -> Self {
fn add_subtree(tree: &mut Tree<Uuid>, parent_idx: usize, node: &GameNode) {
@ -390,32 +518,32 @@ impl<'a> From<&'a GameNode> for Tree<Uuid> {
tree
}
}
*/
pub struct BFSIter<'a, T> {
tree: &'a Tree<T>,
queue: VecDeque<&'a Node<T>>,
tree: &'a DepthTree,
queue: VecDeque<slab_tree::NodeRef<'a, T>>,
}
impl<'a, T> Iterator for BFSIter<'a, T> {
type Item = &'a Node<T>;
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let retval = self.queue.pop_front();
if let Some(retval) = retval {
if let Some(ref retval) = retval {
retval
.children
.iter()
.for_each(|idx| self.queue.push_back(&self.tree.nodes[*idx]));
.children()
.for_each(|noderef| self.queue.push_back(noderef));
}
retval
retval.map(|retval| retval.data())
}
}
#[cfg(test)]
mod test {
use super::*;
use cool_asserts::assert_matches;
use sgf::{Move, MoveNode};
// use sgf::{GameRecord, GameTree, GameType, Move, MoveNode};
use sgf::{GameNode, GameTree, Move, MoveNode};
#[test]
fn current_player_changes_after_move() {
@ -474,80 +602,138 @@ mod test {
// B G H
// C I
// D E F
fn branching_tree() -> GameTree {
let mut game_tree = GameTree::default();
let node_a = game_tree.set_root(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)));
let node_b = game_tree
.get_mut(node_a)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_c = game_tree
.get_mut(node_b)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_d = game_tree
.get_mut(node_c)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_e = game_tree
.get_mut(node_c)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_f = game_tree
.get_mut(node_c)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_g = game_tree
.get_mut(node_a)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let node_h = game_tree
.get_mut(node_a)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
let _ = game_tree
.get_mut(node_h)
.unwrap()
.append(GameNode::MoveNode(MoveNode::new(
sgf::Color::Black,
Move::Move("dp".to_owned()),
)))
.node_id();
game_tree
}
#[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);
let game_tree = branching_tree();
let tree = DepthTree::from(&game_tree);
assert_eq!(
game_tree.root().unwrap().traverse_pre_order().count(),
tree.0.root().unwrap().traverse_pre_order().count()
);
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()));
let game_tree = branching_tree();
let tree = DepthTree::from(&game_tree);
node_c.children.push(GameNode::MoveNode(node_d));
node_c.children.push(GameNode::MoveNode(node_e));
node_c.children.push(GameNode::MoveNode(node_f));
let node_a = tree.root().unwrap();
assert_eq!(node_a.data().position(), (0, 0));
node_b.children.push(GameNode::MoveNode(node_c));
let node_b = node_a.first_child().unwrap();
assert_eq!(node_b.data().position(), (1, 0));
let node_g = node_b.next_sibling().unwrap();
assert_eq!(node_g.data().position(), (1, 1));
let node_h = node_g.next_sibling().unwrap();
assert_eq!(node_h.data().position(), (1, 2));
node_h.children.push(GameNode::MoveNode(node_i));
let node_c = node_b.first_child().unwrap();
assert_eq!(node_c.data().position(), (2, 0));
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 node_d = node_c.first_child().unwrap();
assert_eq!(node_d.data().position(), (3, 0));
let game_tree = GameNode::MoveNode(node_a);
let node_i = node_h.first_child().unwrap();
assert_eq!(node_i.data().position(), (2, 2));
let tree = Tree::from(&game_tree);
assert_eq!(tree.position(2), (2, 0));
assert_eq!(tree.position(1), (1, 0));
assert_eq!(tree.position(0), (0, 0));
assert_eq!(tree.position(4), (3, 1));
assert_eq!(tree.position(5), (3, 2));
assert_eq!(tree.position(6), (1, 3));
assert_eq!(tree.position(7), (1, 4));
/*
assert_eq!(tree.position(test_tree.node_c), (2, 0));
assert_eq!(tree.position(test_tree.node_b), (1, 0));
assert_eq!(tree.position(test_tree.node_a), (0, 0));
assert_eq!(tree.position(test_tree.node_d), (3, 1));
assert_eq!(tree.position(test_tree.node_e), (3, 2));
assert_eq!(tree.position(test_tree.node_f), (1, 3));
assert_eq!(tree.position(test_tree.node_g), (1, 4));
*/
}
#[ignore]
#[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()));
@ -558,6 +744,24 @@ mod test {
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()));
let game = GameRecord::new(
GameType::Go,
Size {
width: 19,
height: 19,
},
Player {
name: Some("Black".to_owned()),
rank: None,
team: None,
},
Player {
name: Some("White".to_owned()),
rank: None,
team: None,
},
);
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()));
@ -585,5 +789,6 @@ mod test {
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));
*/
}
}

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with On
use cairo::Context;
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use otg_core::Tree;
use otg_core::DepthTree;
use sgf::GameRecord;
use std::{cell::RefCell, rc::Rc};
use uuid::Uuid;
@ -28,7 +28,7 @@ const HEIGHT: i32 = 800;
#[derive(Default)]
pub struct ReviewTreePrivate {
record: Rc<RefCell<Option<GameRecord>>>,
tree: Rc<RefCell<Option<Tree<Uuid>>>>,
tree: Rc<RefCell<Option<DepthTree>>>,
}
#[glib::object_subclass]
@ -50,7 +50,9 @@ impl ReviewTree {
pub fn new(record: GameRecord) -> Self {
let s: Self = Object::new();
*s.imp().tree.borrow_mut() = Some(Tree::from(&record.children[0]));
// TODO: there can be more than one tree, especially in instructional files. Either unify
// them into a single tree in the GameTree, or draw all of them here.
*s.imp().tree.borrow_mut() = Some(DepthTree::from(&record.trees[0]));
*s.imp().record.borrow_mut() = Some(record);
s.set_width_request(WIDTH);
@ -67,7 +69,7 @@ impl ReviewTree {
}
pub fn redraw(&self, ctx: &Context, _width: i32, _height: i32) {
let tree: &Option<Tree<Uuid>> = &self.imp().tree.borrow();
let tree: &Option<DepthTree> = &self.imp().tree.borrow();
match tree {
Some(ref tree) => {
for node in tree.bfs_iter() {
@ -76,7 +78,7 @@ impl ReviewTree {
// the parent? do I need to just make it more intrinsically a part of the position
// code?
ctx.set_source_rgb(0.7, 0.7, 0.7);
let (row, column) = tree.position(node.id);
let (row, column) = node.position();
let y = (row as f64) * 20. + 10.;
let x = (column as f64) * 20. + 10.;
ctx.arc(x, y, 5., 0., 2. * std::f64::consts::PI);

View File

@ -55,9 +55,10 @@ impl GameReview {
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
// This is not a fatal error, so I'll replace this `unwrap` call with something that
// renders the board and notifies the user of a problem that cannot be resolved.
let board_repr = otg_core::Goban::default()
.apply_moves(record.mainline())
.unwrap();
let board_repr = match record.mainline() {
Some(iter) => otg_core::Goban::default().apply_moves(iter).unwrap(),
None => otg_core::Goban::default(),
};
let board = Goban::new(board_repr, resources);
/*

View File

@ -7,7 +7,7 @@ use slab_tree::{NodeId, NodeMut, NodeRef, Tree};
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Debug,
ops::Deref,
ops::{Deref, DerefMut},
time::Duration,
};
use uuid::Uuid;
@ -136,8 +136,7 @@ impl GameRecord {
/// was actually played out, and by convention consists of the first node in each list of
/// children.
pub fn mainline(&self) -> Option<impl Iterator<Item = &'_ GameNode>> {
println!("number of trees: {}", self.trees.len());
if !self.trees.is_empty(){
if !self.trees.is_empty() {
Some(MainlineIter {
next: self.trees[0].root(),
tree: &self.trees[0],
@ -309,6 +308,12 @@ impl<'a> Iterator for TreeIter<'a> {
pub struct GameTree(Tree<GameNode>);
impl Default for GameTree {
fn default() -> Self {
Self(Tree::new())
}
}
impl Clone for GameTree {
fn clone(&self) -> Self {
match self.0.root() {
@ -362,6 +367,12 @@ impl Deref for GameTree {
}
}
impl DerefMut for GameTree {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl PartialEq for GameTree {
fn eq(&self, other: &Self) -> bool {
// Get pre-order iterators over both trees, zip them, and ensure that the data contents are

View File

@ -1,7 +1,7 @@
mod date;
mod game;
pub use game::{GameNode, GameRecord, MoveNode, Player};
pub use game::{GameNode, GameRecord, GameTree, MoveNode, Player};
mod parser;
pub use parser::{parse_collection, Move};