use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Variant {
T4,
D4,
T5,
D5,
}
impl Variant {
pub fn line_len(self) -> u8 {
match self {
Variant::T4 | Variant::D4 => 4,
Variant::T5 | Variant::D5 => 5,
}
}
pub fn disjoint(self) -> bool {
matches!(self, Variant::D4 | Variant::D5)
}
pub fn code(self) -> &'static str {
match self {
Variant::T4 => "4T",
Variant::D4 => "4D",
Variant::T5 => "5T",
Variant::D5 => "5D",
}
}
pub fn from_code(s: &str) -> Option<Variant> {
match s.to_ascii_uppercase().as_str() {
"4T" | "T4" => Some(Variant::T4),
"4D" | "D4" => Some(Variant::D4),
"5T" | "T5" => Some(Variant::T5),
"5D" | "D5" => Some(Variant::D5),
_ => None,
}
}
}
impl fmt::Display for Variant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.code())
}
}
impl Serialize for Variant {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.code())
}
}
impl<'de> Deserialize<'de> for Variant {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = Variant;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a variant code such as \"5T\"")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Variant, E> {
Variant::from_code(v).ok_or_else(|| E::custom(format!("unknown variant: {v}")))
}
}
d.deserialize_str(V)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Direction {
H,
V,
DP,
DN,
}
impl Direction {
pub const ALL: [Direction; 4] = [Direction::H, Direction::V, Direction::DP, Direction::DN];
pub fn delta(self) -> (i16, i16) {
match self {
Direction::H => (1, 0),
Direction::V => (0, 1),
Direction::DP => (1, -1),
Direction::DN => (1, 1),
}
}
pub fn code(self) -> &'static str {
match self {
Direction::H => "H",
Direction::V => "V",
Direction::DP => "DP",
Direction::DN => "DN",
}
}
pub fn from_code(s: &str) -> Option<Direction> {
match s {
"H" => Some(Direction::H),
"V" => Some(Direction::V),
"DP" => Some(Direction::DP),
"DN" => Some(Direction::DN),
_ => None,
}
}
}
impl Serialize for Direction {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.code())
}
}
impl<'de> Deserialize<'de> for Direction {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = Direction;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a direction code: H, V, DP or DN")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Direction, E> {
Direction::from_code(v).ok_or_else(|| E::custom(format!("unknown direction: {v}")))
}
}
d.deserialize_str(V)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecordMove {
pub x: i16,
pub y: i16,
pub dir: Direction,
pub pos: u8,
}
impl RecordMove {
pub fn origin(&self) -> (i16, i16) {
let (dx, dy) = self.dir.delta();
(self.x - self.pos as i16 * dx, self.y - self.pos as i16 * dy)
}
pub fn line_points(&self, line_len: u8) -> impl Iterator<Item = (i16, i16)> {
let (ox, oy) = self.origin();
let (dx, dy) = self.dir.delta();
(0..line_len as i16).map(move |i| (ox + i * dx, oy + i * dy))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Record {
#[serde(default = "default_version", deserialize_with = "de_version")]
pub version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub producer: Option<String>,
pub variant: Variant,
pub score: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub available_moves: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub terminal: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bbox: Option<[i16; 4]>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub saved_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transcribed_by: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub solver: Option<Solver>,
pub moves: Vec<RecordMove>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Solver {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nodes_explored: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub elapsed_secs: Option<f64>,
}
impl Solver {
pub fn is_empty(&self) -> bool {
self.tool.is_none()
&& self.method.is_none()
&& self.seed.is_none()
&& self.nodes_explored.is_none()
&& self.elapsed_secs.is_none()
}
}
fn default_version() -> String {
crate::FORMAT_VERSION.to_owned()
}
fn de_version<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = String;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a version string like \"0.1\" or an integer")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<String, E> {
Ok(v.to_owned())
}
fn visit_string<E: de::Error>(self, v: String) -> Result<String, E> {
Ok(v)
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<String, E> {
Ok(v.to_string())
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<String, E> {
Ok(v.to_string())
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<String, E> {
Ok(v.to_string())
}
}
d.deserialize_any(V)
}
impl Record {
pub fn new(variant: Variant, moves: Vec<RecordMove>) -> Record {
Record {
version: default_version(),
producer: None,
variant,
score: moves.len(),
available_moves: None,
terminal: None,
bbox: None,
saved_at: None,
description: None,
author: None,
source: None,
transcribed_by: None,
tags: Vec::new(),
solver: None,
moves,
}
}
}