Compare commits

..

No commits in common. "18216160c9cd7edbd9bdd97e488da349d24042fa" and "aa053cd10fd5d2a5774cd8876b4a8a755900acd5" have entirely different histories.

6 changed files with 298 additions and 938 deletions

View File

@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
cool_asserts = { version = "*" }
chrono = { version = "0.4", features = [ "serde" ] } chrono = { version = "0.4", features = [ "serde" ] }
nom = { version = "7" } nom = { version = "7" }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }

View File

@ -1,175 +1,6 @@
use crate::{tree, Color, Position}; use crate::{Color, Position};
use std::{collections::HashSet, time::Duration};
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyError {
ConflictingProperty,
}
#[derive(Clone, Debug, PartialEq)]
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(Double),
GoodForBlack(Double),
GoodForWhite(Double),
Hotspot(Double),
NodeName(String),
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)]
pub enum TimingProperty {
BlackTimeLeft(f64),
BlackMovesLeft(f64),
WhiteMovesLeft(f64),
WhiteTimeLeft(f64),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MoveAnnotationProperty {
BadMove(f64),
DoubtfulMove(f64),
InterestingMove(f64),
Tesuji(f64),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MoveProperty {
NodeProperty(NodeProperty),
TimingProperty(TimingProperty),
MoveAnnotationProperty(MoveAnnotationProperty),
}
#[derive(Clone, Debug, PartialEq, Default)]
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(node_prop) => {
if NodeProperties::from(self).property_conflicts(&node_prop) {
self.0.push(prop);
} else {
return Err(PropertyError::ConflictingProperty);
}
}
MoveProperty::TimingProperty(_) => {}
MoveProperty::MoveAnnotationProperty(_) => {
if self
.0
.iter()
.filter(|prop| match prop {
MoveProperty::MoveAnnotationProperty(_) => true,
_ => false,
})
.count()
> 0
{
return Err(PropertyError::ConflictingProperty);
}
self.0.push(prop);
}
}
return Ok(());
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SetupProperty {
NodeProperty(NodeProperty),
PlayerTurn(Color),
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct SetupProperties(Vec<SetupProperty>);
impl SetupProperties {
pub fn add_property(&mut self, prop: SetupProperty) {
match prop {
SetupProperty::NodeProperty(_) => {}
SetupProperty::PlayerTurn(_) => self.0.push(prop),
}
}
}
*/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum GameNode { pub enum GameNode {
MoveNode(MoveNode), MoveNode(MoveNode),
@ -177,6 +8,8 @@ pub enum GameNode {
} }
pub trait Node { pub trait Node {
fn children<'a>(&'a self) -> Vec<&'a GameNode>;
/// Provide a pre-order traversal of all of the nodes in the game tree. /// Provide a pre-order traversal of all of the nodes in the game tree.
fn nodes<'a>(&'a self) -> Vec<&'a GameNode> { fn nodes<'a>(&'a self) -> Vec<&'a GameNode> {
self.children() self.children()
@ -191,8 +24,8 @@ pub trait Node {
.collect::<Vec<&'a GameNode>>() .collect::<Vec<&'a GameNode>>()
} }
fn children<'a>(&'a self) -> Vec<&'a GameNode>;
fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode; fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode;
fn add_annotation(&mut self, label: String, note: String);
} }
impl GameNode { impl GameNode {
@ -202,6 +35,10 @@ impl GameNode {
GameNode::SetupNode(node) => node.id, GameNode::SetupNode(node) => node.id,
} }
} }
fn children<'a>(&'a self) -> Vec<&'a GameNode> {
unimplemented!("GameNode::children")
}
} }
impl Node for GameNode { impl Node for GameNode {
@ -225,11 +62,8 @@ impl Node for GameNode {
GameNode::SetupNode(node) => node.add_child(new_node), GameNode::SetupNode(node) => node.add_child(new_node),
} }
} }
}
impl TryFrom<&tree::Node> for GameNode { fn add_annotation(&mut self, label: String, note: String) {
type Error = ConversionError;
fn try_from(n: &tree::Node) -> Result<Self, Self::Error> {
unimplemented!() unimplemented!()
} }
} }
@ -243,6 +77,11 @@ impl GameTree {
fn new() -> Self { fn new() -> Self {
Self { children: vec![] } Self { children: vec![] }
} }
fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode {
self.children.push(node);
self.children.last_mut().unwrap()
}
} }
impl Node for GameTree { impl Node for GameTree {
@ -251,44 +90,33 @@ impl Node for GameTree {
} }
fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode { fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode {
self.children.push(node); unimplemented!()
self.children.last_mut().unwrap() }
fn add_annotation(&mut self, label: String, note: String) {
unimplemented!()
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct MoveNode { pub struct MoveNode {
id: Uuid, id: Uuid,
color: Color, color: Color,
position: Position, position: Position,
annotations: Vec<(String, String)>,
children: Vec<GameNode>, 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 { impl MoveNode {
pub fn new(color: Color, position: Position) -> Self { pub fn new(color: Color, position: Position) -> Self {
Self { Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
color, color,
position, position,
annotations: Vec::new(),
children: Vec::new(), children: Vec::new(),
time_left: None,
moves_left: None,
name: None,
evaluation: None,
value: None,
comments: vec![],
annotation: None,
unknown_props: vec![],
} }
} }
} }
@ -302,30 +130,9 @@ impl Node for MoveNode {
self.children.push(node); self.children.push(node);
self.children.last_mut().unwrap() self.children.last_mut().unwrap()
} }
}
impl TryFrom<&tree::Node> for MoveNode { fn add_annotation(&mut self, label: String, note: String) {
type Error = ConversionError; unimplemented!()
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)
} }
} }
@ -333,25 +140,19 @@ fn parse_position(s: &str) -> Result<Position, ConversionError> {
pub struct SetupNode { pub struct SetupNode {
id: Uuid, id: Uuid,
positions: Vec<(Option<Color>, Position)>, positions: Vec<(Color, Position)>,
annotations: Vec<(String, String)>,
children: Vec<GameNode>, children: Vec<GameNode>,
} }
impl SetupNode { impl SetupNode {
pub fn new(positions: Vec<(Option<Color>, Position)>) -> Result<Self, SetupError> { pub fn new(positions: Vec<(Color, Position)>) -> Self {
let mut coords: HashSet<Position> = HashSet::new(); Self {
for coord in positions.iter().map(|p| p.1.clone()) {
if coords.contains(&coord) {
return Err(SetupError::ConflictingPosition);
}
coords.insert(coord);
}
Ok(Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
positions, positions,
annotations: Vec::new(),
children: Vec::new(), children: Vec::new(),
}) }
} }
} }
@ -363,6 +164,10 @@ impl Node for SetupNode {
fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode { fn add_child<'a>(&'a mut self, node: GameNode) -> &'a mut GameNode {
unimplemented!() unimplemented!()
} }
fn add_annotation(&mut self, label: String, note: String) {
unimplemented!()
}
} }
pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> { pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> {
@ -384,7 +189,6 @@ pub fn path_to_node<'a>(node: &'a GameNode, id: Uuid) -> Vec<&'a GameNode> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use cool_asserts::assert_matches;
#[test] #[test]
fn it_can_create_an_empty_game_tree() { fn it_can_create_an_empty_game_tree() {
@ -396,21 +200,9 @@ mod test {
fn it_can_add_moves_to_a_game() { fn it_can_add_moves_to_a_game() {
let mut tree = GameTree::new(); let mut tree = GameTree::new();
let first_move = MoveNode::new( let first_move = MoveNode::new(Color::Black, Position {});
Color::Black,
Position {
row: 'd',
column: 'd',
},
);
let first_ = tree.add_child(GameNode::MoveNode(first_move.clone())); let first_ = tree.add_child(GameNode::MoveNode(first_move.clone()));
let second_move = MoveNode::new( let second_move = MoveNode::new(Color::White, Position {});
Color::White,
Position {
row: 'q',
column: 'q',
},
);
first_.add_child(GameNode::MoveNode(second_move.clone())); first_.add_child(GameNode::MoveNode(second_move.clone()));
let nodes = tree.nodes(); let nodes = tree.nodes();
@ -419,47 +211,30 @@ mod test {
assert_eq!(nodes[1].id(), second_move.id); assert_eq!(nodes[1].id(), second_move.id);
} }
#[test]
fn it_can_set_up_a_game() { fn it_can_set_up_a_game() {
unimplemented!() unimplemented!()
} }
#[test]
fn it_can_load_tree_from_sgf() { fn it_can_load_tree_from_sgf() {
unimplemented!() unimplemented!()
} }
#[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)] #[cfg(test)]
mod root_node_tests { mod root_node_tests {
#[test]
fn it_rejects_move_properties() { fn it_rejects_move_properties() {
unimplemented!() unimplemented!()
} }
#[test]
fn it_rejects_setup_properties() { fn it_rejects_setup_properties() {
unimplemented!() unimplemented!()
} }
#[test]
fn it_can_parse_a_root_sgf() { fn it_can_parse_a_root_sgf() {
unimplemented!() unimplemented!()
} }
@ -467,35 +242,19 @@ mod root_node_tests {
#[cfg(test)] #[cfg(test)]
mod move_node_tests { mod move_node_tests {
use super::*; #[test]
use cool_asserts::assert_matches; fn it_rejects_setup_properties() {
unimplemented!()
}
#[test]
fn it_rejects_root_properties() {
unimplemented!()
}
#[test] #[test]
fn it_can_parse_an_sgf_move_node() { fn it_can_parse_an_sgf_move_node() {
let n = tree::Node { unimplemented!()
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] #[test]
@ -506,73 +265,40 @@ mod move_node_tests {
#[cfg(test)] #[cfg(test)]
mod setup_node_tests { mod setup_node_tests {
use super::*; #[test]
use cool_asserts::assert_matches; fn it_rejects_move_properties() {
unimplemented!()
}
#[test]
fn it_rejects_root_properties() {
unimplemented!()
}
#[test]
fn it_can_parse_an_sgf_setup_node() { fn it_can_parse_an_sgf_setup_node() {
unimplemented!() unimplemented!()
} }
#[test] #[test]
fn it_rejects_conflicting_placement_properties() { fn it_rejects_an_sgf_move_node() {
assert_matches!( unimplemented!()
SetupNode::new(vec![
(
Some(Color::Black),
Position {
row: 'd',
column: 'd',
},
),
(
Some(Color::Black),
Position {
row: 'd',
column: 'd',
},
),
]),
Err(SetupError::ConflictingPosition)
);
assert_matches!(
SetupNode::new(vec![
(
Some(Color::Black),
Position {
row: 'd',
column: 'd',
},
),
(
Some(Color::Black),
Position {
row: 'e',
column: 'e',
},
),
(
Some(Color::White),
Position {
row: 'e',
column: 'e',
},
),
]),
Err(SetupError::ConflictingPosition)
);
} }
} }
#[cfg(test)] #[cfg(test)]
mod path_test { mod path_test {
#[test]
fn returns_empty_list_if_no_game_nodes() { fn returns_empty_list_if_no_game_nodes() {
unimplemented!() unimplemented!()
} }
#[test]
fn returns_empty_list_if_node_not_found() { fn returns_empty_list_if_node_not_found() {
unimplemented!() unimplemented!()
} }
#[test]
fn path_excludes_root_node() { fn path_excludes_root_node() {
unimplemented!() unimplemented!()
} }

View File

@ -233,7 +233,49 @@ pub struct GameInfo {
pub result: Option<GameResult>, 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 { enum PropType {

View File

@ -1,19 +1,72 @@
mod date; mod date;
pub use date::Date; pub use date::Date;
// pub mod go; pub mod go;
mod tree; mod tree;
use tree::parse_collection; use tree::parse_collection;
// mod game; mod game;
use thiserror::Error; use thiserror::Error;
mod types; #[derive(Debug)]
pub use types::*; pub enum Error {
InvalidField,
InvalidBoardSize,
Incomplete,
InvalidSgf(VerboseNomError),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Color {
Black,
White,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Position {}
#[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(),
})
}
}
/*
pub enum Game { pub enum Game {
Go(go::Game), Go(go::Game),
Unsupported(tree::Tree), Unsupported(tree::Tree),
@ -31,7 +84,6 @@ pub fn parse_sgf(input: &str) -> Result<Vec<Game>, Error> {
}) })
.collect::<Vec<Game>>()) .collect::<Vec<Game>>())
} }
*/
/* /*
impl From<(&str, VerboseErrorKind)> for impl From<(&str, VerboseErrorKind)> for

View File

@ -1,4 +1,4 @@
use crate::{Color, Error, GameResult}; use crate::Error;
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{escaped_transform, tag}, bytes::complete::{escaped_transform, tag},
@ -27,153 +27,6 @@ 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)] #[derive(Clone, Debug, PartialEq)]
pub struct Size { pub struct Size {
pub width: i32, pub width: i32,
@ -198,22 +51,6 @@ 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)] #[derive(Clone, Debug, PartialEq)]
pub struct Tree { pub struct Tree {
pub root: Node, pub root: Node,
@ -256,7 +93,6 @@ impl ToString for Node {
} }
} }
/*
impl Node { impl Node {
pub fn find_prop(&self, ident: &str) -> Option<Property> { pub fn find_prop(&self, ident: &str) -> Option<Property> {
self.properties self.properties
@ -269,253 +105,21 @@ impl Node {
self.next.get(0) self.next.get(0)
} }
} }
*/
// KO
// MN
// N
// AR
// CR
// DD
// LB
// LN
// MA
// SL
// SQ
// TR
// BL
// OB
// OW
// WL
// FG
// PM
// VW
#[derive(Clone, Debug, PartialEq)]
pub enum Property {
// B, W
Move((Color, Position)),
// 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),
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnknownProperty {
ident: String,
values: Vec<String>,
}
/*
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Property { pub struct Property {
pub ident: String, pub ident: String,
pub values: Vec<String>, pub values: Vec<String>,
} }
*/
impl ToString for Property { impl ToString for Property {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { let values = self
Property::Move((color, position)) => { .values
format!("{}[{}]", color.abbreviation(), position.0)
}
Property::Comment(values) => format!(
"C{}",
values
.iter()
.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() .iter()
.map(|val| format!("[{}]", val)) .map(|val| format!("[{}]", val))
.collect::<String>(); .collect::<String>();
format!("{}{}", ident, values) format!("{}{}", self.ident, values)
}
}
/*
let values = self
.collect::<String>();
*/
} }
} }
@ -570,54 +174,13 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
.into_iter() .into_iter()
.map(|v| v.to_owned()) .map(|v| v.to_owned())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
Ok((
let prop = match ident { input,
"W" => Property::Move((Color::White, Position(values[0].clone()))), Property {
"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(), ident: ident.to_owned(),
values, values,
}), },
}; ))
Ok((input, prop))
} }
fn parse_propval<'a, E: nom::error::ParseError<&'a str>>( fn parse_propval<'a, E: nom::error::ParseError<&'a str>>(
@ -673,12 +236,21 @@ mod test {
#[test] #[test]
fn it_can_parse_properties() { fn it_can_parse_properties() {
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a]").unwrap(); let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a]").unwrap();
assert_eq!(prop, Property::Comment(vec!["a".to_owned()])); assert_eq!(
prop,
Property {
ident: "C".to_owned(),
values: vec!["a".to_owned()]
}
);
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a][b][c]").unwrap(); let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a][b][c]").unwrap();
assert_eq!( assert_eq!(
prop, prop,
Property::Comment(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]) Property {
ident: "C".to_owned(),
values: vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
}
); );
} }
@ -689,7 +261,10 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))], properties: vec![Property {
ident: "B".to_owned(),
values: vec!["ab".to_owned()]
}],
next: vec![] next: vec![]
} }
); );
@ -701,13 +276,25 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))], properties: vec![Property {
ident: "B".to_owned(),
values: vec!["ab".to_owned()]
}],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, Position("dp".to_owned())))], properties: vec![Property {
ident: "W".to_owned(),
values: vec!["dp".to_owned()]
}],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, Position("pq".to_owned()))), Property {
Property::Comment(vec!["some comments".to_owned()]) ident: "B".to_owned(),
values: vec!["pq".to_owned()]
},
Property {
ident: "C".to_owned(),
values: vec!["some comments".to_owned()]
}
], ],
next: vec![], next: vec![],
}] }]
@ -725,13 +312,25 @@ mod test {
assert_eq!( assert_eq!(
sequence, sequence,
Node { Node {
properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))], properties: vec![Property {
ident: "B".to_owned(),
values: vec!["ab".to_owned()]
}],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, Position("dp".to_owned())))], properties: vec![Property {
ident: "W".to_owned(),
values: vec!["dp".to_owned()]
}],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, Position("pq".to_owned()))), Property {
Property::Comment(vec!["some comments".to_owned()]) ident: "B".to_owned(),
values: vec!["pq".to_owned()]
},
Property {
ident: "C".to_owned(),
values: vec!["some comments".to_owned()]
}
], ],
next: vec![], next: vec![],
}] }]
@ -746,18 +345,33 @@ mod test {
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap();
let expected = Node { let expected = Node {
properties: vec![Property::Comment(vec!["a".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["a".to_owned()],
}],
next: vec![Node { next: vec![Node {
properties: vec![Property::Comment(vec!["b".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["b".to_owned()],
}],
next: vec![ next: vec![
Node { Node {
properties: vec![Property::Comment(vec!["c".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["c".to_owned()],
}],
next: vec![], next: vec![],
}, },
Node { Node {
properties: vec![Property::Comment(vec!["d".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["d".to_owned()],
}],
next: vec![Node { next: vec![Node {
properties: vec![Property::Comment(vec!["e".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["e".to_owned()],
}],
next: vec![], next: vec![],
}], }],
}, },
@ -773,49 +387,85 @@ mod test {
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
let j = Node { let j = Node {
properties: vec![Property::Comment(vec!["j".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["j".to_owned()],
}],
next: vec![], next: vec![],
}; };
let i = Node { let i = Node {
properties: vec![Property::Comment(vec!["i".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["i".to_owned()],
}],
next: vec![], next: vec![],
}; };
let h = Node { let h = Node {
properties: vec![Property::Comment(vec!["h".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["h".to_owned()],
}],
next: vec![i], next: vec![i],
}; };
let g = Node { let g = Node {
properties: vec![Property::Comment(vec!["g".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["g".to_owned()],
}],
next: vec![h], next: vec![h],
}; };
let f = Node { let f = Node {
properties: vec![Property::Comment(vec!["f".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["f".to_owned()],
}],
next: vec![g, j], next: vec![g, j],
}; };
let e = Node { let e = Node {
properties: vec![Property::Comment(vec!["e".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["e".to_owned()],
}],
next: vec![], next: vec![],
}; };
let d = Node { let d = Node {
properties: vec![Property::Comment(vec!["d".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["d".to_owned()],
}],
next: vec![e], next: vec![e],
}; };
let c = Node { let c = Node {
properties: vec![Property::Comment(vec!["c".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["c".to_owned()],
}],
next: vec![], next: vec![],
}; };
let b = Node { let b = Node {
properties: vec![Property::Comment(vec!["b".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["b".to_owned()],
}],
next: vec![c, d], next: vec![c, d],
}; };
let a = Node { let a = Node {
properties: vec![Property::Comment(vec!["a".to_owned()])], properties: vec![Property {
ident: "C".to_owned(),
values: vec!["a".to_owned()],
}],
next: vec![b], next: vec![b],
}; };
let expected = Node { let expected = Node {
properties: vec![ properties: vec![
Property::FileFormat(4), Property {
Property::Comment(vec!["root".to_owned()]), ident: "FF".to_owned(),
values: vec!["4".to_owned()],
},
Property {
ident: "C".to_owned(),
values: vec!["root".to_owned()],
},
], ],
next: vec![a, f], next: vec![a, f],
}; };

View File

@ -1,109 +0,0 @@
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,
}