From c51fced23af8f81b9743b72c7981aa0c82aee9c8 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 19:06:59 -0400 Subject: [PATCH 1/6] Rename go-sgf to sgf. Add make instructions --- Makefile | 9 +++++++ kifu/core/Cargo.lock | 24 +++++++++---------- kifu/core/Cargo.toml | 2 +- kifu/core/src/database.rs | 4 ++-- kifu/core/src/ui/elements/game_preview.rs | 2 +- kifu/core/src/ui/home.rs | 2 +- kifu/gtk/Cargo.lock | 24 +++++++++---------- {go-sgf => sgf}/Cargo.lock | 0 {go-sgf => sgf}/Cargo.toml | 2 +- {go-sgf => sgf}/Makefile | 0 {go-sgf => sgf}/src/date.rs | 0 {go-sgf => sgf}/src/go.rs | 0 {go-sgf => sgf}/src/lib.rs | 0 {go-sgf => sgf}/src/tree.rs | 0 .../test_data/2020 USGO DDK, Round 1.sgf | 0 .../test_data/2020 USGO DDK, Round 3.sgf | 0 .../33745402-213-Ormos-savanni.dgerinel.sgf | 0 {go-sgf => sgf}/test_data/9kyu-lecture.sgf | 0 {go-sgf => sgf}/test_data/ff4_a.sgf | 0 {go-sgf => sgf}/test_data/ff4_b.sgf | 0 {go-sgf => sgf}/test_data/ff4_ex.sgf | 0 {go-sgf => sgf}/test_data/linebreak_tests.sgf | 0 {go-sgf => sgf}/test_data/print1.sgf | 0 {go-sgf => sgf}/test_data/print2.sgf | 0 24 files changed, 39 insertions(+), 30 deletions(-) rename {go-sgf => sgf}/Cargo.lock (100%) rename {go-sgf => sgf}/Cargo.toml (95%) rename {go-sgf => sgf}/Makefile (100%) rename {go-sgf => sgf}/src/date.rs (100%) rename {go-sgf => sgf}/src/go.rs (100%) rename {go-sgf => sgf}/src/lib.rs (100%) rename {go-sgf => sgf}/src/tree.rs (100%) rename {go-sgf => sgf}/test_data/2020 USGO DDK, Round 1.sgf (100%) rename {go-sgf => sgf}/test_data/2020 USGO DDK, Round 3.sgf (100%) rename {go-sgf => sgf}/test_data/33745402-213-Ormos-savanni.dgerinel.sgf (100%) rename {go-sgf => sgf}/test_data/9kyu-lecture.sgf (100%) rename {go-sgf => sgf}/test_data/ff4_a.sgf (100%) rename {go-sgf => sgf}/test_data/ff4_b.sgf (100%) rename {go-sgf => sgf}/test_data/ff4_ex.sgf (100%) rename {go-sgf => sgf}/test_data/linebreak_tests.sgf (100%) rename {go-sgf => sgf}/test_data/print1.sgf (100%) rename {go-sgf => sgf}/test_data/print2.sgf (100%) diff --git a/Makefile b/Makefile index ef03c40..44f3aa7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,10 @@ +all: test bin + +test: kifu-core/test-oneshot sgf/test-oneshot + +bin: kifu-gtk + changeset-dev: cd changeset && make dev @@ -58,3 +64,6 @@ kifu-pwa/dev: kifu-pwa/server: pushd kifu/pwa && make server + +sgf/test-oneshot: + pushd sgf && make test-oneshot diff --git a/kifu/core/Cargo.lock b/kifu/core/Cargo.lock index 7ad8625..e15421d 100644 --- a/kifu/core/Cargo.lock +++ b/kifu/core/Cargo.lock @@ -120,17 +120,6 @@ dependencies = [ "syn 2.0.12", ] -[[package]] -name = "go-sgf" -version = "0.1.0" -dependencies = [ - "chrono", - "nom", - "serde", - "thiserror", - "typeshare", -] - [[package]] name = "grid" version = "0.9.0" @@ -191,10 +180,10 @@ version = "0.1.0" dependencies = [ "chrono", "cool_asserts", - "go-sgf", "grid", "serde", "serde_json", + "sgf", "thiserror", "typeshare", ] @@ -337,6 +326,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sgf" +version = "0.1.0" +dependencies = [ + "chrono", + "nom", + "serde", + "thiserror", + "typeshare", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/kifu/core/Cargo.toml b/kifu/core/Cargo.toml index 5972a7a..737ec89 100644 --- a/kifu/core/Cargo.toml +++ b/kifu/core/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] chrono = { version = "0.4" } -go-sgf = { path = "../../go-sgf" } +sgf = { path = "../../sgf" } grid = { version = "0.9" } serde_json = { version = "1" } serde = { version = "1", features = [ "derive" ] } diff --git a/kifu/core/src/database.rs b/kifu/core/src/database.rs index 6797e29..9ace920 100644 --- a/kifu/core/src/database.rs +++ b/kifu/core/src/database.rs @@ -1,6 +1,6 @@ use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf}; -use go_sgf::{parse_sgf, GameTree}; +use sgf::{parse_sgf, GameTree}; use thiserror::Error; #[derive(Error, Debug)] @@ -63,7 +63,7 @@ impl Database { mod test { use super::*; use cool_asserts::assert_matches; - use go_sgf::{Date, GameType}; + use sgf::{Date, GameType}; #[test] fn it_reads_empty_database() { diff --git a/kifu/core/src/ui/elements/game_preview.rs b/kifu/core/src/ui/elements/game_preview.rs index cd16694..6669d71 100644 --- a/kifu/core/src/ui/elements/game_preview.rs +++ b/kifu/core/src/ui/elements/game_preview.rs @@ -1,5 +1,5 @@ -use go_sgf::{Date, GameTree, Rank}; use serde::{Deserialize, Serialize}; +use sgf::{Date, GameTree, Rank}; use typeshare::typeshare; #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/kifu/core/src/ui/home.rs b/kifu/core/src/ui/home.rs index 12cca5b..7c06ae2 100644 --- a/kifu/core/src/ui/home.rs +++ b/kifu/core/src/ui/home.rs @@ -1,6 +1,6 @@ use crate::ui::{Action, GamePreviewElement}; -use go_sgf::GameTree; use serde::{Deserialize, Serialize}; +use sgf::GameTree; use typeshare::typeshare; fn rank_strings() -> Vec { diff --git a/kifu/gtk/Cargo.lock b/kifu/gtk/Cargo.lock index 7225c74..53e612d 100644 --- a/kifu/gtk/Cargo.lock +++ b/kifu/gtk/Cargo.lock @@ -545,17 +545,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "go-sgf" -version = "0.1.0" -dependencies = [ - "chrono", - "nom", - "serde", - "thiserror", - "typeshare", -] - [[package]] name = "gobject-sys" version = "0.17.4" @@ -799,10 +788,10 @@ name = "kifu-core" version = "0.1.0" dependencies = [ "chrono", - "go-sgf", "grid", "serde", "serde_json", + "sgf", "thiserror", "typeshare", ] @@ -1237,6 +1226,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sgf" +version = "0.1.0" +dependencies = [ + "chrono", + "nom", + "serde", + "thiserror", + "typeshare", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" diff --git a/go-sgf/Cargo.lock b/sgf/Cargo.lock similarity index 100% rename from go-sgf/Cargo.lock rename to sgf/Cargo.lock diff --git a/go-sgf/Cargo.toml b/sgf/Cargo.toml similarity index 95% rename from go-sgf/Cargo.toml rename to sgf/Cargo.toml index cc24121..ab75ce2 100644 --- a/go-sgf/Cargo.toml +++ b/sgf/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "go-sgf" +name = "sgf" version = "0.1.0" edition = "2021" diff --git a/go-sgf/Makefile b/sgf/Makefile similarity index 100% rename from go-sgf/Makefile rename to sgf/Makefile diff --git a/go-sgf/src/date.rs b/sgf/src/date.rs similarity index 100% rename from go-sgf/src/date.rs rename to sgf/src/date.rs diff --git a/go-sgf/src/go.rs b/sgf/src/go.rs similarity index 100% rename from go-sgf/src/go.rs rename to sgf/src/go.rs diff --git a/go-sgf/src/lib.rs b/sgf/src/lib.rs similarity index 100% rename from go-sgf/src/lib.rs rename to sgf/src/lib.rs diff --git a/go-sgf/src/tree.rs b/sgf/src/tree.rs similarity index 100% rename from go-sgf/src/tree.rs rename to sgf/src/tree.rs diff --git a/go-sgf/test_data/2020 USGO DDK, Round 1.sgf b/sgf/test_data/2020 USGO DDK, Round 1.sgf similarity index 100% rename from go-sgf/test_data/2020 USGO DDK, Round 1.sgf rename to sgf/test_data/2020 USGO DDK, Round 1.sgf diff --git a/go-sgf/test_data/2020 USGO DDK, Round 3.sgf b/sgf/test_data/2020 USGO DDK, Round 3.sgf similarity index 100% rename from go-sgf/test_data/2020 USGO DDK, Round 3.sgf rename to sgf/test_data/2020 USGO DDK, Round 3.sgf diff --git a/go-sgf/test_data/33745402-213-Ormos-savanni.dgerinel.sgf b/sgf/test_data/33745402-213-Ormos-savanni.dgerinel.sgf similarity index 100% rename from go-sgf/test_data/33745402-213-Ormos-savanni.dgerinel.sgf rename to sgf/test_data/33745402-213-Ormos-savanni.dgerinel.sgf diff --git a/go-sgf/test_data/9kyu-lecture.sgf b/sgf/test_data/9kyu-lecture.sgf similarity index 100% rename from go-sgf/test_data/9kyu-lecture.sgf rename to sgf/test_data/9kyu-lecture.sgf diff --git a/go-sgf/test_data/ff4_a.sgf b/sgf/test_data/ff4_a.sgf similarity index 100% rename from go-sgf/test_data/ff4_a.sgf rename to sgf/test_data/ff4_a.sgf diff --git a/go-sgf/test_data/ff4_b.sgf b/sgf/test_data/ff4_b.sgf similarity index 100% rename from go-sgf/test_data/ff4_b.sgf rename to sgf/test_data/ff4_b.sgf diff --git a/go-sgf/test_data/ff4_ex.sgf b/sgf/test_data/ff4_ex.sgf similarity index 100% rename from go-sgf/test_data/ff4_ex.sgf rename to sgf/test_data/ff4_ex.sgf diff --git a/go-sgf/test_data/linebreak_tests.sgf b/sgf/test_data/linebreak_tests.sgf similarity index 100% rename from go-sgf/test_data/linebreak_tests.sgf rename to sgf/test_data/linebreak_tests.sgf diff --git a/go-sgf/test_data/print1.sgf b/sgf/test_data/print1.sgf similarity index 100% rename from go-sgf/test_data/print1.sgf rename to sgf/test_data/print1.sgf diff --git a/go-sgf/test_data/print2.sgf b/sgf/test_data/print2.sgf similarity index 100% rename from go-sgf/test_data/print2.sgf rename to sgf/test_data/print2.sgf -- 2.44.1 From b9808a1862d35ebce33114c222eca06a9bb92f75 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 19:33:50 -0400 Subject: [PATCH 2/6] Introduce the Game data structure --- Makefile | 3 +++ sgf/Cargo.lock | 22 +++++++++++----------- sgf/src/go.rs | 9 ++------- sgf/src/lib.rs | 27 +++++++++++++++++++++++++-- sgf/src/tree.rs | 1 + 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 44f3aa7..7706641 100644 --- a/Makefile +++ b/Makefile @@ -65,5 +65,8 @@ kifu-pwa/dev: kifu-pwa/server: pushd kifu/pwa && make server +sgf/test: + pushd sgf && make test + sgf/test-oneshot: pushd sgf && make test-oneshot diff --git a/sgf/Cargo.lock b/sgf/Cargo.lock index bfdf38e..699220b 100644 --- a/sgf/Cargo.lock +++ b/sgf/Cargo.lock @@ -63,17 +63,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "go-sgf" -version = "0.1.0" -dependencies = [ - "chrono", - "nom", - "serde", - "thiserror", - "typeshare", -] - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -216,6 +205,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sgf" +version = "0.1.0" +dependencies = [ + "chrono", + "nom", + "serde", + "thiserror", + "typeshare", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 79fab10..0597fbb 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -71,17 +71,12 @@ use crate::{ date::{self, parse_date_field, Date}, tree::{parse_collection, ParseSizeError, Size}, + Error, }; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -#[derive(Debug)] -pub enum Error<'a> { - InvalidField, - InvalidBoardSize, - Incomplete, - InvalidSgf(nom::error::VerboseError<&'a str>), -} +pub struct Game {} impl<'a> From>> for Error<'a> { fn from(err: nom::Err>) -> Self { diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index db6f9a3..485267f 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -2,13 +2,20 @@ mod date; pub use date::Date; mod go; -pub use go::{parse_sgf, GameTree, GameType, Rank}; +pub use go::{GameTree, GameType, Rank}; mod tree; +use tree::parse_collection; use thiserror::Error; -pub enum Warning {} +#[derive(Debug)] +pub enum Error<'a> { + InvalidField, + InvalidBoardSize, + Incomplete, + InvalidSgf(nom::error::VerboseError<&'a str>), +} #[derive(Debug, PartialEq, Error)] pub enum ParseError { @@ -25,6 +32,22 @@ impl From> for ParseError { } } +pub enum Game { + Go(go::Game), + Unsupported(tree::Tree), +} + +pub fn parse_sgf<'a>(input: &'a str) -> Result, Error<'a>> { + let (_, trees) = parse_collection::>(input)?; + Ok(trees + .into_iter() + .map(|t| match t.sequence[0].find_prop("GM") { + Some(prop) if prop.values == vec!["1".to_owned()] => Game::Go(go::Game {}), + _ => Game::Unsupported(t), + }) + .collect::>()) +} + /* impl From<(&str, VerboseErrorKind)> for diff --git a/sgf/src/tree.rs b/sgf/src/tree.rs index 7c2681d..317be80 100644 --- a/sgf/src/tree.rs +++ b/sgf/src/tree.rs @@ -1,3 +1,4 @@ +use crate::Error; use nom::{ branch::alt, bytes::complete::{escaped_transform, tag}, -- 2.44.1 From 9533934e05d09d101a3a6463573639c2ad8e7de9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 20:15:22 -0400 Subject: [PATCH 3/6] Remove lifetime and borrows from the error type --- sgf/src/go.rs | 25 +++++-------------------- sgf/src/lib.rs | 33 +++++++++++++++++++++++++++++---- sgf/src/tree.rs | 6 ++++++ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 0597fbb..1f9f273 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -70,30 +70,15 @@ use crate::{ date::{self, parse_date_field, Date}, - tree::{parse_collection, ParseSizeError, Size}, - Error, + tree::{parse_collection, Size}, + Error, VerboseNomError, }; +use nom::error::VerboseError; use serde::{Deserialize, Serialize}; use typeshare::typeshare; pub struct Game {} -impl<'a> From>> for Error<'a> { - fn from(err: nom::Err>) -> Self { - match err { - nom::Err::Incomplete(_) => Error::Incomplete, - nom::Err::Error(e) => Error::InvalidSgf(e.clone()), - nom::Err::Failure(e) => Error::InvalidSgf(e.clone()), - } - } -} - -impl<'a> From for Error<'a> { - fn from(_: ParseSizeError) -> Self { - Self::InvalidBoardSize - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] pub enum Rank { @@ -236,8 +221,8 @@ enum PropValue { } */ -pub fn parse_sgf<'a>(input: &'a str) -> Result, Error<'a>> { - let (_, trees) = parse_collection::>(input)?; +pub fn parse_sgf(input: &str) -> Result, Error> { + let (_, trees) = parse_collection::>(input)?; let games = trees .into_iter() diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index 485267f..73f965a 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -10,11 +10,36 @@ use tree::parse_collection; use thiserror::Error; #[derive(Debug)] -pub enum Error<'a> { +pub enum Error { InvalidField, InvalidBoardSize, Incomplete, - InvalidSgf(nom::error::VerboseError<&'a str>), + InvalidSgf(VerboseNomError), +} + +#[derive(Debug)] +pub struct VerboseNomError(nom::error::VerboseError); + +impl From> 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>> for Error { + fn from(err: nom::Err>) -> 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)] @@ -37,8 +62,8 @@ pub enum Game { Unsupported(tree::Tree), } -pub fn parse_sgf<'a>(input: &'a str) -> Result, Error<'a>> { - let (_, trees) = parse_collection::>(input)?; +pub fn parse_sgf(input: &str) -> Result, Error> { + let (_, trees) = parse_collection::>(input)?; Ok(trees .into_iter() .map(|t| match t.sequence[0].find_prop("GM") { diff --git a/sgf/src/tree.rs b/sgf/src/tree.rs index 317be80..c0d46ff 100644 --- a/sgf/src/tree.rs +++ b/sgf/src/tree.rs @@ -10,6 +10,12 @@ use nom::{ }; use std::num::ParseIntError; +impl From for Error { + fn from(_: ParseSizeError) -> Self { + Self::InvalidBoardSize + } +} + #[derive(Debug)] pub enum ParseSizeError { ParseIntError(ParseIntError), -- 2.44.1 From d79598eaa342e9765bb8d4ad74f15befec41f626 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 20:24:05 -0400 Subject: [PATCH 4/6] Remove the GameTree data structure --- sgf/src/go.rs | 230 +++++++++++++++++++++++-------------------------- sgf/src/lib.rs | 7 +- 2 files changed, 113 insertions(+), 124 deletions(-) diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 1f9f273..831a092 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -69,15 +69,108 @@ // VW use crate::{ - date::{self, parse_date_field, Date}, - tree::{parse_collection, Size}, - Error, VerboseNomError, + date::{parse_date_field, Date}, + tree::{Size, Tree}, + Error, }; -use nom::error::VerboseError; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -pub struct Game {} +pub struct Game { + pub file_format: i8, + pub app_name: Option, + pub board_size: Size, + pub info: GameInfo, +} + +impl TryFrom for Game { + type Error = Error; + + fn try_from(tree: Tree) -> Result { + let file_format = match tree.sequence[0].find_prop("FF") { + Some(prop) => prop.values[0].parse::().unwrap(), + None => 4, + }; + let app_name = tree.sequence[0] + .find_prop("AP") + .map(|prop| prop.values[0].clone()); + let board_size = match tree.sequence[0].find_prop("SZ") { + Some(prop) => Size::try_from(prop.values[0].as_str())?, + None => Size { + width: 19, + height: 19, + }, + }; + let mut info = GameInfo::default(); + info.black_player = tree.sequence[0] + .find_prop("PB") + .map(|prop| prop.values.join(", ")); + + info.black_rank = tree.sequence[0] + .find_prop("BR") + .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); + + info.white_player = tree.sequence[0] + .find_prop("PW") + .map(|prop| prop.values.join(", ")); + + info.white_rank = tree.sequence[0] + .find_prop("WR") + .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); + + info.result = tree.sequence[0] + .find_prop("RE") + .and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok()); + + info.time_limits = tree.sequence[0] + .find_prop("TM") + .and_then(|prop| prop.values[0].parse::().ok()) + .and_then(|seconds| Some(std::time::Duration::from_secs(seconds))); + + info.date = tree.sequence[0] + .find_prop("DT") + .and_then(|prop| { + let v = prop + .values + .iter() + .map(|val| parse_date_field(val)) + .fold(Ok(vec![]), |acc, v| match (acc, v) { + (Ok(mut acc), Ok(mut values)) => { + acc.append(&mut values); + Ok(acc) + } + (Ok(_), Err(err)) => Err(err), + (Err(err), _) => Err(err), + }) + .ok()?; + Some(v) + }) + .unwrap_or(vec![]); + + info.event = tree.sequence[0] + .find_prop("EV") + .map(|prop| prop.values.join(", ")); + + info.round = tree.sequence[0] + .find_prop("RO") + .map(|prop| prop.values.join(", ")); + + info.source = tree.sequence[0] + .find_prop("SO") + .map(|prop| prop.values.join(", ")); + + info.game_keeper = tree.sequence[0] + .find_prop("US") + .map(|prop| prop.values.join(", ")); + + Ok(Game { + file_format, + app_name, + board_size, + info, + }) + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] @@ -107,16 +200,6 @@ impl ToString for Rank { } } -#[derive(Clone, Debug)] -pub struct GameTree { - pub file_format: i8, - pub app_name: Option, - pub game_type: GameType, - pub board_size: Size, - pub info: GameInfo, - // pub text: String, -} - #[derive(Clone, Debug, Default)] pub struct GameInfo { pub annotator: Option, @@ -193,12 +276,6 @@ pub enum Win { Time, } -#[derive(Clone, Debug, PartialEq)] -pub enum GameType { - Go, - Unsupported, -} - /* enum PropType { Move, @@ -221,104 +298,13 @@ enum PropValue { } */ -pub fn parse_sgf(input: &str) -> Result, Error> { - let (_, trees) = parse_collection::>(input)?; - - let games = trees - .into_iter() - .map(|tree| { - let file_format = match tree.sequence[0].find_prop("FF") { - Some(prop) => prop.values[0].parse::().unwrap(), - None => 4, - }; - let app_name = tree.sequence[0] - .find_prop("AP") - .map(|prop| prop.values[0].clone()); - let board_size = match tree.sequence[0].find_prop("SZ") { - Some(prop) => Size::try_from(prop.values[0].as_str())?, - None => Size { - width: 19, - height: 19, - }, - }; - let mut info = GameInfo::default(); - info.black_player = tree.sequence[0] - .find_prop("PB") - .map(|prop| prop.values.join(", ")); - - info.black_rank = tree.sequence[0] - .find_prop("BR") - .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); - - info.white_player = tree.sequence[0] - .find_prop("PW") - .map(|prop| prop.values.join(", ")); - - info.white_rank = tree.sequence[0] - .find_prop("WR") - .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); - - info.result = tree.sequence[0] - .find_prop("RE") - .and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok()); - - info.time_limits = tree.sequence[0] - .find_prop("TM") - .and_then(|prop| prop.values[0].parse::().ok()) - .and_then(|seconds| Some(std::time::Duration::from_secs(seconds))); - - info.date = tree.sequence[0] - .find_prop("DT") - .and_then(|prop| { - let v = prop - .values - .iter() - .map(|val| parse_date_field(val)) - .fold(Ok(vec![]), |acc, v| match (acc, v) { - (Ok(mut acc), Ok(mut values)) => { - acc.append(&mut values); - Ok(acc) - } - (Ok(_), Err(err)) => Err(err), - (Err(err), _) => Err(err), - }) - .ok()?; - Some(v) - }) - .unwrap_or(vec![]); - - info.event = tree.sequence[0] - .find_prop("EV") - .map(|prop| prop.values.join(", ")); - - info.round = tree.sequence[0] - .find_prop("RO") - .map(|prop| prop.values.join(", ")); - - info.source = tree.sequence[0] - .find_prop("SO") - .map(|prop| prop.values.join(", ")); - - info.game_keeper = tree.sequence[0] - .find_prop("US") - .map(|prop| prop.values.join(", ")); - - Ok(GameTree { - file_format, - app_name, - game_type: GameType::Go, - board_size, - info, - }) - }) - .collect::, Error>>()?; - Ok(games) -} - #[cfg(test)] mod tests { use super::*; - use crate::{date::Date, tree::Size}; + use crate::{ + date::Date, + tree::{parse_collection, Size}, + }; use std::fs::File; use std::io::Read; @@ -327,12 +313,16 @@ mod tests { (;C[f](;C[g];C[h];C[i]) (;C[j])))"; - fn with_text(text: &str, f: impl FnOnce(Vec)) { - let games = parse_sgf(text).unwrap(); + fn with_text(text: &str, f: impl FnOnce(Vec)) { + let (_, games) = parse_collection::>(text).unwrap(); + let games = games + .into_iter() + .map(|game| Game::try_from(game).expect("game to parse")) + .collect::>(); f(games); } - fn with_file(path: &std::path::Path, f: impl FnOnce(Vec)) { + fn with_file(path: &std::path::Path, f: impl FnOnce(Vec)) { let mut file = File::open(path).unwrap(); let mut text = String::new(); let _ = file.read_to_string(&mut text); @@ -346,7 +336,6 @@ mod tests { let tree = &trees[0]; assert_eq!(tree.file_format, 4); assert_eq!(tree.app_name, None); - assert_eq!(tree.game_type, GameType::Go); assert_eq!( tree.board_size, Size { @@ -362,7 +351,6 @@ mod tests { let tree = &trees[0]; assert_eq!(tree.file_format, 4); assert_eq!(tree.app_name, None); - assert_eq!(tree.game_type, GameType::Go); assert_eq!( tree.board_size, Size { diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index 73f965a..4b4f8fa 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -1,8 +1,7 @@ mod date; pub use date::Date; -mod go; -pub use go::{GameTree, GameType, Rank}; +pub mod go; mod tree; use tree::parse_collection; @@ -67,7 +66,9 @@ pub fn parse_sgf(input: &str) -> Result, Error> { Ok(trees .into_iter() .map(|t| match t.sequence[0].find_prop("GM") { - Some(prop) if prop.values == vec!["1".to_owned()] => Game::Go(go::Game {}), + Some(prop) if prop.values == vec!["1".to_owned()] => { + Game::Go(go::Game::try_from(t).expect("properly structured game tree")) + } _ => Game::Unsupported(t), }) .collect::>()) -- 2.44.1 From 92ca170a2459fa7e1ae8475539f0ec35c0d4b207 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 20:29:12 -0400 Subject: [PATCH 5/6] Switch from GameTree to go::Game in Kifu --- kifu/core/src/database.rs | 22 ++++++++++++---------- kifu/core/src/ui/elements/game_preview.rs | 7 +++++-- kifu/core/src/ui/home.rs | 4 ++-- sgf/src/go.rs | 1 + 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/kifu/core/src/database.rs b/kifu/core/src/database.rs index 9ace920..cad6a5c 100644 --- a/kifu/core/src/database.rs +++ b/kifu/core/src/database.rs @@ -1,6 +1,6 @@ use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf}; -use sgf::{parse_sgf, GameTree}; +use sgf::{go, parse_sgf, Game}; use thiserror::Error; #[derive(Error, Debug)] @@ -20,12 +20,12 @@ impl From for Error { #[derive(Debug)] pub struct Database { path: PathBuf, - games: Vec, + games: Vec, } impl Database { pub fn open_path(path: PathBuf) -> Result { - let mut games: Vec = Vec::new(); + let mut games: Vec = Vec::new(); let extension = PathBuf::from("sgf").into_os_string(); @@ -39,8 +39,12 @@ impl Database { .unwrap() .read_to_string(&mut buffer) .unwrap(); - let sgf = parse_sgf(&buffer).unwrap(); - games.extend(sgf); + for sgf in parse_sgf(&buffer).unwrap() { + match sgf { + Game::Go(game) => games.push(game), + Game::Unsupported(_) => {} + } + } } } Err(err) => println!("failed entry: {:?}", err), @@ -54,7 +58,7 @@ impl Database { self.games.len() } - pub fn all_games(&self) -> impl Iterator { + pub fn all_games(&self) -> impl Iterator { self.games.iter() } } @@ -63,7 +67,7 @@ impl Database { mod test { use super::*; use cool_asserts::assert_matches; - use sgf::{Date, GameType}; + use sgf::Date; #[test] fn it_reads_empty_database() { @@ -77,9 +81,7 @@ mod test { let db = Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open"); assert_eq!(db.all_games().count(), 5); - for game in db.all_games() { - assert_eq!(game.game_type, GameType::Go); - } + for game in db.all_games() {} assert_matches!(db.all_games().find(|g| g.info.black_player == Some("Steve".to_owned())), Some(game) => { diff --git a/kifu/core/src/ui/elements/game_preview.rs b/kifu/core/src/ui/elements/game_preview.rs index 6669d71..1624fb9 100644 --- a/kifu/core/src/ui/elements/game_preview.rs +++ b/kifu/core/src/ui/elements/game_preview.rs @@ -1,5 +1,8 @@ use serde::{Deserialize, Serialize}; -use sgf::{Date, GameTree, Rank}; +use sgf::{ + go::{Game, Rank}, + Date, +}; use typeshare::typeshare; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -13,7 +16,7 @@ pub struct GamePreviewElement { } impl GamePreviewElement { - pub fn new(game: &GameTree) -> GamePreviewElement { + pub fn new(game: &Game) -> GamePreviewElement { GamePreviewElement { date: game.info.date.clone(), black_player: game diff --git a/kifu/core/src/ui/home.rs b/kifu/core/src/ui/home.rs index 7c06ae2..59a064b 100644 --- a/kifu/core/src/ui/home.rs +++ b/kifu/core/src/ui/home.rs @@ -1,6 +1,6 @@ use crate::ui::{Action, GamePreviewElement}; use serde::{Deserialize, Serialize}; -use sgf::GameTree; +use sgf::go::Game; use typeshare::typeshare; fn rank_strings() -> Vec { @@ -56,7 +56,7 @@ pub struct HomeView { pub start_game: Action<()>, } -pub fn home<'a>(games: impl Iterator) -> HomeView { +pub fn home<'a>(games: impl Iterator) -> HomeView { let black_player = PlayerElement::Hotseat(HotseatPlayerElement { placeholder: Some("black player".to_owned()), default_rank: None, diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 831a092..6a610a5 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -76,6 +76,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use typeshare::typeshare; +#[derive(Clone, Debug)] pub struct Game { pub file_format: i8, pub app_name: Option, -- 2.44.1 From 5a471a3dbfc27086457dc9f1a6d478488d0e6ed9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 26 Jul 2023 20:53:21 -0400 Subject: [PATCH 6/6] Add the SGF file to the Go game structure --- sgf/src/go.rs | 25 ++++++++++--------------- sgf/src/tree.rs | 4 ++-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 6a610a5..167b0c2 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -78,23 +78,16 @@ use typeshare::typeshare; #[derive(Clone, Debug)] pub struct Game { - pub file_format: i8, - pub app_name: Option, pub board_size: Size, pub info: GameInfo, + + pub tree: Tree, } impl TryFrom for Game { type Error = Error; fn try_from(tree: Tree) -> Result { - let file_format = match tree.sequence[0].find_prop("FF") { - Some(prop) => prop.values[0].parse::().unwrap(), - None => 4, - }; - let app_name = tree.sequence[0] - .find_prop("AP") - .map(|prop| prop.values[0].clone()); let board_size = match tree.sequence[0].find_prop("SZ") { Some(prop) => Size::try_from(prop.values[0].as_str())?, None => Size { @@ -103,6 +96,9 @@ impl TryFrom for Game { }, }; let mut info = GameInfo::default(); + info.app_name = tree.sequence[0] + .find_prop("AP") + .map(|prop| prop.values[0].clone()); info.black_player = tree.sequence[0] .find_prop("PB") .map(|prop| prop.values.join(", ")); @@ -165,10 +161,10 @@ impl TryFrom for Game { .map(|prop| prop.values.join(", ")); Ok(Game { - file_format, - app_name, board_size, info, + + tree, }) } } @@ -203,6 +199,7 @@ impl ToString for Rank { #[derive(Clone, Debug, Default)] pub struct GameInfo { + pub app_name: Option, pub annotator: Option, pub copyright: Option, pub event: Option, @@ -335,8 +332,7 @@ mod tests { with_text(EXAMPLE, |trees| { assert_eq!(trees.len(), 1); let tree = &trees[0]; - assert_eq!(tree.file_format, 4); - assert_eq!(tree.app_name, None); + assert_eq!(tree.info.app_name, None); assert_eq!( tree.board_size, Size { @@ -350,8 +346,7 @@ mod tests { with_file(std::path::Path::new("test_data/print1.sgf"), |trees| { assert_eq!(trees.len(), 1); let tree = &trees[0]; - assert_eq!(tree.file_format, 4); - assert_eq!(tree.app_name, None); + assert_eq!(tree.info.app_name, None); assert_eq!( tree.board_size, Size { diff --git a/sgf/src/tree.rs b/sgf/src/tree.rs index c0d46ff..59d24c0 100644 --- a/sgf/src/tree.rs +++ b/sgf/src/tree.rs @@ -52,7 +52,7 @@ impl TryFrom<&str> for Size { } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Tree { pub sequence: Vec, pub sub_sequences: Vec, @@ -74,7 +74,7 @@ impl ToString for Tree { } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Node { pub properties: Vec, } -- 2.44.1