1use serde::de::{self, Visitor};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Variant {
16 T4,
18 D4,
20 T5,
22 D5,
24}
25
26impl Variant {
27 pub fn line_len(self) -> u8 {
29 match self {
30 Variant::T4 | Variant::D4 => 4,
31 Variant::T5 | Variant::D5 => 5,
32 }
33 }
34
35 pub fn disjoint(self) -> bool {
38 matches!(self, Variant::D4 | Variant::D5)
39 }
40
41 pub fn code(self) -> &'static str {
43 match self {
44 Variant::T4 => "4T",
45 Variant::D4 => "4D",
46 Variant::T5 => "5T",
47 Variant::D5 => "5D",
48 }
49 }
50
51 pub fn from_code(s: &str) -> Option<Variant> {
53 match s.to_ascii_uppercase().as_str() {
54 "4T" | "T4" => Some(Variant::T4),
55 "4D" | "D4" => Some(Variant::D4),
56 "5T" | "T5" => Some(Variant::T5),
57 "5D" | "D5" => Some(Variant::D5),
58 _ => None,
59 }
60 }
61}
62
63impl fmt::Display for Variant {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str(self.code())
66 }
67}
68
69impl Serialize for Variant {
70 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
71 s.serialize_str(self.code())
72 }
73}
74
75impl<'de> Deserialize<'de> for Variant {
76 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
77 struct V;
78 impl Visitor<'_> for V {
79 type Value = Variant;
80 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str("a variant code such as \"5T\"")
82 }
83 fn visit_str<E: de::Error>(self, v: &str) -> Result<Variant, E> {
84 Variant::from_code(v).ok_or_else(|| E::custom(format!("unknown variant: {v}")))
85 }
86 }
87 d.deserialize_str(V)
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94pub enum Direction {
95 H,
97 V,
99 DP,
101 DN,
103}
104
105impl Direction {
106 pub const ALL: [Direction; 4] = [Direction::H, Direction::V, Direction::DP, Direction::DN];
108
109 pub fn delta(self) -> (i16, i16) {
111 match self {
112 Direction::H => (1, 0),
113 Direction::V => (0, 1),
114 Direction::DP => (1, -1),
115 Direction::DN => (1, 1),
116 }
117 }
118
119 pub fn code(self) -> &'static str {
121 match self {
122 Direction::H => "H",
123 Direction::V => "V",
124 Direction::DP => "DP",
125 Direction::DN => "DN",
126 }
127 }
128
129 pub fn from_code(s: &str) -> Option<Direction> {
131 match s {
132 "H" => Some(Direction::H),
133 "V" => Some(Direction::V),
134 "DP" => Some(Direction::DP),
135 "DN" => Some(Direction::DN),
136 _ => None,
137 }
138 }
139}
140
141impl Serialize for Direction {
142 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
143 s.serialize_str(self.code())
144 }
145}
146
147impl<'de> Deserialize<'de> for Direction {
148 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
149 struct V;
150 impl Visitor<'_> for V {
151 type Value = Direction;
152 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.write_str("a direction code: H, V, DP or DN")
154 }
155 fn visit_str<E: de::Error>(self, v: &str) -> Result<Direction, E> {
156 Direction::from_code(v).ok_or_else(|| E::custom(format!("unknown direction: {v}")))
157 }
158 }
159 d.deserialize_str(V)
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167pub struct RecordMove {
168 pub x: i16,
170 pub y: i16,
172 pub dir: Direction,
174 pub pos: u8,
176}
177
178impl RecordMove {
179 pub fn origin(&self) -> (i16, i16) {
181 let (dx, dy) = self.dir.delta();
182 (self.x - self.pos as i16 * dx, self.y - self.pos as i16 * dy)
183 }
184
185 pub fn line_points(&self, line_len: u8) -> impl Iterator<Item = (i16, i16)> {
187 let (ox, oy) = self.origin();
188 let (dx, dy) = self.dir.delta();
189 (0..line_len as i16).map(move |i| (ox + i * dx, oy + i * dy))
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199pub struct Record {
200 #[serde(default = "default_version", deserialize_with = "de_version")]
203 pub version: String,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub producer: Option<String>,
207 pub variant: Variant,
209 pub score: usize,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub available_moves: Option<usize>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub terminal: Option<bool>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub bbox: Option<[i16; 4]>,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub saved_at: Option<String>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub description: Option<String>,
226 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub author: Option<String>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub source: Option<String>,
234 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub transcribed_by: Option<String>,
239 #[serde(default, skip_serializing_if = "Vec::is_empty")]
241 pub tags: Vec<String>,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub solver: Option<Solver>,
246 pub moves: Vec<RecordMove>,
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct Solver {
254 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub tool: Option<String>,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub method: Option<String>,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub seed: Option<u64>,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub nodes_explored: Option<u64>,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub elapsed_secs: Option<f64>,
273}
274
275impl Solver {
276 pub fn is_empty(&self) -> bool {
278 self.tool.is_none()
279 && self.method.is_none()
280 && self.seed.is_none()
281 && self.nodes_explored.is_none()
282 && self.elapsed_secs.is_none()
283 }
284}
285
286fn default_version() -> String {
287 crate::FORMAT_VERSION.to_owned()
288}
289
290fn de_version<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
293 struct V;
294 impl Visitor<'_> for V {
295 type Value = String;
296 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 f.write_str("a version string like \"0.1\" or an integer")
298 }
299 fn visit_str<E: de::Error>(self, v: &str) -> Result<String, E> {
300 Ok(v.to_owned())
301 }
302 fn visit_string<E: de::Error>(self, v: String) -> Result<String, E> {
303 Ok(v)
304 }
305 fn visit_u64<E: de::Error>(self, v: u64) -> Result<String, E> {
306 Ok(v.to_string())
307 }
308 fn visit_i64<E: de::Error>(self, v: i64) -> Result<String, E> {
309 Ok(v.to_string())
310 }
311 fn visit_f64<E: de::Error>(self, v: f64) -> Result<String, E> {
312 Ok(v.to_string())
313 }
314 }
315 d.deserialize_any(V)
316}
317
318impl Record {
319 pub fn new(variant: Variant, moves: Vec<RecordMove>) -> Record {
322 Record {
323 version: default_version(),
324 producer: None,
325 variant,
326 score: moves.len(),
327 available_moves: None,
328 terminal: None,
329 bbox: None,
330 saved_at: None,
331 description: None,
332 author: None,
333 source: None,
334 transcribed_by: None,
335 tags: Vec::new(),
336 solver: None,
337 moves,
338 }
339 }
340}