use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use serde_derive::{Serialize, Deserialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("Invalid hai number")]
InvalidHaiNumber,
#[error("Invalid player number")]
InvalidPlayerNumber,
#[error("Invalid tenhou rank")]
InvalidTenhouRank,
#[error("Invalid extra ryuukyoku reason")]
InvalidExtraRyuukyokuReason,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Hai(u8);
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Player(u8);
pub type GamePoint = i32;
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
pub enum Direction {
#[default]
SelfSeat,
Shimocha,
Toimen,
Kamicha,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
pub enum TenhouRoom {
#[default]
Ippan,
Joukyu,
Tokujou,
Houou,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
pub enum TenhouRank {
#[default]
Newcomer,
Kyu9,
Kyu8,
Kyu7,
Kyu6,
Kyu5,
Kyu4,
Kyu3,
Kyu2,
Kyu1,
Dan1,
Dan2,
Dan3,
Dan4,
Dan5,
Dan6,
Dan7,
Dan8,
Dan9,
Dan10,
Tenhou,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct GameSettings {
pub vs_human: bool,
pub no_red: bool,
pub no_kuitan: bool,
pub hanchan: bool,
pub sanma: bool,
pub soku: bool,
pub room: TenhouRoom,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct InitSeed {
pub kyoku: u8,
pub honba: u8,
pub kyoutaku: u8,
pub dice: (u8, u8),
pub dora_hyouji: Hai,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Meld {
Chii {
combination: (Hai, Hai, Hai),
called_position: u8,
},
Pon {
dir: Direction,
combination: (Hai, Hai, Hai),
called: Hai,
unused: Hai,
},
Kakan {
dir: Direction,
combination: (Hai, Hai, Hai),
called: Hai,
added: Hai,
},
Daiminkan {
dir: Direction,
hai: Hai,
},
Ankan {
hai: Hai,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub enum ExtraRyuukyokuReason {
#[default]
KyuusyuKyuuhai,
SuuchaRiichi,
SanchaHoura,
SuukanSanra,
SuufuuRenda,
NagashiMangan,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
pub enum ScoreRank {
#[default]
Normal,
Mangan,
Haneman,
Baiman,
Sanbaiman,
Yakuman,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
pub enum Yaku {
#[default]
MenzenTsumo,
Riichi,
Ippatsu,
Chankan,
Rinshankaihou,
HaiteiTsumo,
HouteiRon,
Pinfu,
Tanyao,
Iipeikou,
PlayerWindTon,
PlayerWindNan,
PlayerWindSha,
PlayerWindPei,
FieldWindTon,
FieldWindNan,
FieldWindSha,
FieldWindPei,
YakuhaiHaku,
YakuhaiHatsu,
YakuhaiChun,
DoubleRiichi,
Chiitoitsu,
Chanta,
Ikkitsuukan,
SansyokuDoujun,
SanshokuDoukou,
Sankantsu,
Toitoi,
Sanannkou,
Shousangen,
Honroutou,
Ryanpeikou,
Junchan,
Honiisou,
Chiniisou,
Renhou,
Tenhou,
Chiihou,
Daisangen,
Suuankou,
SuuankouTanki,
Tsuuiisou,
Ryuuiisou,
Chinroutou,
Tyuurenpoutou,
Tyuurenpoutou9,
Kokushimusou,
Kokushimusou13,
Daisuushii,
Syousuushii,
Suukantsu,
Dora,
UraDora,
AkaDora,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionAGARI {
pub honba: u8,
pub kyoutaku: u8,
pub hai: Vec<Hai>,
pub m: Vec<Meld>,
pub machi: Hai,
pub fu: u8,
pub net_score: u32,
pub score_rank: ScoreRank,
pub yaku: Vec<(Yaku, u8)>,
pub yakuman: Vec<Yaku>,
pub dora_hai: Vec<Hai>,
pub dora_hai_ura: Vec<Hai>,
pub who: Player,
pub from_who: Player,
pub pao_who: Option<Player>,
pub before_points: Vec<GamePoint>,
pub delta_points: Vec<GamePoint>,
pub owari: Option<(Vec<GamePoint>, Vec<f64>)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionRYUUKYOKU {
pub honba: u8,
pub kyoutaku: u8,
pub before_points: Vec<GamePoint>,
pub delta_points: Vec<GamePoint>,
pub hai0: Option<Vec<Hai>>,
pub hai1: Option<Vec<Hai>>,
pub hai2: Option<Vec<Hai>>,
pub hai3: Option<Vec<Hai>>,
pub reason: Option<ExtraRyuukyokuReason>,
pub owari: Option<(Vec<GamePoint>, Vec<f64>)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionSHUFFLE {
pub seed: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionGO {
pub settings: GameSettings,
pub lobby: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionUN1 {
pub names: Vec<String>,
pub dan: Vec<TenhouRank>,
pub rate: Vec<f64>,
pub sx: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionUN2 {
pub who: Player,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionBYE {
pub who: Player,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionTAIKYOKU {
pub oya: Player,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionINIT {
pub seed: InitSeed,
pub ten: Vec<GamePoint>,
pub oya: Player,
pub hai: Vec<Vec<Hai>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionREACH1 {
pub who: Player,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionREACH2 {
pub who: Player,
pub ten: Vec<GamePoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionN {
pub who: Player,
pub m: Meld,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionDORA {
pub hai: Hai,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionDRAW {
pub who: Player,
pub hai: Hai,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionDISCARD {
pub who: Player,
pub hai: Hai,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Action {
SHUFFLE(ActionSHUFFLE),
GO(ActionGO),
UN1(ActionUN1),
UN2(ActionUN2),
BYE(ActionBYE),
TAIKYOKU(ActionTAIKYOKU),
INIT(ActionINIT),
REACH1(ActionREACH1),
REACH2(ActionREACH2),
N(ActionN),
DORA(ActionDORA),
AGARI(ActionAGARI),
RYUUKYOKU(ActionRYUUKYOKU),
DRAW(ActionDRAW),
DISCARD(ActionDISCARD),
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Mjlog {
pub ver: f64,
pub actions: Vec<Action>,
}
impl Hai {
pub fn new(x: u8) -> Hai {
Hai(x)
}
pub fn to_u8(&self) -> u8 {
self.0
}
pub fn is_number5(&self) -> bool {
let pict_index = self.0 / 4;
let pict_type = pict_index / 9;
let number = (pict_index % 9) + 1;
pict_type <= 2 && number == 5
}
}
impl Player {
pub fn new(x: u8) -> Self {
Player(x)
}
pub fn to_u8(&self) -> u8 {
self.0
}
}
impl ActionAGARI {
pub fn is_tsumo(&self) -> bool {
self.who == self.from_who
}
}
impl Action {
pub fn as_shuffle(&self) -> Option<&ActionSHUFFLE> {
match self {
Action::SHUFFLE(x) => Some(x),
_ => None,
}
}
pub fn as_go(&self) -> Option<&ActionGO> {
match self {
Action::GO(x) => Some(x),
_ => None,
}
}
pub fn as_un1(&self) -> Option<&ActionUN1> {
match self {
Action::UN1(x) => Some(x),
_ => None,
}
}
pub fn as_un2(&self) -> Option<&ActionUN2> {
match self {
Action::UN2(x) => Some(x),
_ => None,
}
}
pub fn as_bye(&self) -> Option<&ActionBYE> {
match self {
Action::BYE(x) => Some(x),
_ => None,
}
}
pub fn as_taikyoku(&self) -> Option<&ActionTAIKYOKU> {
match self {
Action::TAIKYOKU(x) => Some(x),
_ => None,
}
}
pub fn as_init(&self) -> Option<&ActionINIT> {
match self {
Action::INIT(x) => Some(x),
_ => None,
}
}
pub fn as_reach1(&self) -> Option<&ActionREACH1> {
match self {
Action::REACH1(x) => Some(x),
_ => None,
}
}
pub fn as_reach2(&self) -> Option<&ActionREACH2> {
match self {
Action::REACH2(x) => Some(x),
_ => None,
}
}
pub fn as_n(&self) -> Option<&ActionN> {
match self {
Action::N(x) => Some(x),
_ => None,
}
}
pub fn as_dora(&self) -> Option<&ActionDORA> {
match self {
Action::DORA(x) => Some(x),
_ => None,
}
}
pub fn as_agari(&self) -> Option<&ActionAGARI> {
match self {
Action::AGARI(x) => Some(x),
_ => None,
}
}
pub fn as_ryuukyoku(&self) -> Option<&ActionRYUUKYOKU> {
match self {
Action::RYUUKYOKU(x) => Some(x),
_ => None,
}
}
pub fn as_draw(&self) -> Option<&ActionDRAW> {
match self {
Action::DRAW(x) => Some(x),
_ => None,
}
}
pub fn as_discard(&self) -> Option<&ActionDISCARD> {
match self {
Action::DISCARD(x) => Some(x),
_ => None,
}
}
pub fn is_shuffle(&self) -> bool {
self.as_shuffle().is_some()
}
pub fn is_go(&self) -> bool {
self.as_go().is_some()
}
pub fn is_un1(&self) -> bool {
self.as_un1().is_some()
}
pub fn is_un2(&self) -> bool {
self.as_un2().is_some()
}
pub fn is_bye(&self) -> bool {
self.as_bye().is_some()
}
pub fn is_taikyoku(&self) -> bool {
self.as_taikyoku().is_some()
}
pub fn is_init(&self) -> bool {
self.as_init().is_some()
}
pub fn is_reach1(&self) -> bool {
self.as_reach1().is_some()
}
pub fn is_reach2(&self) -> bool {
self.as_reach2().is_some()
}
pub fn is_n(&self) -> bool {
self.as_n().is_some()
}
pub fn is_dora(&self) -> bool {
self.as_dora().is_some()
}
pub fn is_agari(&self) -> bool {
self.as_agari().is_some()
}
pub fn is_ryuukyoku(&self) -> bool {
self.as_ryuukyoku().is_some()
}
pub fn is_draw(&self) -> bool {
self.as_draw().is_some()
}
pub fn is_discard(&self) -> bool {
self.as_discard().is_some()
}
}
impl std::str::FromStr for Hai {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u8>().map(Hai).map_err(|_| ParseError::InvalidHaiNumber)
}
}
impl std::str::FromStr for Player {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u8>().map(Player).map_err(|_| ParseError::InvalidPlayerNumber)
}
}
impl std::str::FromStr for TenhouRank {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rank = s.parse::<u8>().map_err(|_| ParseError::InvalidTenhouRank)?;
TenhouRank::from_u8(rank).ok_or(ParseError::InvalidTenhouRank)
}
}
impl std::str::FromStr for ExtraRyuukyokuReason {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"yao9" => ExtraRyuukyokuReason::KyuusyuKyuuhai,
"reach4" => ExtraRyuukyokuReason::SuuchaRiichi,
"ron3" => ExtraRyuukyokuReason::SanchaHoura,
"kan4" => ExtraRyuukyokuReason::SuukanSanra,
"kaze4" => ExtraRyuukyokuReason::SuufuuRenda,
"nm" => ExtraRyuukyokuReason::NagashiMangan,
_ => return Err(ParseError::InvalidExtraRyuukyokuReason),
})
}
}
impl Default for Meld {
fn default() -> Self {
Meld::Ankan { hai: Hai::default() }
}
}