Compare commits
No commits in common. "18216160c9cd7edbd9bdd97e488da349d24042fa" and "aa053cd10fd5d2a5774cd8876b4a8a755900acd5" have entirely different histories.
18216160c9
...
aa053cd10f
|
@ -6,13 +6,12 @@ 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" ] }
|
thiserror = { version = "1"}
|
||||||
thiserror = { version = "1"}
|
typeshare = { version = "1" }
|
||||||
typeshare = { version = "1" }
|
uuid = { version = "1.4", features = ["v4", "serde"] }
|
||||||
uuid = { version = "1.4", features = ["v4", "serde"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
cool_asserts = { version = "2" }
|
cool_asserts = { version = "2" }
|
||||||
|
|
408
sgf/src/game.rs
408
sgf/src/game.rs
|
@ -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!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
600
sgf/src/tree.rs
600
sgf/src/tree.rs
|
@ -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 {
|
|
||||||
Property::Move((color, position)) => {
|
|
||||||
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()
|
|
||||||
.map(|val| format!("[{}]", val))
|
|
||||||
.collect::<String>();
|
|
||||||
format!("{}{}", ident, values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
let values = self
|
let values = self
|
||||||
|
.values
|
||||||
|
.iter()
|
||||||
|
.map(|val| format!("[{}]", val))
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
*/
|
format!("{}{}", self.ident, values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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],
|
||||||
};
|
};
|
||||||
|
|
109
sgf/src/types.rs
109
sgf/src/types.rs
|
@ -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,
|
|
||||||
}
|
|
Loading…
Reference in New Issue