Interpret a lot of properties lower in the stack. Move a lot of things around
This commit is contained in:
parent
8070148a99
commit
18216160c9
268
sgf/src/game.rs
268
sgf/src/game.rs
|
@ -1,6 +1,5 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use crate::{Color, Position};
|
||||
use crate::{tree, Color, Position};
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -13,16 +12,82 @@ pub enum SetupError {
|
|||
ConflictingPosition,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ConversionError {
|
||||
IncompatibleNodeType,
|
||||
InvalidPositionSyntax,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Evaluation {
|
||||
Even,
|
||||
GoodForBlack,
|
||||
GoodForWhite,
|
||||
Unclear,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Annotation {
|
||||
BadMove,
|
||||
DoubtfulMove,
|
||||
InterestingMove,
|
||||
Tesuji,
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum NodeProperty {
|
||||
Comment(String),
|
||||
EvenResult(f64),
|
||||
GoodForBlack(f64),
|
||||
GoodForWhite(f64),
|
||||
Hotspot(f64),
|
||||
EvenResult(Double),
|
||||
GoodForBlack(Double),
|
||||
GoodForWhite(Double),
|
||||
Hotspot(Double),
|
||||
NodeName(String),
|
||||
Unclear(f64),
|
||||
Unclear(Double),
|
||||
Value(f64),
|
||||
Unknown((String, String)),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NodeProperties(Vec<NodeProperty>);
|
||||
|
||||
impl NodeProperties {
|
||||
fn property_conflicts(&self, prop: &NodeProperty) -> bool {
|
||||
match prop {
|
||||
NodeProperty::EvenResult(_)
|
||||
| NodeProperty::GoodForBlack(_)
|
||||
| NodeProperty::GoodForWhite(_)
|
||||
| NodeProperty::Unclear(_) => self.contains_exclusive(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_exclusive(&self) -> bool {
|
||||
self.0.iter().any(|prop| match prop {
|
||||
NodeProperty::EvenResult(_)
|
||||
| NodeProperty::GoodForBlack(_)
|
||||
| NodeProperty::GoodForWhite(_)
|
||||
| NodeProperty::Unclear(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MoveProperties> for NodeProperties {
|
||||
fn from(props: &MoveProperties) -> NodeProperties {
|
||||
let props: Vec<NodeProperty> = props
|
||||
.0
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if let MoveProperty::NodeProperty(p) = prop {
|
||||
Some(p.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
NodeProperties(props)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -52,12 +117,31 @@ pub enum MoveProperty {
|
|||
pub struct MoveProperties(Vec<MoveProperty>);
|
||||
|
||||
impl MoveProperties {
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn add_property(&mut self, prop: MoveProperty) -> Result<(), PropertyError> {
|
||||
match prop {
|
||||
MoveProperty::NodeProperty(_) => {}
|
||||
MoveProperty::NodeProperty(node_prop) => {
|
||||
if NodeProperties::from(self).property_conflicts(&node_prop) {
|
||||
self.0.push(prop);
|
||||
} else {
|
||||
return Err(PropertyError::ConflictingProperty);
|
||||
}
|
||||
}
|
||||
MoveProperty::TimingProperty(_) => {}
|
||||
MoveProperty::MoveAnnotationProperty(_) => {
|
||||
if contains_move_annotation_property(&self.0) {
|
||||
if self
|
||||
.0
|
||||
.iter()
|
||||
.filter(|prop| match prop {
|
||||
MoveProperty::MoveAnnotationProperty(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.count()
|
||||
> 0
|
||||
{
|
||||
return Err(PropertyError::ConflictingProperty);
|
||||
}
|
||||
self.0.push(prop);
|
||||
|
@ -70,9 +154,6 @@ impl MoveProperties {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SetupProperty {
|
||||
NodeProperty(NodeProperty),
|
||||
AddBlack(Vec<Position>),
|
||||
AddWhite(Vec<Position>),
|
||||
ClearPoints(Vec<Position>),
|
||||
PlayerTurn(Color),
|
||||
}
|
||||
|
||||
|
@ -81,9 +162,13 @@ pub struct SetupProperties(Vec<SetupProperty>);
|
|||
|
||||
impl SetupProperties {
|
||||
pub fn add_property(&mut self, prop: SetupProperty) {
|
||||
unimplemented!()
|
||||
match prop {
|
||||
SetupProperty::NodeProperty(_) => {}
|
||||
SetupProperty::PlayerTurn(_) => self.0.push(prop),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GameNode {
|
||||
|
@ -142,6 +227,13 @@ impl Node for GameNode {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&tree::Node> for GameNode {
|
||||
type Error = ConversionError;
|
||||
fn try_from(n: &tree::Node) -> Result<Self, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
// Root node
|
||||
pub struct GameTree {
|
||||
children: Vec<GameNode>,
|
||||
|
@ -167,27 +259,37 @@ impl Node for GameTree {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MoveNode {
|
||||
id: Uuid,
|
||||
|
||||
color: Color,
|
||||
position: Position,
|
||||
properties: MoveProperties,
|
||||
children: Vec<GameNode>,
|
||||
|
||||
time_left: Option<Duration>,
|
||||
moves_left: Option<usize>,
|
||||
name: Option<String>,
|
||||
evaluation: Option<Evaluation>,
|
||||
value: Option<f64>,
|
||||
comments: Vec<String>,
|
||||
annotation: Option<Annotation>,
|
||||
unknown_props: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl MoveNode {
|
||||
pub fn new(color: Color, position: Position) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
|
||||
color,
|
||||
position,
|
||||
properties: MoveProperties::default(),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_property(&mut self, prop: MoveProperty) -> Result<(), PropertyError> {
|
||||
self.properties.add_property(prop)
|
||||
time_left: None,
|
||||
moves_left: None,
|
||||
name: None,
|
||||
evaluation: None,
|
||||
value: None,
|
||||
comments: vec![],
|
||||
annotation: None,
|
||||
unknown_props: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,6 +304,31 @@ impl Node for MoveNode {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&tree::Node> for MoveNode {
|
||||
type Error = ConversionError;
|
||||
|
||||
fn try_from(n: &tree::Node) -> Result<Self, Self::Error> {
|
||||
let move_ = match (n.find_prop("W"), n.find_prop("B")) {
|
||||
(Some(white_move), _) => Some((Color::White, Position{ row: white_move
|
||||
(None, Some(black_move)) => unimplemented!(),
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
match move_ {
|
||||
Some((color, position)) => unimplemented!(),
|
||||
None => Err(ConversionError::IncompatibleNodeType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_position(s: &str) -> Result<Position, ConversionError> {
|
||||
if s.len() == 2 {
|
||||
Ok(Position{ row: s[0], column: s[1] })
|
||||
} else {
|
||||
Err(ConversionError::InvalidPositionSyntax)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SetupNode {
|
||||
id: Uuid,
|
||||
|
@ -257,6 +384,7 @@ pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cool_asserts::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn it_can_create_an_empty_game_tree() {
|
||||
|
@ -291,37 +419,47 @@ mod test {
|
|||
assert_eq!(nodes[1].id(), second_move.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_set_up_a_game() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_load_tree_from_sgf() {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_move_annotation_property(lst: &Vec<MoveProperty>) -> bool {
|
||||
lst.iter().any(|item| match item {
|
||||
MoveProperty::MoveAnnotationProperty(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
#[test]
|
||||
fn game_node_can_parse_sgf_move_node() {
|
||||
let n = tree::Node {
|
||||
properties: vec![
|
||||
tree::Property {
|
||||
ident: "W".to_owned(),
|
||||
values: vec!["dp".to_owned()],
|
||||
},
|
||||
tree::Property {
|
||||
ident: "WL".to_owned(),
|
||||
values: vec!["176.099".to_owned()],
|
||||
},
|
||||
tree::Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["Comments in the game".to_owned()],
|
||||
},
|
||||
],
|
||||
next: vec![],
|
||||
};
|
||||
assert_matches!(GameNode::try_from(&n), Ok(GameNode::MoveNode(_)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod root_node_tests {
|
||||
#[test]
|
||||
fn it_rejects_move_properties() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_rejects_setup_properties() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_parse_a_root_sgf() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -332,48 +470,38 @@ mod move_node_tests {
|
|||
use super::*;
|
||||
use cool_asserts::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn it_rejects_setup_properties() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_rejects_root_properties() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_parse_an_sgf_move_node() {
|
||||
unimplemented!()
|
||||
let n = tree::Node {
|
||||
properties: vec![
|
||||
tree::Property {
|
||||
ident: "W".to_owned(),
|
||||
values: vec!["dp".to_owned()],
|
||||
},
|
||||
tree::Property {
|
||||
ident: "WL".to_owned(),
|
||||
values: vec!["176.099".to_owned()],
|
||||
},
|
||||
tree::Property {
|
||||
ident: "C".to_owned(),
|
||||
values: vec!["Comments in the game".to_owned()],
|
||||
},
|
||||
],
|
||||
next: vec![],
|
||||
};
|
||||
assert_matches!(MoveNode::try_from(&n), Ok(node) => {
|
||||
assert_eq!(node.color, Color::White);
|
||||
assert_eq!(node.position, Position{ row: 'd', column: 'p' });
|
||||
assert_eq!(node.children, vec![]);
|
||||
assert_eq!(node.time_left, Some(Duration::from_secs(176)));
|
||||
assert_eq!(node.comments, vec!["Comments in the game".to_owned()]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_rejects_an_sgf_setup_property() {
|
||||
fn it_rejects_an_sgf_setup_node() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_prevents_multiple_move_annotation_properties() {
|
||||
let mut node = MoveNode::new(
|
||||
Color::Black,
|
||||
Position {
|
||||
row: 'a',
|
||||
column: 'b',
|
||||
},
|
||||
);
|
||||
assert_matches!(
|
||||
node.add_property(MoveProperty::MoveAnnotationProperty(
|
||||
MoveAnnotationProperty::BadMove(5.),
|
||||
)),
|
||||
Ok(_)
|
||||
);
|
||||
assert_matches!(
|
||||
node.add_property(MoveProperty::MoveAnnotationProperty(
|
||||
MoveAnnotationProperty::Tesuji(5.),
|
||||
)),
|
||||
Err(PropertyError::ConflictingProperty)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -381,7 +509,6 @@ mod setup_node_tests {
|
|||
use super::*;
|
||||
use cool_asserts::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn it_can_parse_an_sgf_setup_node() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -438,17 +565,14 @@ mod setup_node_tests {
|
|||
|
||||
#[cfg(test)]
|
||||
mod path_test {
|
||||
#[test]
|
||||
fn returns_empty_list_if_no_game_nodes() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_empty_list_if_node_not_found() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_excludes_root_node() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -233,49 +233,7 @@ pub struct GameInfo {
|
|||
pub result: Option<GameResult>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GameResult {
|
||||
Annulled,
|
||||
Draw,
|
||||
Black(Win),
|
||||
White(Win),
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for GameResult {
|
||||
type Error = String;
|
||||
fn try_from(s: &str) -> Result<GameResult, Self::Error> {
|
||||
if s == "0" {
|
||||
Ok(GameResult::Draw)
|
||||
} else if s == "Void" {
|
||||
Ok(GameResult::Annulled)
|
||||
} else {
|
||||
let parts = s.split("+").collect::<Vec<&str>>();
|
||||
let res = match parts[0].to_ascii_lowercase().as_str() {
|
||||
"b" => GameResult::Black,
|
||||
"w" => GameResult::White,
|
||||
_ => return Ok(GameResult::Unknown(parts[0].to_owned())),
|
||||
};
|
||||
match parts[1].to_ascii_lowercase().as_str() {
|
||||
"r" | "resign" => Ok(res(Win::Resignation)),
|
||||
"t" | "time" => Ok(res(Win::Time)),
|
||||
"f" | "forfeit" => Ok(res(Win::Forfeit)),
|
||||
_ => {
|
||||
let score = parts[1].parse::<f32>().unwrap();
|
||||
Ok(res(Win::Score(score)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Win {
|
||||
Score(f32),
|
||||
Resignation,
|
||||
Forfeit,
|
||||
Time,
|
||||
}
|
||||
|
||||
/*
|
||||
enum PropType {
|
||||
|
|
|
@ -10,62 +10,8 @@ use tree::parse_collection;
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidField,
|
||||
InvalidBoardSize,
|
||||
Incomplete,
|
||||
InvalidSgf(VerboseNomError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
White,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Position(String);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerboseNomError(nom::error::VerboseError<String>);
|
||||
|
||||
impl From<nom::error::VerboseError<&str>> for VerboseNomError {
|
||||
fn from(err: nom::error::VerboseError<&str>) -> Self {
|
||||
VerboseNomError(nom::error::VerboseError {
|
||||
errors: err
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| (err.0.to_owned(), err.1))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nom::Err<nom::error::VerboseError<&str>>> for Error {
|
||||
fn from(err: nom::Err<nom::error::VerboseError<&str>>) -> Self {
|
||||
match err {
|
||||
nom::Err::Incomplete(_) => Error::Incomplete,
|
||||
nom::Err::Error(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
nom::Err::Failure(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum ParseError {
|
||||
#[error("An unknown error was found")]
|
||||
NomError(nom::error::Error<String>),
|
||||
}
|
||||
|
||||
impl From<nom::error::Error<&str>> for ParseError {
|
||||
fn from(err: nom::error::Error<&str>) -> Self {
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
/*
|
||||
pub enum Game {
|
||||
|
|
434
sgf/src/tree.rs
434
sgf/src/tree.rs
|
@ -1,4 +1,4 @@
|
|||
use crate::{Color, Error, Position};
|
||||
use crate::{Color, Error, GameResult};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{escaped_transform, tag},
|
||||
|
@ -27,6 +27,153 @@ impl From<ParseIntError> for ParseSizeError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GameType {
|
||||
Go,
|
||||
Othello,
|
||||
Chess,
|
||||
GomokuRenju,
|
||||
NineMensMorris,
|
||||
Backgammon,
|
||||
ChineseChess,
|
||||
Shogi,
|
||||
LinesOfAction,
|
||||
Ataxx,
|
||||
Hex,
|
||||
Jungle,
|
||||
Neutron,
|
||||
PhilosophersFootball,
|
||||
Quadrature,
|
||||
Trax,
|
||||
Tantrix,
|
||||
Amazons,
|
||||
Octi,
|
||||
Gess,
|
||||
Twixt,
|
||||
Zertz,
|
||||
Plateau,
|
||||
Yinsh,
|
||||
Punct,
|
||||
Gobblet,
|
||||
Hive,
|
||||
Exxit,
|
||||
Hnefatal,
|
||||
Kuba,
|
||||
Tripples,
|
||||
Chase,
|
||||
TumblingDown,
|
||||
Sahara,
|
||||
Byte,
|
||||
Focus,
|
||||
Dvonn,
|
||||
Tamsk,
|
||||
Gipf,
|
||||
Kropki,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<&str> for GameType {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"1" => Self::Go,
|
||||
"2" => Self::Othello,
|
||||
"3" => Self::Chess,
|
||||
"4" => Self::GomokuRenju,
|
||||
"5" => Self::NineMensMorris,
|
||||
"6" => Self::Backgammon,
|
||||
"7" => Self::ChineseChess,
|
||||
"8" => Self::Shogi,
|
||||
"9" => Self::LinesOfAction,
|
||||
"10" => Self::Ataxx,
|
||||
"11" => Self::Hex,
|
||||
"12" => Self::Jungle,
|
||||
"13" => Self::Neutron,
|
||||
"14" => Self::PhilosophersFootball,
|
||||
"15" => Self::Quadrature,
|
||||
"16" => Self::Trax,
|
||||
"17" => Self::Tantrix,
|
||||
"18" => Self::Amazons,
|
||||
"19" => Self::Octi,
|
||||
"20" => Self::Gess,
|
||||
"21" => Self::Twixt,
|
||||
"22" => Self::Zertz,
|
||||
"23" => Self::Plateau,
|
||||
"24" => Self::Yinsh,
|
||||
"25" => Self::Punct,
|
||||
"26" => Self::Gobblet,
|
||||
"27" => Self::Hive,
|
||||
"28" => Self::Exxit,
|
||||
"29" => Self::Hnefatal,
|
||||
"30" => Self::Kuba,
|
||||
"31" => Self::Tripples,
|
||||
"32" => Self::Chase,
|
||||
"33" => Self::TumblingDown,
|
||||
"34" => Self::Sahara,
|
||||
"35" => Self::Byte,
|
||||
"36" => Self::Focus,
|
||||
"37" => Self::Dvonn,
|
||||
"38" => Self::Tamsk,
|
||||
"39" => Self::Gipf,
|
||||
"40" => Self::Kropki,
|
||||
_ => Self::Other(s.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GameType> for String {
|
||||
fn from(g: &GameType) -> String {
|
||||
match g {
|
||||
GameType::Go => "1".to_owned(),
|
||||
GameType::Othello => "2".to_owned(),
|
||||
GameType::Chess => "3".to_owned(),
|
||||
GameType::GomokuRenju => "4".to_owned(),
|
||||
GameType::NineMensMorris => "5".to_owned(),
|
||||
GameType::Backgammon => "6".to_owned(),
|
||||
GameType::ChineseChess => "7".to_owned(),
|
||||
GameType::Shogi => "8".to_owned(),
|
||||
GameType::LinesOfAction => "9".to_owned(),
|
||||
GameType::Ataxx => "10".to_owned(),
|
||||
GameType::Hex => "11".to_owned(),
|
||||
GameType::Jungle => "12".to_owned(),
|
||||
GameType::Neutron => "13".to_owned(),
|
||||
GameType::PhilosophersFootball => "14".to_owned(),
|
||||
GameType::Quadrature => "15".to_owned(),
|
||||
GameType::Trax => "16".to_owned(),
|
||||
GameType::Tantrix => "17".to_owned(),
|
||||
GameType::Amazons => "18".to_owned(),
|
||||
GameType::Octi => "19".to_owned(),
|
||||
GameType::Gess => "20".to_owned(),
|
||||
GameType::Twixt => "21".to_owned(),
|
||||
GameType::Zertz => "22".to_owned(),
|
||||
GameType::Plateau => "23".to_owned(),
|
||||
GameType::Yinsh => "24".to_owned(),
|
||||
GameType::Punct => "25".to_owned(),
|
||||
GameType::Gobblet => "26".to_owned(),
|
||||
GameType::Hive => "27".to_owned(),
|
||||
GameType::Exxit => "28".to_owned(),
|
||||
GameType::Hnefatal => "29".to_owned(),
|
||||
GameType::Kuba => "30".to_owned(),
|
||||
GameType::Tripples => "31".to_owned(),
|
||||
GameType::Chase => "32".to_owned(),
|
||||
GameType::TumblingDown => "33".to_owned(),
|
||||
GameType::Sahara => "34".to_owned(),
|
||||
GameType::Byte => "35".to_owned(),
|
||||
GameType::Focus => "36".to_owned(),
|
||||
GameType::Dvonn => "37".to_owned(),
|
||||
GameType::Tamsk => "38".to_owned(),
|
||||
GameType::Gipf => "39".to_owned(),
|
||||
GameType::Kropki => "40".to_owned(),
|
||||
GameType::Other(v) => v.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for GameType {
|
||||
fn to_string(&self) -> String {
|
||||
String::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Size {
|
||||
pub width: i32,
|
||||
|
@ -51,6 +198,22 @@ impl TryFrom<&str> for Size {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Position(String);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PositionList(Vec<Position>);
|
||||
|
||||
impl PositionList {
|
||||
pub fn compressed_list(&self) -> String {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|v| v.0.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(":")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Tree {
|
||||
pub root: Node,
|
||||
|
@ -110,21 +273,7 @@ impl Node {
|
|||
|
||||
// KO
|
||||
// MN
|
||||
// AB
|
||||
// AE
|
||||
// AW
|
||||
// PL
|
||||
// DM
|
||||
// GB
|
||||
// GW
|
||||
// HO
|
||||
// N
|
||||
// UC
|
||||
// V
|
||||
// BM
|
||||
// DO
|
||||
// IT
|
||||
// TE
|
||||
// AR
|
||||
// CR
|
||||
// DD
|
||||
|
@ -134,33 +283,6 @@ impl Node {
|
|||
// SL
|
||||
// SQ
|
||||
// TR
|
||||
// AP
|
||||
// CA
|
||||
// FF
|
||||
// GM
|
||||
// ST
|
||||
// SZ
|
||||
// AN
|
||||
// BR
|
||||
// BT
|
||||
// CP
|
||||
// DT
|
||||
// EV
|
||||
// GN
|
||||
// GC
|
||||
// ON
|
||||
// OT
|
||||
// PB
|
||||
// PC
|
||||
// PW
|
||||
// RE
|
||||
// RO
|
||||
// RU
|
||||
// SO
|
||||
// TM
|
||||
// US
|
||||
// WR
|
||||
// WT
|
||||
// BL
|
||||
// OB
|
||||
// OW
|
||||
|
@ -176,6 +298,129 @@ pub enum Property {
|
|||
// C
|
||||
Comment(Vec<String>),
|
||||
|
||||
// BM
|
||||
BadMove,
|
||||
|
||||
// DO
|
||||
DoubtfulMove,
|
||||
|
||||
// IT
|
||||
InterestingMove,
|
||||
|
||||
// TE
|
||||
Tesuji,
|
||||
|
||||
// AP
|
||||
Application(String),
|
||||
|
||||
// CA
|
||||
Charset(String),
|
||||
|
||||
// FF
|
||||
FileFormat(u8),
|
||||
|
||||
// GM
|
||||
GameType(GameType),
|
||||
|
||||
// ST
|
||||
VariationDisplay,
|
||||
|
||||
// SZ
|
||||
BoardSize(Size),
|
||||
|
||||
// AB
|
||||
SetupBlackStones(PositionList),
|
||||
|
||||
// AE
|
||||
ClearStones(PositionList),
|
||||
|
||||
// AW
|
||||
SetupWhiteStones(PositionList),
|
||||
|
||||
// PL
|
||||
NextPlayer(Color),
|
||||
|
||||
// DM
|
||||
EvenResult,
|
||||
|
||||
// GB
|
||||
GoodForBlack,
|
||||
|
||||
// GW
|
||||
GoodForWhite,
|
||||
|
||||
// UC
|
||||
UnclearResult,
|
||||
|
||||
// HO
|
||||
Hotspot,
|
||||
|
||||
// V
|
||||
Value(f32),
|
||||
|
||||
// AN
|
||||
Annotator(String),
|
||||
|
||||
// BR
|
||||
BlackRank(String),
|
||||
|
||||
// BT
|
||||
BlackTeam(String),
|
||||
|
||||
// CP
|
||||
Copyright(String),
|
||||
|
||||
// DT
|
||||
EventDates(Vec<chrono::NaiveDate>),
|
||||
|
||||
// EV
|
||||
EventName(String),
|
||||
|
||||
// GN
|
||||
GameName(String),
|
||||
|
||||
// GC
|
||||
ExtraGameInformation(String),
|
||||
|
||||
// ON
|
||||
GameOpening(String),
|
||||
|
||||
// OT
|
||||
Overtime(String),
|
||||
|
||||
// PB
|
||||
BlackPlayer(String),
|
||||
|
||||
// PC
|
||||
GameLocation(String),
|
||||
|
||||
// PW
|
||||
WhitePlayer(String),
|
||||
|
||||
// RE
|
||||
Result(GameResult),
|
||||
|
||||
// RO
|
||||
Round(String),
|
||||
|
||||
// RU
|
||||
Ruleset(String),
|
||||
|
||||
// SO
|
||||
Source(String),
|
||||
|
||||
// TM
|
||||
TimeLimit(std::time::Duration),
|
||||
|
||||
// US
|
||||
User(String),
|
||||
|
||||
// WR
|
||||
WhiteRank(String),
|
||||
|
||||
// WT
|
||||
WhiteTeam(String),
|
||||
|
||||
Unknown(UnknownProperty),
|
||||
}
|
||||
|
||||
|
@ -196,14 +441,9 @@ pub struct Property {
|
|||
impl ToString for Property {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Property::Move((color, position)) => format!(
|
||||
"{}[{}]",
|
||||
match color {
|
||||
Color::White => "W",
|
||||
Color::Black => "B",
|
||||
},
|
||||
position.0
|
||||
),
|
||||
Property::Move((color, position)) => {
|
||||
format!("{}[{}]", color.abbreviation(), position.0)
|
||||
}
|
||||
Property::Comment(values) => format!(
|
||||
"C{}",
|
||||
values
|
||||
|
@ -211,6 +451,59 @@ impl ToString for Property {
|
|||
.map(|v| format!("[{}]", v))
|
||||
.collect::<String>()
|
||||
),
|
||||
Property::BadMove => "BM[]".to_owned(),
|
||||
Property::DoubtfulMove => "DO[]".to_owned(),
|
||||
Property::InterestingMove => "IT[]".to_owned(),
|
||||
Property::Tesuji => "TE[]".to_owned(),
|
||||
Property::Application(app) => format!("AP[{}]", app),
|
||||
Property::Charset(set) => format!("CA[{}]", set),
|
||||
Property::FileFormat(ff) => format!("FF[{}]", ff),
|
||||
Property::GameType(gt) => format!("GM[{}]", gt.to_string()),
|
||||
Property::VariationDisplay => unimplemented!(),
|
||||
Property::BoardSize(Size { width, height }) => {
|
||||
if width == height {
|
||||
format!("SZ[{}]", width)
|
||||
} else {
|
||||
format!("SZ[{}:{}]", width, height)
|
||||
}
|
||||
}
|
||||
Property::SetupBlackStones(positions) => {
|
||||
format!("AB[{}]", positions.compressed_list(),)
|
||||
}
|
||||
Property::ClearStones(positions) => {
|
||||
format!("AE[{}]", positions.compressed_list(),)
|
||||
}
|
||||
Property::SetupWhiteStones(positions) => {
|
||||
format!("AW[{}]", positions.compressed_list(),)
|
||||
}
|
||||
Property::NextPlayer(color) => format!("PL[{}]", color.abbreviation()),
|
||||
Property::EvenResult => "DM[]".to_owned(),
|
||||
Property::GoodForBlack => "GB[]".to_owned(),
|
||||
Property::GoodForWhite => "GW[]".to_owned(),
|
||||
Property::UnclearResult => "UC[]".to_owned(),
|
||||
Property::Hotspot => "HO[]".to_owned(),
|
||||
Property::Value(value) => format!("V[{}]", value),
|
||||
Property::Annotator(value) => format!("AN[{}]", value),
|
||||
Property::BlackRank(value) => format!("BR[{}]", value),
|
||||
Property::BlackTeam(value) => format!("BT[{}]", value),
|
||||
Property::Copyright(value) => format!("CP[{}]", value),
|
||||
Property::EventDates(_) => unimplemented!(),
|
||||
Property::EventName(value) => format!("EV[{}]", value),
|
||||
Property::GameName(value) => format!("GN[{}]", value),
|
||||
Property::ExtraGameInformation(value) => format!("GC[{}]", value),
|
||||
Property::GameOpening(value) => format!("ON[{}]", value),
|
||||
Property::Overtime(value) => format!("OT[{}]", value),
|
||||
Property::BlackPlayer(value) => format!("PB[{}]", value),
|
||||
Property::GameLocation(value) => format!("PC[{}]", value),
|
||||
Property::WhitePlayer(value) => format!("PW[{}]", value),
|
||||
Property::Result(_) => unimplemented!(),
|
||||
Property::Round(value) => format!("RO[{}]", value),
|
||||
Property::Ruleset(value) => format!("RU[{}]", value),
|
||||
Property::Source(value) => format!("SO[{}]", value),
|
||||
Property::TimeLimit(value) => format!("TM[{}]", value.as_secs()),
|
||||
Property::User(value) => format!("US[{}]", value),
|
||||
Property::WhiteRank(value) => format!("WR[{}]", value),
|
||||
Property::WhiteTeam(value) => format!("WT[{}]", value),
|
||||
Property::Unknown(UnknownProperty { ident, values }) => {
|
||||
let values = values
|
||||
.iter()
|
||||
|
@ -282,6 +575,42 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
|
|||
"W" => Property::Move((Color::White, Position(values[0].clone()))),
|
||||
"B" => Property::Move((Color::Black, Position(values[0].clone()))),
|
||||
"C" => Property::Comment(values),
|
||||
"BM" => Property::BadMove,
|
||||
"DO" => Property::DoubtfulMove,
|
||||
"IT" => Property::InterestingMove,
|
||||
"TE" => Property::Tesuji,
|
||||
"AP" => Property::Application(values.join(",")),
|
||||
"CA" => Property::Charset(values.join(",")),
|
||||
"FF" => Property::FileFormat(values.join("").parse::<u8>().unwrap()),
|
||||
"GM" => Property::GameType(GameType::from(values.join("").as_ref())),
|
||||
"ST" => unimplemented!(),
|
||||
"SZ" => Property::BoardSize(Size::try_from(values.join("").as_ref()).unwrap()),
|
||||
"DM" => Property::EvenResult,
|
||||
"GB" => Property::GoodForBlack,
|
||||
"GW" => Property::GoodForWhite,
|
||||
"UC" => Property::UnclearResult,
|
||||
"V" => Property::Value(values.join("").parse::<f32>().unwrap()),
|
||||
"AN" => Property::Annotator(values.join("")),
|
||||
"BR" => Property::BlackRank(values.join("")),
|
||||
"BT" => Property::BlackTeam(values.join("")),
|
||||
"CP" => Property::Copyright(values.join("")),
|
||||
"DT" => unimplemented!(),
|
||||
"EV" => Property::EventName(values.join("")),
|
||||
"GN" => Property::GameName(values.join("")),
|
||||
"GC" => Property::ExtraGameInformation(values.join("")),
|
||||
"ON" => Property::GameOpening(values.join("")),
|
||||
"OT" => Property::Overtime(values.join("")),
|
||||
"PB" => Property::BlackPlayer(values.join("")),
|
||||
"PC" => Property::GameLocation(values.join("")),
|
||||
"PW" => Property::WhitePlayer(values.join("")),
|
||||
"RE" => Property::Result(GameResult::try_from(values.join("").as_ref()).unwrap()),
|
||||
"RO" => Property::Round(values.join("")),
|
||||
"RU" => Property::Ruleset(values.join("")),
|
||||
"SO" => Property::Source(values.join("")),
|
||||
"TM" => unimplemented!(),
|
||||
"US" => Property::User(values.join("")),
|
||||
"WR" => Property::WhiteRank(values.join("")),
|
||||
"WT" => Property::WhiteTeam(values.join("")),
|
||||
_ => Property::Unknown(UnknownProperty {
|
||||
ident: ident.to_owned(),
|
||||
values,
|
||||
|
@ -485,10 +814,7 @@ mod test {
|
|||
};
|
||||
let expected = Node {
|
||||
properties: vec![
|
||||
Property::Unknown(UnknownProperty {
|
||||
ident: "FF".to_owned(),
|
||||
values: vec!["4".to_owned()],
|
||||
}),
|
||||
Property::FileFormat(4),
|
||||
Property::Comment(vec!["root".to_owned()]),
|
||||
],
|
||||
next: vec![a, f],
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidField,
|
||||
InvalidBoardSize,
|
||||
Incomplete,
|
||||
InvalidSgf(VerboseNomError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerboseNomError(nom::error::VerboseError<String>);
|
||||
|
||||
impl From<nom::error::VerboseError<&str>> for VerboseNomError {
|
||||
fn from(err: nom::error::VerboseError<&str>) -> Self {
|
||||
VerboseNomError(nom::error::VerboseError {
|
||||
errors: err
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| (err.0.to_owned(), err.1))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nom::Err<nom::error::VerboseError<&str>>> for Error {
|
||||
fn from(err: nom::Err<nom::error::VerboseError<&str>>) -> Self {
|
||||
match err {
|
||||
nom::Err::Incomplete(_) => Error::Incomplete,
|
||||
nom::Err::Error(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
nom::Err::Failure(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum ParseError {
|
||||
#[error("An unknown error was found")]
|
||||
NomError(nom::error::Error<String>),
|
||||
}
|
||||
|
||||
impl From<nom::error::Error<&str>> for ParseError {
|
||||
fn from(err: nom::error::Error<&str>) -> Self {
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
White,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn abbreviation(&self) -> String {
|
||||
match self {
|
||||
Color::White => "W",
|
||||
Color::Black => "B",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GameResult {
|
||||
Draw,
|
||||
Black(Win),
|
||||
White(Win),
|
||||
Void,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for GameResult {
|
||||
type Error = String;
|
||||
fn try_from(s: &str) -> Result<GameResult, Self::Error> {
|
||||
if s == "0" {
|
||||
Ok(GameResult::Draw)
|
||||
} else if s == "Void" {
|
||||
Ok(GameResult::Void)
|
||||
} else {
|
||||
let parts = s.split("+").collect::<Vec<&str>>();
|
||||
let res = match parts[0].to_ascii_lowercase().as_str() {
|
||||
"b" => GameResult::Black,
|
||||
"w" => GameResult::White,
|
||||
_ => return Ok(GameResult::Unknown(parts[0].to_owned())),
|
||||
};
|
||||
match parts[1].to_ascii_lowercase().as_str() {
|
||||
"r" | "resign" => Ok(res(Win::Resignation)),
|
||||
"t" | "time" => Ok(res(Win::Time)),
|
||||
"f" | "forfeit" => Ok(res(Win::Forfeit)),
|
||||
_ => {
|
||||
let score = parts[1].parse::<f32>().unwrap();
|
||||
Ok(res(Win::Score(score)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Win {
|
||||
Score(f32),
|
||||
Resignation,
|
||||
Forfeit,
|
||||
Time,
|
||||
}
|
Loading…
Reference in New Issue