use serde_tuple::{Serialize_tuple, Deserialize_tuple};
use riichi_elements::prelude::*;
use crate::{
model::*,
};
use super::{
end_info::*,
entry::*,
};
#[derive(Clone, Debug, Default, Serialize_tuple, Deserialize_tuple)]
#[cfg_attr(test, derive(Eq, PartialEq))] pub struct TenhouRoundRaw {
pub round_id_and_pot: RoundIdAndPot,
pub points: [GamePoints; 4],
pub dora_indicators: Vec<TenhouIncoming>,
pub ura_dora_indicators: Vec<TenhouIncoming>,
pub deal0: Vec<TenhouIncoming>,
pub incoming0: Vec<TenhouIncoming>,
pub outgoing0: Vec<TenhouOutgoing>,
pub deal1: Vec<TenhouIncoming>,
pub incoming1: Vec<TenhouIncoming>,
pub outgoing1: Vec<TenhouOutgoing>,
pub deal2: Vec<TenhouIncoming>,
pub incoming2: Vec<TenhouIncoming>,
pub outgoing2: Vec<TenhouOutgoing>,
pub deal3: Vec<TenhouIncoming>,
pub incoming3: Vec<TenhouIncoming>,
pub outgoing3: Vec<TenhouOutgoing>,
pub end_info: TenhouEndInfo,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct RoundIdAndPot {
pub kyoku: u8,
pub honba: u8,
pub pot_count: u8,
}
impl RoundIdAndPot {
pub fn from_parts(round_id: RoundId, pot_count: u8) -> Self {
Self {
kyoku: round_id.kyoku,
honba: round_id.honba,
pot_count,
}
}
pub fn round_id(self) -> RoundId {
RoundId { kyoku: self.kyoku, honba: self.honba }
}
pub fn to_parts(self) -> (RoundId, u8) {
(self.round_id(), self.pot_count)
}
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use itertools::Itertools;
use crate::{
interop::tenhou_log_json::*,
model::Discard,
yaku::Yaku
};
use super::*;
fn t(s: &str) -> Tile { t!(s) }
fn it(s: &str) -> TenhouIncoming { TenhouIncoming::Draw(t(s)) }
fn ts(s: &str) -> impl Iterator<Item = Tile> + '_ { tiles_from_str(s) }
fn its(s: &str) -> Vec<TenhouIncoming> {
ts(s).into_iter().map(TenhouIncoming::Draw).collect_vec()
}
fn od(s: &str) -> TenhouOutgoing {
TenhouOutgoing::Discard(Discard{
tile: t(s),
called_by: Default::default(),
declares_riichi: false,
is_tsumogiri: false,
})
}
fn odd() -> TenhouOutgoing {
TenhouOutgoing::Discard(Discard{
tile: Default::default(),
called_by: Default::default(),
declares_riichi: false,
is_tsumogiri: true,
})
}
fn or(s: &str) -> TenhouOutgoing {
TenhouOutgoing::Discard(Discard{
tile: t(s),
called_by: Default::default(),
declares_riichi: true,
is_tsumogiri: false,
})
}
#[allow(unused)]
fn orr() -> TenhouOutgoing {
TenhouOutgoing::Discard(Discard{
tile: Default::default(),
called_by: Default::default(),
declares_riichi: true,
is_tsumogiri: true,
})
}
#[test]
fn round_json_example() {
let round_struct = TenhouRoundRaw {
round_id_and_pot: RoundIdAndPot { kyoku: 3, honba: 2, pot_count: 2 },
points: [45200, 19500, 7300, 26000],
dora_indicators: vec![it("8s"), it("8m")],
ura_dora_indicators: vec![],
deal0: its("1147m67p1458s456z"),
incoming0: vec![
it("1p"), it("3p"), it("3z"), it("6p"), it("1z"), it("2m"),
it("1p"), it("9s"), it("6z"),
],
outgoing0: vec![
odd(), od("1s"), od("4z"), od("3z"), od("3p"), od("7m"),
od("4m"), od("6z"), odd(),
],
deal1: its("1247m55p122567s1z"),
incoming1: vec![
it("8p"), it("4p"), it("9s"), it("3m"),
TenhouIncoming::ChiiPonDaiminkan(Meld::Chii(Chii::from_tiles(
t("4p"), t("5p"), t("3p")
).unwrap())),
it("3m"), it("8s"), it("6s"), it("7s"),
],
outgoing1: vec![
od("1m"), od("1z"), od("8p"), od("9s"), od("1s"), odd(),
od("7m"), od("5p")
],
deal2: its("999m1334077p2s36z"),
incoming2: its("2z6p4s2p7m9m4m3m8p"),
outgoing2: vec![
od("3z"), od("2z"), od("6z"), or("3p"), odd(),
TenhouOutgoing::KakanAnkan(Meld::Ankan(Ankan::from_tiles([
t("9m"), t("9m"), t("9m"), t("9m"),
]).unwrap())),
odd(), odd(), odd(),
],
deal3: its("556m149p1348s237z"),
incoming3: its("8p8m4z2s3z8m6z2p3s"),
outgoing3: vec![
od("1p"), od("2z"), odd(), od("3z"), odd(), od("1s"),
odd(), od("6m"), od("8p"),
],
end_info: TenhouEndInfo {
result: ActionResult::Agari(AgariKind::Tsumo),
overall_delta: [-500, 4700, -500, 700],
agari: vec![
TenhouAgariResult {
winner: P1,
contributor: P1,
liable_player: P1,
points_delta_after_pot: [-500, 4700, -500, -700],
scoring: TenhouScoring {
kind: TenhouScoringKind::HanFu { han: 1, fu: 30 },
payout: TenhouPayout::TsumoByNonButton { non_button: 300, button: 500 },
},
details: vec![
YakuOrDora::Yaku(Yaku::Tanyaochuu, 1),
]
},
]
},
};
let round_json = serde_json::json!([
[3, 2, 2], [45200, 19500, 7300, 26000],
[38, 18],
[],
[11, 11, 14, 17, 26, 27, 31, 34, 35, 38, 44, 45, 46], [21, 23, 43, 26, 41, 12, 21, 39, 46],
[60, 31, 44, 43, 23, 17, 14, 46, 60],
[11, 12, 14, 17, 25, 25, 31, 32, 32, 35, 36, 37, 41], [28, 24, 39, 13, "c232425", 13, 38, 36, 37],
[11, 41, 28, 39, 31, 60, 17, 25],
[19, 19, 19, 21, 23, 23, 24, 52, 27, 27, 32, 43, 46], [42, 26, 34, 22, 17, 19, 14, 13, 28],
[43, 42, 46, "r23", 60, "191919a19", 60, 60, 60],
[15, 15, 16, 21, 24, 29, 31, 33, 34, 38, 42, 43, 47], [28, 18, 44, 32, 43, 18, 46, 22, 33],
[21, 42, 60, 43, 60, 31, 60, 16, 28],
["和了", [-500, 4700, -500, -700], [1, 1, 1, "30符1飜300-500点", "断幺九(1飜)"]]
]);
let deserialized = serde_json::from_value::<TenhouRoundRaw>(round_json.clone()).unwrap();
let serialized = serde_json::to_value(&round_struct).unwrap();
assert_json_eq!(round_json, serialized);
assert_json_eq!(round_struct, deserialized);
}
}