Overhaul the sgf representation
This commit is contained in:
parent
b2ba257cac
commit
d94dd5245c
|
@ -11,3 +11,6 @@ nom = { version = "7" }
|
|||
serde = { version = "1", features = [ "derive" ] }
|
||||
thiserror = { version = "1"}
|
||||
typeshare = { version = "1" }
|
||||
|
||||
[dev-dependencies]
|
||||
cool_asserts = { version = "2" }
|
||||
|
|
|
@ -74,6 +74,7 @@ use crate::{
|
|||
Error,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use typeshare::typeshare;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -84,11 +85,18 @@ pub struct Game {
|
|||
pub tree: Tree,
|
||||
}
|
||||
|
||||
impl Deref for Game {
|
||||
type Target = Tree;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.tree
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Tree> for Game {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(tree: Tree) -> Result<Self, Self::Error> {
|
||||
let board_size = match tree.sequence[0].find_prop("SZ") {
|
||||
let board_size = match tree.root.find_prop("SZ") {
|
||||
Some(prop) => Size::try_from(prop.values[0].as_str())?,
|
||||
None => Size {
|
||||
width: 19,
|
||||
|
@ -96,35 +104,34 @@ impl TryFrom<Tree> for Game {
|
|||
},
|
||||
};
|
||||
let mut info = GameInfo::default();
|
||||
info.app_name = tree.sequence[0]
|
||||
.find_prop("AP")
|
||||
.map(|prop| prop.values[0].clone());
|
||||
info.black_player = tree.sequence[0]
|
||||
.find_prop("PB")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.app_name = tree.root.find_prop("AP").map(|prop| prop.values[0].clone());
|
||||
info.black_player = tree.root.find_prop("PB").map(|prop| prop.values.join(", "));
|
||||
|
||||
info.black_rank = tree.sequence[0]
|
||||
info.black_rank = tree
|
||||
.root
|
||||
.find_prop("BR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.white_player = tree.sequence[0]
|
||||
.find_prop("PW")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.white_player = tree.root.find_prop("PW").map(|prop| prop.values.join(", "));
|
||||
|
||||
info.white_rank = tree.sequence[0]
|
||||
info.white_rank = tree
|
||||
.root
|
||||
.find_prop("WR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.result = tree.sequence[0]
|
||||
info.result = tree
|
||||
.root
|
||||
.find_prop("RE")
|
||||
.and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.time_limits = tree.sequence[0]
|
||||
info.time_limits = tree
|
||||
.root
|
||||
.find_prop("TM")
|
||||
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
||||
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
|
||||
|
||||
info.date = tree.sequence[0]
|
||||
info.date = tree
|
||||
.root
|
||||
.find_prop("DT")
|
||||
.and_then(|prop| {
|
||||
let v = prop
|
||||
|
@ -144,21 +151,13 @@ impl TryFrom<Tree> for Game {
|
|||
})
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
info.event = tree.sequence[0]
|
||||
.find_prop("EV")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.event = tree.root.find_prop("EV").map(|prop| prop.values.join(", "));
|
||||
|
||||
info.round = tree.sequence[0]
|
||||
.find_prop("RO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.round = tree.root.find_prop("RO").map(|prop| prop.values.join(", "));
|
||||
|
||||
info.source = tree.sequence[0]
|
||||
.find_prop("SO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.source = tree.root.find_prop("SO").map(|prop| prop.values.join(", "));
|
||||
|
||||
info.game_keeper = tree.sequence[0]
|
||||
.find_prop("US")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
info.game_keeper = tree.root.find_prop("US").map(|prop| prop.values.join(", "));
|
||||
|
||||
Ok(Game {
|
||||
board_size,
|
||||
|
@ -301,7 +300,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
date::Date,
|
||||
tree::{parse_collection, Size},
|
||||
tree::{parse_collection, Property, Size},
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
@ -384,4 +383,32 @@ mod tests {
|
|||
assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_presents_a_mainline() {
|
||||
with_file(
|
||||
std::path::Path::new("test_data/2020 USGO DDK, Round 1.sgf"),
|
||||
|trees| {
|
||||
assert_eq!(trees.len(), 1);
|
||||
let tree = &trees[0];
|
||||
|
||||
let node = &tree.root;
|
||||
println!("{:?}", node);
|
||||
|
||||
let node = node.next();
|
||||
println!("{:?}", node);
|
||||
|
||||
/*
|
||||
assert_eq!(
|
||||
node.find_prop("B"),
|
||||
Some(Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["dp".to_owned()]
|
||||
})
|
||||
);
|
||||
*/
|
||||
assert!(false);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ pub enum Game {
|
|||
Unsupported(tree::Tree),
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn parse_sgf(input: &str) -> Result<Vec<Game>, Error> {
|
||||
let (_, trees) = parse_collection::<nom::error::VerboseError<&str>>(input)?;
|
||||
Ok(trees
|
||||
|
@ -73,6 +74,7 @@ pub fn parse_sgf(input: &str) -> Result<Vec<Game>, Error> {
|
|||
})
|
||||
.collect::<Vec<Game>>())
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
impl From<(&str, VerboseErrorKind)> for
|
||||
|
|
333
sgf/src/tree.rs
333
sgf/src/tree.rs
|
@ -54,29 +54,19 @@ impl TryFrom<&str> for Size {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Tree {
|
||||
pub sequence: Vec<Node>,
|
||||
pub sub_sequences: Vec<Tree>,
|
||||
pub root: Node,
|
||||
}
|
||||
|
||||
impl ToString for Tree {
|
||||
fn to_string(&self) -> String {
|
||||
let sequence = self
|
||||
.sequence
|
||||
.iter()
|
||||
.map(|node| node.to_string())
|
||||
.collect::<String>();
|
||||
let subsequences = self
|
||||
.sub_sequences
|
||||
.iter()
|
||||
.map(|seq| seq.to_string())
|
||||
.collect::<String>();
|
||||
format!("({}{})", sequence, subsequences)
|
||||
format!("({})", self.root.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Node {
|
||||
pub properties: Vec<Property>,
|
||||
pub next: Vec<Node>,
|
||||
}
|
||||
|
||||
impl ToString for Node {
|
||||
|
@ -86,7 +76,21 @@ impl ToString for Node {
|
|||
.iter()
|
||||
.map(|prop| prop.to_string())
|
||||
.collect::<String>();
|
||||
format!(";{}", props)
|
||||
|
||||
let next = if self.next.len() == 1 {
|
||||
self.next
|
||||
.iter()
|
||||
.map(|node| node.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
} else {
|
||||
self.next
|
||||
.iter()
|
||||
.map(|node| format!("({})", node.to_string()))
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
};
|
||||
format!(";{}{}", props, next)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +101,10 @@ impl Node {
|
|||
.find(|prop| prop.ident == ident)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn next<'a>(&'a self) -> Option<&'a Node> {
|
||||
self.next.get(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -119,40 +127,40 @@ impl ToString for Property {
|
|||
pub fn parse_collection<'a, E: nom::error::ParseError<&'a str>>(
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, Vec<Tree>, E> {
|
||||
separated_list1(multispace1, parse_tree)(input)
|
||||
let (input, roots) = separated_list1(multispace1, parse_tree)(input)?;
|
||||
let trees = roots
|
||||
.into_iter()
|
||||
.map(|root| Tree { root })
|
||||
.collect::<Vec<Tree>>();
|
||||
|
||||
Ok((input, trees))
|
||||
}
|
||||
|
||||
// note: must preserve unknown properties
|
||||
// note: must fix or preserve illegally formatted game-info properties
|
||||
// note: must correct or delete illegally foramtted properties, but display a warning
|
||||
fn parse_tree<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Tree, E> {
|
||||
fn parse_tree<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
delimited(tag("("), parse_sequence, tag(")"))(input)
|
||||
}
|
||||
let (input, _) = tag("(")(input)?;
|
||||
let (input, node) = parse_node(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = tag(")")(input)?;
|
||||
|
||||
fn parse_sequence<'a, E: nom::error::ParseError<&'a str>>(
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, Tree, E> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, nodes) = many1(parse_node)(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, sub_sequences) = many0(parse_tree)(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
Tree {
|
||||
sequence: nodes,
|
||||
sub_sequences,
|
||||
},
|
||||
))
|
||||
Ok((input, node))
|
||||
}
|
||||
|
||||
fn parse_node<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = tag(";")(input)?;
|
||||
let (input, _) = opt(tag(";"))(input)?;
|
||||
let (input, properties) = many1(parse_property)(input)?;
|
||||
Ok((input, Node { properties }))
|
||||
|
||||
let (input, next) = opt(parse_node)(input)?;
|
||||
let (input, mut next_seq) = many0(parse_tree)(input)?;
|
||||
|
||||
let mut next = next.map(|n| vec![n]).unwrap_or(vec![]);
|
||||
next.append(&mut next_seq);
|
||||
|
||||
Ok((input, Node { properties, next }))
|
||||
}
|
||||
|
||||
fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
|
||||
|
@ -219,9 +227,8 @@ pub fn parse_size<'a, E: nom::error::ParseError<&'a str>>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
use super::*;
|
||||
use cool_asserts::assert_matches;
|
||||
|
||||
const EXAMPLE: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c])
|
||||
(;C[d];C[e]))
|
||||
|
@ -259,7 +266,8 @@ mod test {
|
|||
properties: vec![Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["ab".to_owned()]
|
||||
}]
|
||||
}],
|
||||
next: vec![]
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -273,6 +281,25 @@ mod test {
|
|||
properties: vec![Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["ab".to_owned()]
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![Property {
|
||||
ident: "W".to_owned(),
|
||||
values: vec!["dp".to_owned()]
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![
|
||||
Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["pq".to_owned()]
|
||||
},
|
||||
Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["some comments".to_owned()]
|
||||
}
|
||||
],
|
||||
next: vec![],
|
||||
}]
|
||||
}]
|
||||
}
|
||||
);
|
||||
|
@ -286,21 +313,17 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
sequence,
|
||||
Tree {
|
||||
sequence: vec![
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["ab".to_owned()]
|
||||
}]
|
||||
},
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "W".to_owned(),
|
||||
values: vec!["dp".to_owned()]
|
||||
}]
|
||||
},
|
||||
Node {
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "B".to_owned(),
|
||||
values: vec!["ab".to_owned()]
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![Property {
|
||||
ident: "W".to_owned(),
|
||||
values: vec!["dp".to_owned()]
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![
|
||||
Property {
|
||||
ident: "B".to_owned(),
|
||||
|
@ -310,114 +333,158 @@ mod test {
|
|||
ident: "C".to_owned(),
|
||||
values: vec!["some comments".to_owned()]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
sub_sequences: vec![],
|
||||
}
|
||||
],
|
||||
next: vec![],
|
||||
}]
|
||||
}],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_parse_a_sequence_with_subsequences() {
|
||||
fn it_can_parse_a_branching_sequence() {
|
||||
let text = "(;C[a];C[b](;C[c])(;C[d];C[e]))";
|
||||
let (_, sequence) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||
|
||||
let main_sequence = vec![
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["a".to_owned()],
|
||||
}],
|
||||
},
|
||||
Node {
|
||||
let expected = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["a".to_owned()],
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["b".to_owned()],
|
||||
}],
|
||||
},
|
||||
];
|
||||
let subsequence_1 = Tree {
|
||||
sequence: vec![Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["c".to_owned()],
|
||||
}],
|
||||
next: vec![
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["c".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
},
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["d".to_owned()],
|
||||
}],
|
||||
next: vec![Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["e".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
}],
|
||||
},
|
||||
],
|
||||
}],
|
||||
sub_sequences: vec![],
|
||||
};
|
||||
let subsequence_2 = Tree {
|
||||
sequence: vec![
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["d".to_owned()],
|
||||
}],
|
||||
},
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["e".to_owned()],
|
||||
}],
|
||||
},
|
||||
],
|
||||
sub_sequences: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
sequence,
|
||||
Tree {
|
||||
sequence: main_sequence,
|
||||
sub_sequences: vec![subsequence_1, subsequence_2],
|
||||
}
|
||||
);
|
||||
assert_eq!(tree, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_parse_example_1() {
|
||||
let (_, ex_tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
|
||||
assert_eq!(ex_tree.sequence.len(), 1);
|
||||
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
|
||||
|
||||
assert_eq!(ex_tree.sequence[0].properties.len(), 2);
|
||||
assert_eq!(
|
||||
ex_tree.sequence[0].properties[0],
|
||||
Property {
|
||||
ident: "FF".to_owned(),
|
||||
values: vec!["4".to_owned()]
|
||||
}
|
||||
);
|
||||
assert_eq!(ex_tree.sub_sequences.len(), 2);
|
||||
let j = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["j".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
};
|
||||
let i = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["i".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
};
|
||||
let h = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["h".to_owned()],
|
||||
}],
|
||||
next: vec![i],
|
||||
};
|
||||
let g = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["g".to_owned()],
|
||||
}],
|
||||
next: vec![h],
|
||||
};
|
||||
let f = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["f".to_owned()],
|
||||
}],
|
||||
next: vec![g, j],
|
||||
};
|
||||
let e = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["e".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
};
|
||||
let d = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["d".to_owned()],
|
||||
}],
|
||||
next: vec![e],
|
||||
};
|
||||
let c = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["c".to_owned()],
|
||||
}],
|
||||
next: vec![],
|
||||
};
|
||||
let b = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["b".to_owned()],
|
||||
}],
|
||||
next: vec![c, d],
|
||||
};
|
||||
let a = Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["a".to_owned()],
|
||||
}],
|
||||
next: vec![b],
|
||||
};
|
||||
let expected = Node {
|
||||
properties: vec![
|
||||
Property {
|
||||
ident: "FF".to_owned(),
|
||||
values: vec!["4".to_owned()],
|
||||
},
|
||||
Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["root".to_owned()],
|
||||
},
|
||||
],
|
||||
next: vec![a, f],
|
||||
};
|
||||
|
||||
assert_eq!(ex_tree.sub_sequences[0].sequence.len(), 2);
|
||||
assert_eq!(
|
||||
ex_tree.sub_sequences[0].sequence,
|
||||
vec![
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["a".to_owned()]
|
||||
}]
|
||||
},
|
||||
Node {
|
||||
properties: vec![Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["b".to_owned()]
|
||||
}]
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(ex_tree.sub_sequences[0].sub_sequences.len(), 2);
|
||||
assert_eq!(tree, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_regenerate_the_tree() {
|
||||
let (_, tree1) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
|
||||
let tree1 = Tree { root: tree1 };
|
||||
assert_eq!(
|
||||
tree1.to_string(),
|
||||
"(;FF[4]C[root](;C[a];C[b](;C[c])(;C[d];C[e]))(;C[f](;C[g];C[h];C[i])(;C[j])))"
|
||||
);
|
||||
let (_, tree2) = parse_tree::<nom::error::VerboseError<&str>>(&tree1.to_string()).unwrap();
|
||||
assert_eq!(tree1, tree2);
|
||||
assert_eq!(tree1, Tree { root: tree2 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue