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::>())