Convert the recursive parse tree to a slab GameTree
This commit is contained in:
parent
c50bd652f1
commit
c913e9da37
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
Color, Date, GameResult, GameType,
|
Color, Date, GameResult, GameType,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slab_tree::{NodeRef, Tree};
|
use slab_tree::{NodeId, NodeMut, NodeRef, Tree};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{HashSet, VecDeque},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
@ -36,7 +36,7 @@ pub enum SetupNodeError {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum GameNodeError {
|
pub enum GameNodeError {
|
||||||
UnsupportedGameNode(MoveNodeError, SetupNodeError),
|
UnsupportedGameNode(MoveNodeError, SetupNodeError, parser::Node),
|
||||||
ConflictingProperty,
|
ConflictingProperty,
|
||||||
ConflictingPosition,
|
ConflictingPosition,
|
||||||
}
|
}
|
||||||
|
@ -145,10 +145,10 @@ impl GameRecord {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&parser::Tree> for GameRecord {
|
impl TryFrom<parser::Tree> for GameRecord {
|
||||||
type Error = GameError;
|
type Error = GameError;
|
||||||
|
|
||||||
fn try_from(tree: &parser::Tree) -> Result<Self, Self::Error> {
|
fn try_from(tree: parser::Tree) -> Result<Self, Self::Error> {
|
||||||
let mut ty = None;
|
let mut ty = None;
|
||||||
let mut size = None;
|
let mut size = None;
|
||||||
let mut black_player = Player {
|
let mut black_player = Player {
|
||||||
|
@ -232,12 +232,46 @@ impl TryFrom<&parser::Tree> for GameRecord {
|
||||||
.collect::<Result<Vec<GameNode>, GameNodeError>>()
|
.collect::<Result<Vec<GameNode>, GameNodeError>>()
|
||||||
.map_err(GameError::InvalidGameNode)?;
|
.map_err(GameError::InvalidGameNode)?;
|
||||||
*/
|
*/
|
||||||
s.trees = vec![];
|
|
||||||
|
s.trees = tree.root.next.into_iter()
|
||||||
|
.map(recursive_tree_to_slab_tree)
|
||||||
|
.collect::<Result<Vec<Tree<GameNode>>, GameError>>()?;
|
||||||
|
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recursive_tree_to_slab_tree(node: parser::Node) -> Result<Tree<GameNode>, 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<GameNode> = 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> {
|
pub struct TreeIter<'a> {
|
||||||
queue: VecDeque<NodeRef<'a, &'a GameNode>>,
|
queue: VecDeque<NodeRef<'a, &'a GameNode>>,
|
||||||
}
|
}
|
||||||
|
@ -300,10 +334,10 @@ impl GameNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&parser::Node> for GameNode {
|
impl TryFrom<parser::Node> for GameNode {
|
||||||
type Error = GameNodeError;
|
type Error = GameNodeError;
|
||||||
|
|
||||||
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
|
fn try_from(n: parser::Node) -> Result<Self, Self::Error> {
|
||||||
// I originally wrote this recursively. However, on an ordinary game of a couple hundred
|
// 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.
|
// 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.
|
// 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
|
// 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
|
// recurse. Instead, I'm going to process just that node, then return to here and process
|
||||||
// the children.
|
// the children.
|
||||||
let move_node = MoveNode::try_from(n);
|
let move_node = MoveNode::try_from(n.clone());
|
||||||
let setup_node = SetupNode::try_from(n);
|
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
|
// 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
|
// 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)),
|
(Ok(mut node), _) => Ok(Self::MoveNode(node)),
|
||||||
(Err(_), Ok(mut node)) => Ok(Self::SetupNode(node)),
|
(Err(_), Ok(mut node)) => Ok(Self::SetupNode(node)),
|
||||||
(Err(move_err), Err(setup_err)) => {
|
(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<parser::Node> for MoveNode {
|
||||||
type Error = MoveNodeError;
|
type Error = MoveNodeError;
|
||||||
|
|
||||||
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
|
fn try_from(n: parser::Node) -> Result<Self, Self::Error> {
|
||||||
let s = match n.mv() {
|
let s = match n.mv() {
|
||||||
Some((color, mv)) => {
|
Some((color, mv)) => {
|
||||||
let mut s = Self::new(color, mv);
|
let mut s = Self::new(color, mv);
|
||||||
|
@ -462,10 +496,10 @@ impl SetupNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&parser::Node> for SetupNode {
|
impl TryFrom<parser::Node> for SetupNode {
|
||||||
type Error = SetupNodeError;
|
type Error = SetupNodeError;
|
||||||
|
|
||||||
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
|
fn try_from(n: parser::Node) -> Result<Self, Self::Error> {
|
||||||
match n.setup() {
|
match n.setup() {
|
||||||
Some(elements) => Self::new(elements),
|
Some(elements) => Self::new(elements),
|
||||||
None => Err(Self::Error::NotASetupNode),
|
None => Err(Self::Error::NotASetupNode),
|
||||||
|
@ -560,7 +594,7 @@ mod test {
|
||||||
],
|
],
|
||||||
next: vec![],
|
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![],
|
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.color, Color::White);
|
||||||
assert_eq!(node.mv, Move::Move("dp".to_owned()));
|
assert_eq!(node.mv, Move::Move("dp".to_owned()));
|
||||||
// assert_eq!(node.children, vec![]);
|
// assert_eq!(node.children, vec![]);
|
||||||
|
@ -625,7 +659,7 @@ mod move_node_tests {
|
||||||
next: vec![],
|
next: vec![],
|
||||||
};
|
};
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
MoveNode::try_from(&n),
|
MoveNode::try_from(n),
|
||||||
Err(MoveNodeError::IncompatibleProperty(_))
|
Err(MoveNodeError::IncompatibleProperty(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -675,7 +709,7 @@ mod path_test {
|
||||||
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||||
let games = games
|
let games = games
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|game| GameRecord::try_from(&game).expect("game to parse"))
|
.map(|game| GameRecord::try_from(game).expect("game to parse"))
|
||||||
.collect::<Vec<GameRecord>>();
|
.collect::<Vec<GameRecord>>();
|
||||||
f(games);
|
f(games);
|
||||||
}
|
}
|
||||||
|
@ -769,7 +803,7 @@ mod file_test {
|
||||||
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||||
let games = games
|
let games = games
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|game| GameRecord::try_from(&game).expect("game to parse"))
|
.map(|game| GameRecord::try_from(game).expect("game to parse"))
|
||||||
.collect::<Vec<GameRecord>>();
|
.collect::<Vec<GameRecord>>();
|
||||||
f(games);
|
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()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ pub fn parse_sgf(input: &str) -> Result<Vec<Result<GameRecord, game::GameError>>
|
||||||
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(input)?;
|
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(input)?;
|
||||||
let games = games
|
let games = games
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|game| GameRecord::try_from(&game))
|
.map(|game| GameRecord::try_from(game))
|
||||||
.collect::<Vec<Result<GameRecord, game::GameError>>>();
|
.collect::<Vec<Result<GameRecord, game::GameError>>>();
|
||||||
|
|
||||||
Ok(games)
|
Ok(games)
|
||||||
|
|
|
@ -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]))
|
Loading…
Reference in New Issue