1use std::collections::HashSet;
13
14use crate::props::parse::{parse_elist, parse_single_value, FromCompressedList};
15use crate::props::{PropertyType, SgfPropError, ToSgf};
16use crate::{InvalidNodeError, SgfNode, SgfParseError, SgfProp};
17
18pub fn parse(text: &str) -> Result<Vec<SgfNode<Prop>>, SgfParseError> {
38 let gametrees = crate::parse(text)?;
39 gametrees
40 .into_iter()
41 .map(|gametree| gametree.into_go_node())
42 .collect::<Result<Vec<_>, _>>()
43}
44
45#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
55pub struct Point {
56 pub x: u8,
57 pub y: u8,
58}
59
60pub type Stone = Point;
62
63#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
78pub enum Move {
79 Pass,
80 Move(Point),
81}
82
83sgf_prop! {
84 Prop, Move, Point, Point,
85 {
86 HA(i64),
87 KM(f64),
88 TB(HashSet<Point>),
89 TW(HashSet<Point>),
90 }
91}
92
93impl SgfProp for Prop {
94 type Point = Point;
95 type Stone = Stone;
96 type Move = Move;
97
98 fn new(identifier: String, values: Vec<String>) -> Self {
99 match Prop::parse_general_prop(identifier, values) {
100 Self::Unknown(identifier, values) => match &identifier[..] {
101 "KM" => parse_single_value(&values)
102 .map_or_else(|_| Self::Invalid(identifier, values), Self::KM),
103
104 "HA" => match parse_single_value(&values) {
105 Ok(value) => {
106 if value < 2 {
107 Self::Invalid(identifier, values)
108 } else {
109 Self::HA(value)
110 }
111 }
112 _ => Self::Invalid(identifier, values),
113 },
114 "TB" => parse_elist(&values)
115 .map_or_else(|_| Self::Invalid(identifier, values), Self::TB),
116 "TW" => parse_elist(&values)
117 .map_or_else(|_| Self::Invalid(identifier, values), Self::TW),
118 _ => Self::Unknown(identifier, values),
119 },
120 prop => prop,
121 }
122 }
123
124 fn identifier(&self) -> String {
125 match self.general_identifier() {
126 Some(identifier) => identifier,
127 None => match self {
128 Self::KM(_) => "KM".to_string(),
129 Self::HA(_) => "HA".to_string(),
130 Self::TB(_) => "TB".to_string(),
131 Self::TW(_) => "TW".to_string(),
132 _ => panic!("Unimplemented identifier for {:?}", self),
133 },
134 }
135 }
136
137 fn property_type(&self) -> Option<PropertyType> {
138 match self.general_property_type() {
139 Some(property_type) => Some(property_type),
140 None => match self {
141 Self::HA(_) => Some(PropertyType::GameInfo),
142 Self::KM(_) => Some(PropertyType::GameInfo),
143 _ => None,
144 },
145 }
146 }
147
148 fn validate_properties(properties: &[Self], is_root: bool) -> Result<(), InvalidNodeError> {
149 Self::general_validate_properties(properties, is_root)
150 }
151}
152
153impl std::fmt::Display for Prop {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let prop_string = match self.serialize_prop_value() {
156 Some(s) => s,
157 None => match self {
158 Self::HA(x) => x.to_sgf(),
159 Self::KM(x) => x.to_sgf(),
160 Self::TB(x) => x.to_sgf(),
161 Self::TW(x) => x.to_sgf(),
162 _ => panic!("Unimplemented identifier for {:?}", self),
163 },
164 };
165 write!(f, "{}[{}]", self.identifier(), prop_string)
166 }
167}
168
169impl FromCompressedList for Point {
170 fn from_compressed_list(ul: &Self, lr: &Self) -> Result<HashSet<Self>, SgfPropError> {
171 let mut points = HashSet::new();
172 if ul.x > lr.x || ul.y > lr.y {
173 return Err(SgfPropError {});
174 }
175 for x in ul.x..=lr.x {
176 for y in ul.y..=lr.y {
177 let point = Self { x, y };
178 if points.contains(&point) {
179 return Err(SgfPropError {});
180 }
181 points.insert(point);
182 }
183 }
184 Ok(points)
185 }
186}
187
188impl ToSgf for Move {
189 fn to_sgf(&self) -> String {
190 match self {
191 Self::Pass => "".to_string(),
192 Self::Move(point) => point.to_sgf(),
193 }
194 }
195}
196
197impl ToSgf for Point {
198 fn to_sgf(&self) -> String {
199 format!("{}{}", (self.x + b'a') as char, (self.y + b'a') as char)
200 }
201}
202
203impl std::str::FromStr for Move {
204 type Err = SgfPropError;
205
206 fn from_str(s: &str) -> Result<Self, Self::Err> {
207 match s {
208 "" => Ok(Self::Pass),
209 _ => Ok(Self::Move(s.parse()?)),
210 }
211 }
212}
213
214impl std::str::FromStr for Point {
215 type Err = SgfPropError;
216
217 fn from_str(s: &str) -> Result<Self, Self::Err> {
218 fn map_char(c: char) -> Result<u8, SgfPropError> {
219 if c.is_ascii_lowercase() {
220 Ok(c as u8 - b'a')
221 } else if c.is_ascii_uppercase() {
222 Ok(c as u8 - b'A' + 26)
223 } else {
224 Err(SgfPropError {})
225 }
226 }
227
228 let chars: Vec<char> = s.chars().collect();
229 if chars.len() != 2 {
230 return Err(SgfPropError {});
231 }
232
233 Ok(Self {
234 x: map_char(chars[0])?,
235 y: map_char(chars[1])?,
236 })
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::Point;
243
244 #[test]
245 fn large_move_numbers() {
246 let point: Point = "aC".parse().unwrap();
247 let expected = Point { x: 0, y: 28 };
248 assert_eq!(point, expected);
249 }
250}