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, #[serde(deserialize_with = "deserialize_previews")] #[serde(rename = "queue")] pub previews: Vec, } #[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, D::Error> where D: serde::Deserializer<'de>, { Option::::deserialize(de)? .map(|s| s.parse()) .transpose() .map_err(serde::de::Error::custom) } fn deserialize_previews<'de, D>(de: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { Vec::::deserialize(de)? .iter() .map(|s| s.parse()) .collect::, _>>() .map_err(serde::de::Error::custom) } /// Deserializable description of the game matrix. #[derive(Clone, Debug)] pub struct InputMatrix { pub cells: Box<[[Option; 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(de: D) -> Result 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, } #[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 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(ty: &PieceType, ser: S) -> Result where S: serde::Serializer, { ty.as_char().serialize(ser) } fn serialize_loc(loc: &Loc, ser: S) -> Result 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::>(), core::mem::size_of::() ); assert_eq!( core::mem::size_of::<[Option; 10]>(), core::mem::size_of::<[u8; 10]>() ); assert_eq!( unsafe { core::mem::transmute::, u8>(None) }, 0u8 ); } }