use std::fmt::{Display, Formatter};
use riichi_elements::prelude::*;
use super::Discard;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum Action {
Discard(Discard),
Ankan(Tile),
Kakan(Tile),
TsumoAgari(Tile),
AbortNineKinds,
}
impl Action {
pub fn from_meld(meld: &Meld) -> Option<Self> {
match meld {
Meld::Kakan(kakan) => Some(Action::Kakan(kakan.added)),
Meld::Ankan(ankan) => Some(Action::Ankan(ankan.own[0].to_normal())),
_ => None,
}
}
pub fn tile(self) -> Option<Tile> {
match self {
Action::Discard(discard) => Some(discard.tile),
Action::Ankan(tile) => Some(tile),
Action::Kakan(tile) => Some(tile),
Action::TsumoAgari(tile) => Some(tile),
Action::AbortNineKinds => None,
}
}
pub fn is_terminal(self) -> bool {
matches!(self, Action::TsumoAgari(_) | Action::AbortNineKinds)
}
pub fn is_kan(self) -> bool {
matches!(self, Action::Ankan(_)| Action::Kakan(_))
}
}
impl Display for Action {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Action::Discard(discard) => write!(f, "{}", discard),
Action::Ankan(tile) => write!(f, "Ankan({})", tile),
Action::Kakan(tile) => write!(f, "Kakan({})", tile),
Action::TsumoAgari(tile) => write!(f, "Tsumo({})", tile),
Action::AbortNineKinds => write!(f, "NineKinds"),
}
}
}
#[cfg(feature = "serde")]
mod action_serde {
use serde::{
de::{Error, MapAccess},
ser::{SerializeStruct},
Deserializer,
Serializer
};
use super::*;
impl serde::Serialize for Action {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> where S: Serializer {
match self {
Action::Discard(discard) => {
let mut st = s.serialize_struct("Action", 4)?;
st.serialize_field("type", "Discard")?;
st.serialize_field("tile", &discard.tile)?;
if discard.declares_riichi {
st.serialize_field("riichi", &true)?;
}
if discard.is_tsumogiri {
st.serialize_field("tsumogiri", &true)?;
}
st.end()
}
Action::Ankan(t) => {
let mut st = s.serialize_struct("Action", 2)?;
st.serialize_field("type", "Ankan")?;
st.serialize_field("tile", &t)?;
st.end()
}
Action::Kakan(t) => {
let mut st = s.serialize_struct("Action", 2)?;
st.serialize_field("type", "Kakan")?;
st.serialize_field("tile", &t)?;
st.end()
}
Action::TsumoAgari(t) => {
let mut st = s.serialize_struct("Action", 2)?;
st.serialize_field("type", "TsumoAgari")?;
st.serialize_field("tile", &t)?;
st.end()
}
Action::AbortNineKinds => {
let mut st = s.serialize_struct("Action", 1)?;
st.serialize_field("type", "AbortNineKinds")?;
st.end()
}
}
}
}
impl<'de> serde::Deserialize<'de> for Action {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
deserializer.deserialize_any(ActionVisitor)
}
}
struct ActionVisitor;
impl<'de> serde::de::Visitor<'de> for ActionVisitor {
type Value = Action;
fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, r#"{{"type": "Discard" or "Ankan" or "Kakan" or "TsumoAgari" or "AbortNineKinds", ...}}"#)
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where A: MapAccess<'de> {
let mut kind = String::new();
let mut tile = Tile::MIN;
let mut declares_riichi = false;
let mut is_tsumogiri = false;
while let Some((key, value)) = map.next_entry::<String, serde_json::Value>()? {
match key.as_str() {
"type" =>
kind = value.as_str()
.ok_or(Error::custom("invalid type"))?
.to_string(),
"tile" =>
tile = value.as_str().and_then(|str| str.parse().ok())
.ok_or(Error::custom("invalid tile"))?,
"tsumogiri" =>
is_tsumogiri = value.as_bool()
.ok_or(Error::custom("invalid tsumogiri"))?,
"riichi" =>
declares_riichi = value.as_bool()
.ok_or(Error::custom("invalid riichi"))?,
_ => {}
}
}
match kind.as_str() {
"Discard" => Ok(Action::Discard(Discard {
tile, declares_riichi, is_tsumogiri, called_by: P0
})),
"Ankan" => Ok(Action::Ankan(tile)),
"Kakan" => Ok(Action::Kakan(tile)),
"TsumoAgari" => Ok(Action::TsumoAgari(tile)),
"AbortNineKinds" => Ok(Action::AbortNineKinds),
_ => Err(Error::custom("invalid type")),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
mod serde_tests {
use assert_json_diff::assert_json_eq;
use super::*;
#[test]
fn serde_discard() {
let action = Action::Discard(Discard{
tile: t!("1m"), called_by: P0, is_tsumogiri: false, declares_riichi: true});
let json = serde_json::json!({
"type": "Discard", "tile": "1m", "riichi": true
});
let serialized = serde_json::to_value(action).unwrap();
let deserialized = serde_json::from_value(json.clone()).unwrap();
assert_json_eq!(serialized, json);
assert_eq!(action, deserialized);
}
#[test]
fn serde_one_arg() {
let action = Action::TsumoAgari(t!("5z"));
let json = serde_json::json!({
"type": "TsumoAgari", "tile": "5z"
});
let serialized = serde_json::to_value(action).unwrap();
let deserialized = serde_json::from_value(json.clone()).unwrap();
assert_json_eq!(serialized, json);
assert_eq!(action, deserialized);
}
#[test]
fn serde_zero_arg() {
let action = Action::AbortNineKinds;
let json = serde_json::json!({
"type": "AbortNineKinds"
});
let serialized = serde_json::to_value(action).unwrap();
let deserialized = serde_json::from_value(json.clone()).unwrap();
assert_json_eq!(serialized, json);
assert_eq!(action, deserialized);
}
}
}