2023-10-19 13:57:02 +00:00
use crate ::{
parser ::{ self , Annotation , Evaluation , Move , SetupInstr , Size , UnknownProperty } ,
Color , Date , GameResult , GameType ,
} ;
2024-03-22 03:23:47 +00:00
use serde ::{ Deserialize , Serialize } ;
2023-10-19 13:57:02 +00:00
use std ::{ collections ::HashSet , time ::Duration } ;
use uuid ::Uuid ;
#[ derive(Clone, Debug, PartialEq) ]
pub enum GameError {
InvalidGame ,
RequiredPropertiesMissing ,
InvalidGameNode ( GameNodeError ) ,
}
#[ derive(Clone, Debug, PartialEq) ]
pub enum MoveNodeError {
IncompatibleProperty ( parser ::Property ) ,
ConflictingProperty ,
NotAMoveNode ,
ChildError ( Box < GameNodeError > ) ,
}
#[ derive(Clone, Debug, PartialEq) ]
pub enum SetupNodeError {
IncompatibleProperty ( parser ::Property ) ,
ConflictingProperty ,
ConflictingPosition ,
NotASetupNode ,
ChildError ( Box < GameNodeError > ) ,
}
#[ derive(Clone, Debug, PartialEq) ]
pub enum GameNodeError {
UnsupportedGameNode ( MoveNodeError , SetupNodeError ) ,
ConflictingProperty ,
ConflictingPosition ,
}
2024-03-22 03:23:47 +00:00
#[ derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize) ]
2023-10-19 13:57:02 +00:00
pub struct Player {
pub name : Option < String > ,
pub rank : Option < String > ,
pub team : Option < String > ,
}
2023-10-20 04:36:03 +00:00
/// This represents the more semantic version of the game parser. Where the `parser` crate pulls
/// out a raw set of nodes, this structure is guaranteed to be a well-formed game. Getting to this
/// level, the interpreter will reject any games that have setup properties and move properties
/// mixed in a single node. If there are other semantic problems, the interpreter will reject
/// those, as well. Where the function of the parser is to understand and correct fundamental
2024-03-23 17:40:06 +00:00
/// syntax issues, the result of the GameRecord is to have a fully-understood game. However, this
/// doesn't (yet?) go quite to the level of apply the game type (i.e., this is Go, Chess, Yinsh, or
2023-10-20 04:36:03 +00:00
/// whatever).
2024-03-22 03:23:47 +00:00
#[ derive(Clone, Debug, PartialEq, Deserialize, Serialize) ]
2024-03-23 17:40:06 +00:00
pub struct GameRecord {
2024-03-22 03:12:30 +00:00
pub game_type : GameType ,
2023-10-19 13:57:02 +00:00
// TODO: board size is not necessary in all games. Hive has no defined board size.
2024-03-22 03:12:30 +00:00
pub board_size : Size ,
pub black_player : Player ,
pub white_player : Player ,
2023-10-19 13:57:02 +00:00
2024-03-22 03:12:30 +00:00
pub app : Option < String > ,
pub annotator : Option < String > ,
pub copyright : Option < String > ,
pub dates : Vec < Date > ,
pub event : Option < String > ,
pub game_name : Option < String > ,
pub extra_info : Option < String > ,
pub opening_info : Option < String > ,
pub location : Option < String > ,
pub result : Option < GameResult > ,
pub round : Option < String > ,
pub rules : Option < String > ,
pub source : Option < String > ,
pub time_limit : Option < Duration > ,
pub overtime : Option < String > ,
pub transcriber : Option < String > ,
2023-10-19 13:57:02 +00:00
2024-03-22 03:12:30 +00:00
pub children : Vec < GameNode > ,
2023-10-19 13:57:02 +00:00
}
2024-03-23 17:40:06 +00:00
impl GameRecord {
2023-10-19 13:57:02 +00:00
pub fn new (
game_type : GameType ,
board_size : Size ,
black_player : Player ,
white_player : Player ,
) -> Self {
Self {
game_type ,
board_size ,
black_player ,
white_player ,
app : None ,
annotator : None ,
copyright : None ,
dates : vec ! [ ] ,
event : None ,
game_name : None ,
extra_info : None ,
opening_info : None ,
location : None ,
result : None ,
round : None ,
rules : None ,
source : None ,
time_limit : None ,
overtime : None ,
transcriber : None ,
children : vec ! [ ] ,
}
}
}
2024-03-23 17:40:06 +00:00
impl Node for GameRecord {
2023-10-19 13:57:02 +00:00
fn children < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
self . children . iter ( ) . collect ::< Vec < & ' a GameNode > > ( )
}
fn add_child < ' a > ( & ' a mut self , node : GameNode ) -> & ' a mut GameNode {
self . children . push ( node ) ;
self . children . last_mut ( ) . unwrap ( )
}
}
2024-03-23 17:40:06 +00:00
impl TryFrom < & parser ::Tree > for GameRecord {
2023-10-19 13:57:02 +00:00
type Error = GameError ;
fn try_from ( tree : & parser ::Tree ) -> Result < Self , Self ::Error > {
let mut ty = None ;
let mut size = None ;
let mut black_player = Player {
name : None ,
rank : None ,
team : None ,
} ;
let mut white_player = Player {
name : None ,
rank : None ,
team : None ,
} ;
for prop in tree . root . properties . iter ( ) {
match prop {
parser ::Property ::GameType ( ty_ ) = > ty = Some ( ty_ . clone ( ) ) ,
parser ::Property ::BoardSize ( size_ ) = > size = Some ( size_ . clone ( ) ) ,
parser ::Property ::BlackPlayer ( name ) = > {
black_player . name = Some ( name . clone ( ) ) ;
}
parser ::Property ::WhitePlayer ( name ) = > {
white_player . name = Some ( name . clone ( ) ) ;
}
parser ::Property ::BlackRank ( rank ) = > {
black_player . rank = Some ( rank . clone ( ) ) ;
}
parser ::Property ::WhiteRank ( rank ) = > {
white_player . rank = Some ( rank . clone ( ) ) ;
}
parser ::Property ::BlackTeam ( team ) = > {
black_player . team = Some ( team . clone ( ) ) ;
}
parser ::Property ::WhiteTeam ( team ) = > {
white_player . team = Some ( team . clone ( ) ) ;
}
_ = > { }
}
}
let mut s = match ( ty , size ) {
( Some ( ty ) , Some ( size ) ) = > Ok ( Self ::new ( ty , size , black_player , white_player ) ) ,
_ = > Err ( Self ::Error ::RequiredPropertiesMissing ) ,
} ? ;
for prop in tree . root . properties . iter ( ) {
match prop {
parser ::Property ::GameType ( _ )
| parser ::Property ::BoardSize ( _ )
| parser ::Property ::BlackPlayer ( _ )
| parser ::Property ::WhitePlayer ( _ )
| parser ::Property ::BlackRank ( _ )
| parser ::Property ::WhiteRank ( _ )
| parser ::Property ::BlackTeam ( _ )
| parser ::Property ::WhiteTeam ( _ ) = > { }
parser ::Property ::Application ( v ) = > s . app = Some ( v . clone ( ) ) ,
parser ::Property ::Annotator ( v ) = > s . annotator = Some ( v . clone ( ) ) ,
parser ::Property ::Copyright ( v ) = > s . copyright = Some ( v . clone ( ) ) ,
parser ::Property ::EventDates ( v ) = > s . dates = v . clone ( ) ,
parser ::Property ::EventName ( v ) = > s . event = Some ( v . clone ( ) ) ,
parser ::Property ::GameName ( v ) = > s . game_name = Some ( v . clone ( ) ) ,
parser ::Property ::ExtraGameInformation ( v ) = > s . extra_info = Some ( v . clone ( ) ) ,
parser ::Property ::GameOpening ( v ) = > s . opening_info = Some ( v . clone ( ) ) ,
parser ::Property ::GameLocation ( v ) = > s . location = Some ( v . clone ( ) ) ,
parser ::Property ::Result ( v ) = > s . result = Some ( v . clone ( ) ) ,
parser ::Property ::Round ( v ) = > s . round = Some ( v . clone ( ) ) ,
parser ::Property ::Ruleset ( v ) = > s . rules = Some ( v . clone ( ) ) ,
parser ::Property ::Source ( v ) = > s . source = Some ( v . clone ( ) ) ,
parser ::Property ::TimeLimit ( v ) = > s . time_limit = Some ( v . clone ( ) ) ,
parser ::Property ::Overtime ( v ) = > s . overtime = Some ( v . clone ( ) ) ,
// parser::Property::Data(v) => s.transcriber = Some(v.clone()),
_ = > { }
}
}
s . children = tree
. root
. next
. iter ( )
. map ( | node | GameNode ::try_from ( node ) )
. collect ::< Result < Vec < GameNode > , GameNodeError > > ( )
. map_err ( GameError ::InvalidGameNode ) ? ;
Ok ( s )
}
}
2024-03-22 03:23:47 +00:00
#[ derive(Clone, Debug, PartialEq, Deserialize, Serialize) ]
2023-10-19 13:57:02 +00:00
pub enum GameNode {
MoveNode ( MoveNode ) ,
SetupNode ( SetupNode ) ,
}
pub trait Node {
/// Provide a pre-order traversal of all of the nodes in the game tree.
fn nodes < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
self . children ( )
. iter ( )
. map ( | node | {
let mut children = node . nodes ( ) ;
let mut v = vec! [ * node ] ;
v . append ( & mut children ) ;
v
} )
. flatten ( )
. collect ::< Vec < & ' a GameNode > > ( )
}
fn children < ' a > ( & ' a self ) -> Vec < & ' a GameNode > ;
fn add_child < ' a > ( & ' a mut self , node : GameNode ) -> & ' a mut GameNode ;
}
impl GameNode {
pub fn id ( & self ) -> Uuid {
match self {
GameNode ::MoveNode ( node ) = > node . id ,
GameNode ::SetupNode ( node ) = > node . id ,
}
}
}
impl Node for GameNode {
fn children < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
match self {
GameNode ::MoveNode ( node ) = > node . children ( ) ,
GameNode ::SetupNode ( node ) = > node . children ( ) ,
}
}
fn nodes < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
match self {
GameNode ::MoveNode ( node ) = > node . nodes ( ) ,
GameNode ::SetupNode ( node ) = > node . nodes ( ) ,
}
}
fn add_child < ' a > ( & ' a mut self , new_node : GameNode ) -> & ' a mut GameNode {
match self {
GameNode ::MoveNode ( node ) = > node . add_child ( new_node ) ,
GameNode ::SetupNode ( node ) = > node . add_child ( new_node ) ,
}
}
}
impl TryFrom < & parser ::Node > for GameNode {
type Error = GameNodeError ;
2023-10-20 04:36:03 +00:00
2023-10-19 13:57:02 +00:00
fn try_from ( n : & parser ::Node ) -> Result < Self , Self ::Error > {
2023-10-20 04:36:03 +00:00
// I originally wrote this recursively. However, on an ordinary game of a couple hundred
// moves, that meant that I was recursing 500 functions, and that exceeded the stack limit.
// So, instead, I need to unroll everything to non-recursive form.
//
// So, I can treat each branch of the tree as a single line. Iterate over that line. I can
// only use the MoveNode::try_from and SetupNode::try_from if those functions don't
// recurse. Instead, I'm going to process just that node, then return to here and process
// the children.
2023-10-19 13:57:02 +00:00
let move_node = MoveNode ::try_from ( n ) ;
let setup_node = SetupNode ::try_from ( n ) ;
2023-10-20 04:36:03 +00:00
// I'm much too tired when writing this. I'm still recursing, but I did cut the number of
// recursions in half. This helps, but it still doesn't guarantee that I'm going to be able
// to parse all possible games. So, still, treat each branch of the game as a single line.
// Iterate over that line, don't recurse. Create bookmarks at each branch point, and then
// come back to each one.
let children = n
. next
. iter ( )
. map ( | n | GameNode ::try_from ( n ) )
. collect ::< Result < Vec < Self > , Self ::Error > > ( ) ? ;
let node = match ( move_node , setup_node ) {
( Ok ( mut node ) , _ ) = > {
node . children = children ;
Ok ( Self ::MoveNode ( node ) )
}
( Err ( _ ) , Ok ( mut node ) ) = > {
node . children = children ;
Ok ( Self ::SetupNode ( node ) )
}
2023-10-19 13:57:02 +00:00
( Err ( move_err ) , Err ( setup_err ) ) = > {
Err ( Self ::Error ::UnsupportedGameNode ( move_err , setup_err ) )
}
2023-10-20 04:36:03 +00:00
} ? ;
Ok ( node )
2023-10-19 13:57:02 +00:00
}
}
2024-03-22 03:23:47 +00:00
#[ derive(Clone, Debug, PartialEq, Deserialize, Serialize) ]
2023-10-19 13:57:02 +00:00
pub struct MoveNode {
id : Uuid ,
color : Color ,
mv : Move ,
children : Vec < GameNode > ,
time_left : Option < Duration > ,
moves_left : Option < usize > ,
name : Option < String > ,
evaluation : Option < Evaluation > ,
value : Option < f64 > ,
comments : Option < String > ,
annotation : Option < Annotation > ,
unknown_props : Vec < ( String , String ) > ,
}
impl MoveNode {
pub fn new ( color : Color , mv : Move ) -> Self {
Self {
id : Uuid ::new_v4 ( ) ,
color ,
mv ,
children : Vec ::new ( ) ,
time_left : None ,
moves_left : None ,
name : None ,
evaluation : None ,
value : None ,
comments : None ,
annotation : None ,
unknown_props : vec ! [ ] ,
}
}
}
impl Node for MoveNode {
fn children < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
self . children . iter ( ) . collect ::< Vec < & ' a GameNode > > ( )
}
fn add_child < ' a > ( & ' a mut self , node : GameNode ) -> & ' a mut GameNode {
self . children . push ( node ) ;
self . children . last_mut ( ) . unwrap ( )
}
}
impl TryFrom < & parser ::Node > for MoveNode {
type Error = MoveNodeError ;
fn try_from ( n : & parser ::Node ) -> Result < Self , Self ::Error > {
2023-10-20 04:36:03 +00:00
let s = match n . mv ( ) {
2023-10-19 13:57:02 +00:00
Some ( ( color , mv ) ) = > {
let mut s = Self ::new ( color , mv ) ;
for prop in n . properties . iter ( ) {
match prop {
parser ::Property ::Move ( ( color , mv ) ) = > {
if s . color ! = * color | | s . mv ! = * mv {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
}
parser ::Property ::TimeLeft ( ( color , duration ) ) = > {
if s . color ! = * color {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
if s . time_left . is_some ( ) {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
s . time_left = Some ( duration . clone ( ) ) ;
}
parser ::Property ::Comment ( cmt ) = > {
if s . comments . is_some ( ) {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
s . comments = Some ( cmt . clone ( ) ) ;
}
parser ::Property ::Evaluation ( evaluation ) = > {
if s . evaluation . is_some ( ) {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
s . evaluation = Some ( * evaluation )
}
parser ::Property ::Annotation ( annotation ) = > {
if s . annotation . is_some ( ) {
return Err ( Self ::Error ::ConflictingProperty ) ;
}
s . annotation = Some ( * annotation )
}
2024-03-22 02:48:53 +00:00
parser ::Property ::Territory ( .. ) = > {
eprintln! ( " not processing territory property " ) ;
}
2023-10-19 13:57:02 +00:00
parser ::Property ::Unknown ( UnknownProperty { ident , value } ) = > {
s . unknown_props . push ( ( ident . clone ( ) , value . clone ( ) ) ) ;
}
_ = > return Err ( Self ::Error ::IncompatibleProperty ( prop . clone ( ) ) ) ,
}
}
Ok ( s )
}
None = > Err ( Self ::Error ::NotAMoveNode ) ,
} ? ;
Ok ( s )
}
}
2024-03-22 03:23:47 +00:00
#[ derive(Clone, Debug, PartialEq, Deserialize, Serialize) ]
2023-10-19 13:57:02 +00:00
pub struct SetupNode {
id : Uuid ,
positions : Vec < parser ::SetupInstr > ,
children : Vec < GameNode > ,
}
impl SetupNode {
pub fn new ( positions : Vec < parser ::SetupInstr > ) -> Result < Self , SetupNodeError > {
let mut board = HashSet ::new ( ) ;
for position in positions . iter ( ) {
let point = match position {
SetupInstr ::Piece ( ( _ , point ) ) = > point ,
SetupInstr ::Clear ( point ) = > point ,
} ;
if board . contains ( point ) {
return Err ( SetupNodeError ::ConflictingPosition ) ;
}
board . insert ( point ) ;
}
Ok ( Self {
id : Uuid ::new_v4 ( ) ,
positions ,
children : Vec ::new ( ) ,
} )
}
}
impl Node for SetupNode {
fn children < ' a > ( & ' a self ) -> Vec < & ' a GameNode > {
self . children . iter ( ) . collect ::< Vec < & ' a GameNode > > ( )
}
#[ allow(dead_code) ]
fn add_child < ' a > ( & ' a mut self , _node : GameNode ) -> & ' a mut GameNode {
unimplemented! ( )
}
}
impl TryFrom < & parser ::Node > for SetupNode {
type Error = SetupNodeError ;
fn try_from ( n : & parser ::Node ) -> Result < Self , Self ::Error > {
match n . setup ( ) {
Some ( elements ) = > Self ::new ( elements ) ,
None = > Err ( Self ::Error ::NotASetupNode ) ,
}
}
}
#[ allow(dead_code) ]
pub fn path_to_node < ' a > ( node : & ' a GameNode , id : Uuid ) -> Vec < & ' a GameNode > {
if node . id ( ) = = id {
return vec! [ node ] ;
}
for child in node . children ( ) {
let mut path = path_to_node ( child , id ) ;
if path . len ( ) > 1 {
path . push ( child ) ;
return path ;
}
}
Vec ::new ( )
}
#[ cfg(test) ]
mod test {
use super ::* ;
use cool_asserts ::assert_matches ;
#[ test ]
fn it_can_create_an_empty_game_tree ( ) {
2024-03-23 17:40:06 +00:00
let tree = GameRecord ::new (
2023-10-19 13:57:02 +00:00
GameType ::Go ,
Size {
width : 19 ,
height : 19 ,
} ,
Player ::default ( ) ,
Player ::default ( ) ,
) ;
assert_eq! ( tree . nodes ( ) . len ( ) , 0 ) ;
}
#[ test ]
fn it_can_add_moves_to_a_game ( ) {
2024-03-23 17:40:06 +00:00
let mut game = GameRecord ::new (
2023-10-19 13:57:02 +00:00
GameType ::Go ,
Size {
width : 19 ,
height : 19 ,
} ,
Player ::default ( ) ,
Player ::default ( ) ,
) ;
let first_move = MoveNode ::new ( Color ::Black , Move ::Move ( " dd " . to_owned ( ) ) ) ;
let first_ = game . add_child ( GameNode ::MoveNode ( first_move . clone ( ) ) ) ;
let second_move = MoveNode ::new ( Color ::White , Move ::Move ( " qq " . to_owned ( ) ) ) ;
first_ . add_child ( GameNode ::MoveNode ( second_move . clone ( ) ) ) ;
let nodes = game . nodes ( ) ;
assert_eq! ( nodes . len ( ) , 2 ) ;
assert_eq! ( nodes [ 0 ] . id ( ) , first_move . id ) ;
assert_eq! ( nodes [ 1 ] . id ( ) , second_move . id ) ;
}
#[ ignore ]
#[ test ]
fn it_can_set_up_a_game ( ) {
unimplemented! ( )
}
#[ ignore ]
#[ test ]
fn it_can_load_tree_from_sgf ( ) {
unimplemented! ( )
}
#[ test ]
fn game_node_can_parse_sgf_move_node ( ) {
let n = parser ::Node {
properties : vec ! [
parser ::Property ::Move ( ( Color ::White , Move ::Move ( " dp " . to_owned ( ) ) ) ) ,
parser ::Property ::TimeLeft ( ( Color ::White , Duration ::from_secs ( 176 ) ) ) ,
parser ::Property ::Comment ( " Comments in the game " . to_owned ( ) ) ,
] ,
next : vec ! [ ] ,
} ;
assert_matches! ( GameNode ::try_from ( & n ) , Ok ( GameNode ::MoveNode ( _ ) ) ) ;
}
}
#[ cfg(test) ]
mod root_node_tests {
#[ ignore ]
#[ test ]
fn it_rejects_move_properties ( ) {
unimplemented! ( )
}
#[ ignore ]
#[ test ]
fn it_rejects_setup_properties ( ) {
unimplemented! ( )
}
#[ ignore ]
#[ test ]
fn it_can_parse_a_root_sgf ( ) {
unimplemented! ( )
}
}
#[ cfg(test) ]
mod move_node_tests {
use crate ::parser ::PositionList ;
use super ::* ;
use cool_asserts ::assert_matches ;
#[ test ]
fn it_can_parse_an_sgf_move_node ( ) {
let n = parser ::Node {
properties : vec ! [
parser ::Property ::Move ( ( Color ::White , Move ::Move ( " dp " . to_owned ( ) ) ) ) ,
parser ::Property ::TimeLeft ( ( Color ::White , Duration ::from_secs ( 176 ) ) ) ,
parser ::Property ::Comment ( " 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 . mv , Move ::Move ( " dp " . to_owned ( ) ) ) ;
assert_eq! ( node . children , vec! [ ] ) ;
assert_eq! ( node . time_left , Some ( Duration ::from_secs ( 176 ) ) ) ;
assert_eq! ( node . comments , Some ( " Comments in the game " . to_owned ( ) ) ) ;
} ) ;
}
#[ test ]
fn it_rejects_an_sgf_setup_node ( ) {
let n = parser ::Node {
properties : vec ! [
parser ::Property ::Move ( ( Color ::White , Move ::Move ( " dp " . to_owned ( ) ) ) ) ,
parser ::Property ::TimeLeft ( ( Color ::White , Duration ::from_secs ( 176 ) ) ) ,
parser ::Property ::SetupBlackStones ( PositionList ( vec! [
" dd " . to_owned ( ) ,
" de " . to_owned ( ) ,
] ) ) ,
] ,
next : vec ! [ ] ,
} ;
assert_matches! (
MoveNode ::try_from ( & n ) ,
Err ( MoveNodeError ::IncompatibleProperty ( _ ) )
) ;
}
}
#[ cfg(test) ]
mod setup_node_tests {
use crate ::parser ::SetupInstr ;
use super ::* ;
use cool_asserts ::assert_matches ;
#[ ignore ]
#[ test ]
fn it_can_parse_an_sgf_setup_node ( ) {
unimplemented! ( )
}
#[ test ]
fn it_rejects_conflicting_placement_properties ( ) {
assert_matches! (
SetupNode ::new ( vec! [
SetupInstr ::Piece ( ( Color ::Black , " dd " . to_owned ( ) ) ) ,
SetupInstr ::Piece ( ( Color ::Black , " dd " . to_owned ( ) ) ) ,
] ) ,
Err ( SetupNodeError ::ConflictingPosition )
) ;
assert_matches! (
SetupNode ::new ( vec! [
SetupInstr ::Piece ( ( Color ::Black , " dd " . to_owned ( ) ) ) ,
SetupInstr ::Piece ( ( Color ::Black , " ee " . to_owned ( ) ) ) ,
SetupInstr ::Piece ( ( Color ::White , " ee " . to_owned ( ) ) ) ,
] ) ,
Err ( SetupNodeError ::ConflictingPosition )
) ;
}
}
#[ cfg(test) ]
mod path_test {
#[ ignore ]
#[ test ]
fn returns_empty_list_if_no_game_nodes ( ) {
unimplemented! ( )
}
#[ ignore ]
#[ test ]
fn returns_empty_list_if_node_not_found ( ) {
unimplemented! ( )
}
#[ ignore ]
#[ test ]
fn path_excludes_root_node ( ) {
unimplemented! ( )
}
}
#[ cfg(test) ]
mod file_test {
use super ::* ;
use crate ::Win ;
use cool_asserts ::assert_matches ;
use parser ::parse_collection ;
use std ::{ fs ::File , io ::Read } ;
2024-03-23 17:40:06 +00:00
fn with_text ( text : & str , f : impl FnOnce ( Vec < GameRecord > ) ) {
2023-10-19 13:57:02 +00:00
let ( _ , games ) = parse_collection ::< nom ::error ::VerboseError < & str > > ( text ) . unwrap ( ) ;
let games = games
. into_iter ( )
2024-03-23 17:40:06 +00:00
. map ( | game | GameRecord ::try_from ( & game ) . expect ( " game to parse " ) )
. collect ::< Vec < GameRecord > > ( ) ;
2023-10-19 13:57:02 +00:00
f ( games ) ;
}
2024-03-23 17:40:06 +00:00
fn with_file ( path : & std ::path ::Path , f : impl FnOnce ( Vec < GameRecord > ) ) {
2023-10-19 13:57:02 +00:00
let mut file = File ::open ( path ) . unwrap ( ) ;
let mut text = String ::new ( ) ;
let _ = file . read_to_string ( & mut text ) ;
with_text ( & text , f ) ;
}
2023-10-20 04:36:03 +00:00
/// This test checks against an ordinary game from SGF. It is unannotated and should contain
/// only move nodes with no setup nodes. The original source is from a game I played on KGS.
2023-10-19 13:57:02 +00:00
#[ test ]
2023-10-20 04:36:03 +00:00
fn it_can_load_an_ordinary_unannotated_game ( ) {
2023-10-19 13:57:02 +00:00
with_file (
std ::path ::Path ::new ( " test_data/2020 USGO DDK, Round 1.sgf " ) ,
| games | {
assert_eq! ( games . len ( ) , 1 ) ;
let game = & games [ 0 ] ;
assert_eq! ( game . game_type , GameType ::Go ) ;
assert_eq! (
game . board_size ,
Size {
width : 19 ,
height : 19
}
) ;
assert_eq! (
game . black_player ,
Player {
name : Some ( " savanni " . to_owned ( ) ) ,
rank : Some ( " 23k " . to_owned ( ) ) ,
team : None
}
) ;
assert_eq! (
game . white_player ,
Player {
name : Some ( " Geckoz " . to_owned ( ) ) ,
rank : None ,
team : None
}
) ;
assert_eq! ( game . app , Some ( " CGoban:3 " . to_owned ( ) ) ) ;
assert_eq! ( game . annotator , None ) ;
assert_eq! ( game . copyright , None ) ;
assert_eq! (
game . dates ,
vec! [ Date ::Date (
chrono ::NaiveDate ::from_ymd_opt ( 2020 , 8 , 5 ) . unwrap ( )
) ]
) ;
assert_eq! ( game . event , None ) ;
assert_eq! ( game . game_name , None ) ;
assert_eq! ( game . extra_info , None ) ;
assert_eq! ( game . opening_info , None ) ;
assert_eq! (
game . location ,
Some ( " The KGS Go Server at http://www.gokgs.com/ " . to_owned ( ) )
) ;
assert_eq! ( game . result , Some ( GameResult ::White ( Win ::Score ( 17.5 ) ) ) ) ;
assert_eq! ( game . round , None ) ;
assert_eq! ( game . rules , Some ( " AGA " . to_owned ( ) ) ) ;
assert_eq! ( game . source , None ) ;
assert_eq! ( game . time_limit , Some ( Duration ::from_secs ( 1800 ) ) ) ;
assert_eq! ( game . overtime , Some ( " 5x30 byo-yomi " . to_owned ( ) ) ) ;
assert_eq! ( game . transcriber , None ) ;
/*
Property {
ident : " KM " . to_owned ( ) ,
values : vec ! [ " 7.50 " . to_owned ( ) ] ,
} ,
] ;
for i in 0 .. 16 {
assert_eq! ( node . properties [ i ] , expected_properties [ i ] ) ;
}
* /
let children = game . children ( ) ;
let node = children . first ( ) . unwrap ( ) ;
assert_matches! ( node , GameNode ::MoveNode ( node ) = > {
assert_eq! ( node . color , Color ::Black ) ;
assert_eq! ( node . mv , Move ::Move ( " pp " . to_owned ( ) ) ) ;
assert_eq! ( node . time_left , Some ( Duration ::from_secs ( 1795 ) ) ) ;
assert_eq! ( node . comments , Some ( " Geckoz [?]: Good game \n savanni [23k?]: There we go! This UI is... tough. \n savanni [23k?]: Have fun! Talk to you at the end. \n Geckoz [?]: Yeah, OGS is much better; I'm a UX professional \n " . to_owned ( ) )
) } ) ;
let children = node . children ( ) ;
let node = children . first ( ) . unwrap ( ) ;
assert_matches! ( node , GameNode ::MoveNode ( node ) = > {
assert_eq! ( node . color , Color ::White ) ;
assert_eq! ( node . mv , Move ::Move ( " dp " . to_owned ( ) ) ) ;
assert_eq! ( node . time_left , Some ( Duration ::from_secs ( 1765 ) ) ) ;
assert_eq! ( node . comments , None ) ;
} ) ;
/*
let node = node . next ( ) . unwrap ( ) ;
let expected_properties = vec! [
Property {
ident : " W " . to_owned ( ) ,
values : vec ! [ " dp " . to_owned ( ) ] ,
} ,
Property {
ident : " WL " . to_owned ( ) ,
values : vec ! [ " 1765.099 " . to_owned ( ) ] ,
} ,
] ;
for i in 0 .. 2 {
assert_eq! ( node . properties [ i ] , expected_properties [ i ] ) ;
}
* /
} ,
) ;
}
}