Set up a second layer to the SGF parser #226
|
@ -43,6 +43,14 @@ pub struct Player {
|
||||||
pub team: Option<String>,
|
pub team: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This represents the more semantic version of the game parser. Where the `parser` crate pulls
|
||||||
|
/// out a raw set of nodes, this structure is guaranteed to be a well-formed game. Getting to this
|
||||||
|
/// level, the interpreter will reject any games that have setup properties and move properties
|
||||||
|
/// mixed in a single node. If there are other semantic problems, the interpreter will reject
|
||||||
|
/// those, as well. Where the function of the parser is to understand and correct fundamental
|
||||||
|
/// syntax issues, the result of the Game is to have a fully-understood game. However, this doesn't
|
||||||
|
/// (yet?) go quite to the level of apply the game type (i.e., this is Go, Chess, Yinsh, or
|
||||||
|
/// whatever).
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
game_type: GameType,
|
game_type: GameType,
|
||||||
|
@ -267,17 +275,45 @@ impl Node for 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
|
||||||
|
// 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, I can treat each branch of the tree as a single line. Iterate over that line. I can
|
||||||
|
// 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 move_node = MoveNode::try_from(n);
|
||||||
let setup_node = SetupNode::try_from(n);
|
let setup_node = SetupNode::try_from(n);
|
||||||
|
|
||||||
match (move_node, setup_node) {
|
// I'm much too tired when writing this. I'm still recursing, but I did cut the number of
|
||||||
(Ok(node), _) => Ok(Self::MoveNode(node)),
|
// recursions in half. This helps, but it still doesn't guarantee that I'm going to be able
|
||||||
(Err(_), Ok(node)) => Ok(Self::SetupNode(node)),
|
// to parse all possible games. So, still, treat each branch of the game as a single line.
|
||||||
|
// Iterate over that line, don't recurse. Create bookmarks at each branch point, and then
|
||||||
|
// come back to each one.
|
||||||
|
let children = n
|
||||||
|
.next
|
||||||
|
.iter()
|
||||||
|
.map(|n| GameNode::try_from(n))
|
||||||
|
.collect::<Result<Vec<Self>, Self::Error>>()?;
|
||||||
|
|
||||||
|
let node = match (move_node, setup_node) {
|
||||||
|
(Ok(mut node), _) => {
|
||||||
|
node.children = children;
|
||||||
|
Ok(Self::MoveNode(node))
|
||||||
|
}
|
||||||
|
(Err(_), Ok(mut node)) => {
|
||||||
|
node.children = children;
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}?;
|
||||||
|
|
||||||
|
Ok(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +369,7 @@ 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 mut 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);
|
||||||
|
|
||||||
|
@ -383,14 +419,6 @@ impl TryFrom<&parser::Node> for MoveNode {
|
||||||
None => Err(Self::Error::NotAMoveNode),
|
None => Err(Self::Error::NotAMoveNode),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
s.children = n
|
|
||||||
.next
|
|
||||||
.iter()
|
|
||||||
.map(|node| {
|
|
||||||
GameNode::try_from(node).map_err(|err| Self::Error::ChildError(Box::new(err)))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<GameNode>, MoveNodeError>>()?;
|
|
||||||
|
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -677,9 +705,10 @@ mod file_test {
|
||||||
with_text(&text, f);
|
with_text(&text, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
/// This test checks against an ordinary game from SGF. It is unannotated and should contain
|
||||||
|
/// only move nodes with no setup nodes. The original source is from a game I played on KGS.
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_load_basic_game_records() {
|
fn it_can_load_an_ordinary_unannotated_game() {
|
||||||
with_file(
|
with_file(
|
||||||
std::path::Path::new("test_data/2020 USGO DDK, Round 1.sgf"),
|
std::path::Path::new("test_data/2020 USGO DDK, Round 1.sgf"),
|
||||||
|games| {
|
|games| {
|
||||||
|
|
Loading…
Reference in New Issue