195 lines
4.7 KiB
Rust
195 lines
4.7 KiB
Rust
use alloc::boxed::Box;
|
|
use alloc::string::String;
|
|
use alloc::vec::Vec;
|
|
use mino::srs::PieceType;
|
|
use mino::MatBuf;
|
|
use mino::{srs::Piece, Loc};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Deserializable description of an initial game state. This is intentionally similar to
|
|
/// the TBP "start" message.
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub struct InputState {
|
|
#[serde(flatten)]
|
|
pub queue: InputQueue,
|
|
#[allow(dead_code)]
|
|
#[serde(flatten)]
|
|
_attack: AttackState,
|
|
// XXX(iitalics): TBP uses the terminology "board" but we use "matrix".
|
|
#[serde(rename = "board")]
|
|
pub matrix: InputMatrix,
|
|
}
|
|
|
|
/// Deserializable description of the queue.
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub struct InputQueue {
|
|
#[serde(deserialize_with = "deserialize_hold")]
|
|
pub hold: Option<PieceType>,
|
|
#[serde(deserialize_with = "deserialize_previews")]
|
|
#[serde(rename = "queue")]
|
|
pub previews: Vec<PieceType>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
#[allow(dead_code)]
|
|
struct AttackState {
|
|
combo: u32,
|
|
back_to_back: u32,
|
|
// TODO: use this?
|
|
}
|
|
|
|
// use FromStr to deserialize a PieceType
|
|
|
|
fn deserialize_hold<'de, D>(de: D) -> Result<Option<PieceType>, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
Option::<String>::deserialize(de)?
|
|
.map(|s| s.parse())
|
|
.transpose()
|
|
.map_err(serde::de::Error::custom)
|
|
}
|
|
|
|
fn deserialize_previews<'de, D>(de: D) -> Result<Vec<PieceType>, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
Vec::<String>::deserialize(de)?
|
|
.iter()
|
|
.map(|s| s.parse())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(serde::de::Error::custom)
|
|
}
|
|
|
|
/// Deserializable description of the game matrix.
|
|
#[derive(Clone, Debug)]
|
|
pub struct InputMatrix {
|
|
pub cells: Box<[[Option<Color>; 10]; 40]>,
|
|
}
|
|
|
|
impl InputMatrix {
|
|
/// Converts this matrix to a [`MatBuf`], which discards color information but is
|
|
/// stored more efficiently.
|
|
pub fn to_mat(&self) -> MatBuf {
|
|
let mut mat = MatBuf::new();
|
|
for (y, row) in self.cells.iter().enumerate() {
|
|
for (x, cell) in row.iter().enumerate() {
|
|
if cell.is_some() {
|
|
mat.set(x as i16, y as i16);
|
|
}
|
|
}
|
|
}
|
|
mat
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Deserialize)]
|
|
#[repr(u8)]
|
|
pub enum Color {
|
|
I = 1,
|
|
J = 2,
|
|
L = 3,
|
|
O = 4,
|
|
S = 5,
|
|
T = 6,
|
|
Z = 7,
|
|
G = 8,
|
|
}
|
|
|
|
impl<'de> serde::de::Deserialize<'de> for InputMatrix {
|
|
fn deserialize<D>(de: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
// XXX(iitalics): serde doesn't let you deserialize a [T; 40] so we have to go
|
|
// through Vec first and then check the length :|
|
|
let cells: Vec<_> = serde::de::Deserialize::deserialize(de)?;
|
|
let cells: [_; 40] = cells
|
|
.try_into()
|
|
.map_err(|_| serde::de::Error::custom("board must contain 40 rows"))?;
|
|
Ok(Self {
|
|
cells: Box::new(cells),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Default)]
|
|
pub struct OutputMoves {
|
|
pub moves: Vec<OutputMove>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
pub struct OutputMove {
|
|
pub location: OutputLocation,
|
|
pub spin: Spin,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
pub struct OutputLocation {
|
|
#[serde(serialize_with = "serialize_piece_type")]
|
|
#[serde(rename = "type")]
|
|
pub ty: PieceType,
|
|
#[serde(serialize_with = "serialize_loc")]
|
|
#[serde(flatten)]
|
|
pub location: Loc,
|
|
}
|
|
|
|
impl From<Piece> for OutputLocation {
|
|
fn from(pc: Piece) -> Self {
|
|
Self {
|
|
ty: pc.ty,
|
|
location: pc.loc,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Spin {
|
|
#[default]
|
|
None,
|
|
Mini,
|
|
Full,
|
|
}
|
|
|
|
fn serialize_piece_type<S>(ty: &PieceType, ser: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
ty.as_char().serialize(ser)
|
|
}
|
|
|
|
fn serialize_loc<S>(loc: &Loc, ser: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
use serde::ser::SerializeStruct;
|
|
let mut ser = ser.serialize_struct("Location", 3)?;
|
|
ser.serialize_field("x", &loc.x)?;
|
|
ser.serialize_field("y", &loc.y)?;
|
|
let r = ["north", "east", "south", "west"][loc.r as usize];
|
|
ser.serialize_field("orientation", r)?;
|
|
ser.end()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_color_repr() {
|
|
assert_eq!(
|
|
core::mem::size_of::<Option<Color>>(),
|
|
core::mem::size_of::<u8>()
|
|
);
|
|
assert_eq!(
|
|
core::mem::size_of::<[Option<Color>; 10]>(),
|
|
core::mem::size_of::<[u8; 10]>()
|
|
);
|
|
assert_eq!(
|
|
unsafe { core::mem::transmute::<Option<Color>, u8>(None) },
|
|
0u8
|
|
);
|
|
}
|
|
}
|