From 8fc9f18d842865ec73a54be6f886bc73caed97e8 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 19 Apr 2024 09:22:42 -0400 Subject: [PATCH] Convert the recursive parse tree to a slab GameTree --- sgf/src/game.rs | 97 +++++++++++++++++++++++++++++------- sgf/src/lib.rs | 2 +- sgf/test_data/multi-tree.sgf | 1 + 3 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 sgf/test_data/multi-tree.sgf diff --git a/sgf/src/game.rs b/sgf/src/game.rs index 161f16b..cca9488 100644 --- a/sgf/src/game.rs +++ b/sgf/src/game.rs @@ -3,7 +3,7 @@ use crate::{ Color, Date, GameResult, GameType, }; use serde::{Deserialize, Serialize}; -use slab_tree::{NodeRef, Tree}; +use slab_tree::{NodeId, NodeMut, NodeRef, Tree}; use std::{ collections::{HashSet, VecDeque}, time::Duration, @@ -36,7 +36,7 @@ pub enum SetupNodeError { #[derive(Clone, Debug, PartialEq)] pub enum GameNodeError { - UnsupportedGameNode(MoveNodeError, SetupNodeError), + UnsupportedGameNode(MoveNodeError, SetupNodeError, parser::Node), ConflictingProperty, ConflictingPosition, } @@ -145,10 +145,10 @@ impl GameRecord { } } -impl TryFrom<&parser::Tree> for GameRecord { +impl TryFrom for GameRecord { type Error = GameError; - fn try_from(tree: &parser::Tree) -> Result { + fn try_from(tree: parser::Tree) -> Result { let mut ty = None; let mut size = None; let mut black_player = Player { @@ -232,12 +232,46 @@ impl TryFrom<&parser::Tree> for GameRecord { .collect::, GameNodeError>>() .map_err(GameError::InvalidGameNode)?; */ - s.trees = vec![]; + + s.trees = tree.root.next.into_iter() + .map(recursive_tree_to_slab_tree) + .collect::>, GameError>>()?; Ok(s) } } +fn recursive_tree_to_slab_tree(node: parser::Node) -> Result, GameError> { + let mut slab = Tree::new(); + let mut nodes: VecDeque<(NodeId, parser::Node)> = VecDeque::new(); + + let root_id = + slab.set_root(GameNode::try_from(node.clone()).map_err(GameError::InvalidGameNode)?); + nodes.push_back((root_id, node)); + + // I need to keep track of the current parent, and I need to keep on digging deeper into the + // tree. Given that I have the root, I can then easily find out all of the children. + // + // So, maybe I take the list of children. Assign each one of them to a place in the slab tree. + // Then push the child *and* its ID into a dequeue. So long as the dequeue is not empty, I want + // to pop a node and its ID from the dequeue. The retrieve the NodeMut for it and work on the + // node's children. + while let Some((node_id, node)) = nodes.pop_front() { + let mut game_node: NodeMut = slab + .get_mut(node_id) + .expect("invalid node_id when retrieving nodes from the game"); + // I have a node that is in the tree. Now run across all of its children, adding each one + // to the tree and pushing them into the deque along with their IDs. + for child in node.next { + let slab_child = game_node + .append(GameNode::try_from(child.clone()).map_err(GameError::InvalidGameNode)?); + nodes.push_back((slab_child.node_id(), child)); + } + } + + Ok(slab) +} + pub struct TreeIter<'a> { queue: VecDeque>, } @@ -300,10 +334,10 @@ impl GameNode { } } -impl TryFrom<&parser::Node> for GameNode { +impl TryFrom for GameNode { type Error = GameNodeError; - fn try_from(n: &parser::Node) -> Result { + fn try_from(n: parser::Node) -> Result { // I originally wrote this recursively. However, on an ordinary game of a couple hundred // moves, that meant that I was recursing 500 functions, and that exceeded the stack limit. // So, instead, I need to unroll everything to non-recursive form. @@ -312,8 +346,8 @@ impl TryFrom<&parser::Node> for GameNode { // only use the MoveNode::try_from and SetupNode::try_from if those functions don't // recurse. Instead, I'm going to process just that node, then return to here and process // the children. - let move_node = MoveNode::try_from(n); - let setup_node = SetupNode::try_from(n); + let move_node = MoveNode::try_from(n.clone()); + let setup_node = SetupNode::try_from(n.clone()); // I'm much too tired when writing this. I'm still recursing, but I did cut the number of // recursions in half. This helps, but it still doesn't guarantee that I'm going to be able @@ -332,7 +366,7 @@ impl TryFrom<&parser::Node> for GameNode { (Ok(mut node), _) => Ok(Self::MoveNode(node)), (Err(_), Ok(mut node)) => Ok(Self::SetupNode(node)), (Err(move_err), Err(setup_err)) => { - Err(Self::Error::UnsupportedGameNode(move_err, setup_err)) + Err(Self::Error::UnsupportedGameNode(move_err, setup_err, n)) } } } @@ -373,10 +407,10 @@ impl MoveNode { } } -impl TryFrom<&parser::Node> for MoveNode { +impl TryFrom for MoveNode { type Error = MoveNodeError; - fn try_from(n: &parser::Node) -> Result { + fn try_from(n: parser::Node) -> Result { let s = match n.mv() { Some((color, mv)) => { let mut s = Self::new(color, mv); @@ -462,10 +496,10 @@ impl SetupNode { } } -impl TryFrom<&parser::Node> for SetupNode { +impl TryFrom for SetupNode { type Error = SetupNodeError; - fn try_from(n: &parser::Node) -> Result { + fn try_from(n: parser::Node) -> Result { match n.setup() { Some(elements) => Self::new(elements), None => Err(Self::Error::NotASetupNode), @@ -560,7 +594,7 @@ mod test { ], next: vec![], }; - assert_matches!(GameNode::try_from(&n), Ok(GameNode::MoveNode(_))); + assert_matches!(GameNode::try_from(n), Ok(GameNode::MoveNode(_))); } } @@ -602,7 +636,7 @@ mod move_node_tests { ], next: vec![], }; - assert_matches!(MoveNode::try_from(&n), Ok(node) => { + assert_matches!(MoveNode::try_from(n), Ok(node) => { assert_eq!(node.color, Color::White); assert_eq!(node.mv, Move::Move("dp".to_owned())); // assert_eq!(node.children, vec![]); @@ -625,7 +659,7 @@ mod move_node_tests { next: vec![], }; assert_matches!( - MoveNode::try_from(&n), + MoveNode::try_from(n), Err(MoveNodeError::IncompatibleProperty(_)) ); } @@ -675,7 +709,7 @@ mod path_test { let (_, games) = parse_collection::>(text).unwrap(); let games = games .into_iter() - .map(|game| GameRecord::try_from(&game).expect("game to parse")) + .map(|game| GameRecord::try_from(game).expect("game to parse")) .collect::>(); f(games); } @@ -769,7 +803,7 @@ mod file_test { let (_, games) = parse_collection::>(text).unwrap(); let games = games .into_iter() - .map(|game| GameRecord::try_from(&game).expect("game to parse")) + .map(|game| GameRecord::try_from(game).expect("game to parse")) .collect::>(); f(games); } @@ -891,4 +925,29 @@ mod file_test { }, ); } + + #[test] + fn it_can_load_a_file_with_multiple_roots() { + with_file(std::path::Path::new("test_data/multi-tree.sgf"), |games| { + assert_eq!(games.len(), 1); + let game = &games[0]; + assert_eq!(game.game_type, GameType::Go); + assert_eq!( + game.board_size, + Size { + width: 19, + height: 19 + } + ); + assert_eq!(game.trees.len(), 2); + assert_matches!(game.trees[0].root().unwrap().data(), GameNode::MoveNode(node) => { + assert_eq!(node.color, Color::Black); + assert_eq!(node.mv, Move::Move("pd".to_owned())); + }); + assert_matches!(game.trees[1].root().unwrap().data(), GameNode::MoveNode(node) => { + assert_eq!(node.color, Color::Black); + assert_eq!(node.mv, Move::Move("pc".to_owned())); + }); + }); + } } diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index 6849499..1685bb3 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -73,7 +73,7 @@ pub fn parse_sgf(input: &str) -> Result> let (_, games) = parse_collection::>(input)?; let games = games .into_iter() - .map(|game| GameRecord::try_from(&game)) + .map(|game| GameRecord::try_from(game)) .collect::>>(); Ok(games) diff --git a/sgf/test_data/multi-tree.sgf b/sgf/test_data/multi-tree.sgf new file mode 100644 index 0000000..3fb4dd2 --- /dev/null +++ b/sgf/test_data/multi-tree.sgf @@ -0,0 +1 @@ +(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[7.5]SZ[19]DT[2024-04-19](;B[pd](;W[qc];B[qd];W[pc];B[oc];W[ob];B[nc];W[nb];B[mc];W[rd];B[re];W[rc];B[qf])(;W[qf];B[nc];W[rd];B[qc];W[pi]))(;B[pc];W[qe];B[oe];W[pg];B[ld];W[qj])) \ No newline at end of file