use alloc::vec::Vec;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{Player, Qi};
#[derive(Serialize)]
struct QiOut<'a, P, S> {
shape: &'a [usize],
board: &'a [Option<P>],
first_hand: Vec<(&'a P, usize)>,
second_hand: Vec<(&'a P, usize)>,
first_style: &'a S,
second_style: &'a S,
turn: Player,
}
#[derive(Deserialize)]
struct QiIn<P, S> {
shape: Vec<usize>,
board: Vec<Option<P>>,
first_hand: Vec<(P, usize)>,
second_hand: Vec<(P, usize)>,
first_style: S,
second_style: S,
turn: Player,
}
impl<P: Serialize, S: Serialize> Serialize for Qi<P, S> {
fn serialize<Sr>(&self, serializer: Sr) -> Result<Sr::Ok, Sr::Error>
where
Sr: Serializer,
{
QiOut {
shape: self.shape(),
board: self.board(),
first_hand: self.first_hand().collect(),
second_hand: self.second_hand().collect(),
first_style: self.first_style(),
second_style: self.second_style(),
turn: self.turn(),
}
.serialize(serializer)
}
}
impl<'de, P, S> Deserialize<'de> for Qi<P, S>
where
P: Ord + Deserialize<'de>,
S: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = QiIn::<P, S>::deserialize(deserializer)?;
let qi =
Qi::new(&raw.shape, raw.first_style, raw.second_style).map_err(D::Error::custom)?;
if raw.board.len() != qi.square_count() {
return Err(D::Error::custom(
"serialized board length does not match the shape",
));
}
let placements = raw
.board
.into_iter()
.enumerate()
.filter(|(_, square)| square.is_some());
let qi = qi.board_diff(placements).map_err(D::Error::custom)?;
let first = hand_deltas::<D::Error, _>(raw.first_hand)?;
let second = hand_deltas::<D::Error, _>(raw.second_hand)?;
let qi = qi.first_hand_diff(first).map_err(D::Error::custom)?;
let qi = qi.second_hand_diff(second).map_err(D::Error::custom)?;
Ok(qi.with_turn(raw.turn))
}
}
fn hand_deltas<E, P>(items: Vec<(P, usize)>) -> Result<Vec<(P, i32)>, E>
where
E: serde::de::Error,
{
items
.into_iter()
.map(|(piece, count)| {
i32::try_from(count)
.map(|delta| (piece, delta))
.map_err(|_| E::custom("hand count is too large"))
})
.collect()
}