use serde::{Deserialize, Serialize};
use crate::parse::TileCounts;
use crate::tile::{Honor, Tile};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WinType {
Ron,
Tsumo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GameContext {
pub win_type: WinType,
pub winning_tile: Option<Tile>,
pub round_wind: Honor,
pub seat_wind: Honor,
pub is_open: bool,
pub is_riichi: bool,
pub is_double_riichi: bool,
pub is_ippatsu: bool,
pub is_rinshan: bool,
pub is_chankan: bool,
pub is_last_tile: bool,
pub is_tenhou: bool,
pub is_chiihou: bool,
pub dora_indicators: Vec<Tile>,
pub ura_dora_indicators: Vec<Tile>,
pub aka_count: u8,
}
impl GameContext {
pub fn new(win_type: WinType, round_wind: Honor, seat_wind: Honor) -> Self {
GameContext {
win_type,
winning_tile: None,
round_wind,
seat_wind,
is_open: false,
is_riichi: false,
is_double_riichi: false,
is_ippatsu: false,
is_rinshan: false,
is_chankan: false,
is_last_tile: false,
is_tenhou: false,
is_chiihou: false,
dora_indicators: Vec::new(),
ura_dora_indicators: Vec::new(),
aka_count: 0,
}
}
pub fn with_winning_tile(mut self, tile: Tile) -> Self {
self.winning_tile = Some(tile);
self
}
pub fn open(mut self) -> Self {
self.is_open = true;
self
}
pub fn riichi(mut self) -> Self {
self.is_riichi = true;
self
}
pub fn double_riichi(mut self) -> Self {
self.is_double_riichi = true;
self.is_riichi = true;
self
}
pub fn ippatsu(mut self) -> Self {
self.is_ippatsu = true;
self
}
pub fn rinshan(mut self) -> Self {
self.is_rinshan = true;
self
}
pub fn chankan(mut self) -> Self {
self.is_chankan = true;
self
}
pub fn last_tile(mut self) -> Self {
self.is_last_tile = true;
self
}
pub fn tenhou(mut self) -> Self {
self.is_tenhou = true;
self
}
pub fn chiihou(mut self) -> Self {
self.is_chiihou = true;
self
}
pub fn with_dora(mut self, indicators: Vec<Tile>) -> Self {
self.dora_indicators = indicators;
self
}
pub fn with_ura_dora(mut self, indicators: Vec<Tile>) -> Self {
self.ura_dora_indicators = indicators;
self
}
pub fn with_aka(mut self, count: u8) -> Self {
self.aka_count = count;
self
}
pub fn is_value_wind(&self, wind: Honor) -> bool {
wind == self.round_wind || wind == self.seat_wind
}
pub fn is_closed(&self) -> bool {
!self.is_open
}
pub fn is_dealer(&self) -> bool {
self.seat_wind == Honor::East
}
}
pub fn indicator_to_dora(indicator: Tile) -> Tile {
match indicator {
Tile::Suited { suit, value } => {
let next_value = if value == 9 { 1 } else { value + 1 };
Tile::suited(suit, next_value)
}
Tile::Honor(honor) => {
let next_honor = match honor {
Honor::East => Honor::South,
Honor::South => Honor::West,
Honor::West => Honor::North,
Honor::North => Honor::East,
Honor::White => Honor::Green,
Honor::Green => Honor::Red,
Honor::Red => Honor::White,
};
Tile::honor(next_honor)
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct DoraCount {
pub regular: u8,
pub ura: u8,
pub aka: u8,
}
impl DoraCount {
pub fn total(&self) -> u8 {
self.regular + self.ura + self.aka
}
}
pub fn count_dora(counts: &TileCounts, context: &GameContext) -> u8 {
count_dora_detailed(counts, context).total()
}
pub fn count_dora_detailed(counts: &TileCounts, context: &GameContext) -> DoraCount {
let mut result = DoraCount::default();
for indicator in &context.dora_indicators {
let dora = indicator_to_dora(*indicator);
result.regular += counts.get(&dora).copied().unwrap_or(0);
}
if context.is_riichi {
for indicator in &context.ura_dora_indicators {
let dora = indicator_to_dora(*indicator);
result.ura += counts.get(&dora).copied().unwrap_or(0);
}
}
result.aka = context.aka_count;
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::{parse_hand, to_counts};
use crate::tile::Suit;
#[test]
fn test_indicator_to_dora_suited() {
assert_eq!(
indicator_to_dora(Tile::suited(Suit::Man, 1)),
Tile::suited(Suit::Man, 2)
);
assert_eq!(
indicator_to_dora(Tile::suited(Suit::Pin, 9)),
Tile::suited(Suit::Pin, 1)
);
}
#[test]
fn test_indicator_to_dora_winds() {
assert_eq!(
indicator_to_dora(Tile::honor(Honor::East)),
Tile::honor(Honor::South)
);
assert_eq!(
indicator_to_dora(Tile::honor(Honor::South)),
Tile::honor(Honor::West)
);
assert_eq!(
indicator_to_dora(Tile::honor(Honor::West)),
Tile::honor(Honor::North)
);
assert_eq!(
indicator_to_dora(Tile::honor(Honor::North)),
Tile::honor(Honor::East)
);
}
#[test]
fn test_indicator_to_dora_dragons() {
assert_eq!(
indicator_to_dora(Tile::honor(Honor::White)),
Tile::honor(Honor::Green)
);
assert_eq!(
indicator_to_dora(Tile::honor(Honor::Green)),
Tile::honor(Honor::Red)
);
assert_eq!(
indicator_to_dora(Tile::honor(Honor::Red)),
Tile::honor(Honor::White)
);
}
#[test]
fn test_count_dora_simple() {
let tiles = parse_hand("222m456p789s11122z").unwrap();
let counts = to_counts(&tiles);
let context = GameContext::new(WinType::Tsumo, Honor::East, Honor::East)
.with_dora(vec![Tile::suited(Suit::Man, 1)]);
assert_eq!(count_dora(&counts, &context), 3);
}
#[test]
fn test_count_dora_with_ura() {
let tiles = parse_hand("222m555p789s11122z").unwrap();
let counts = to_counts(&tiles);
let context = GameContext::new(WinType::Tsumo, Honor::East, Honor::East)
.riichi()
.with_dora(vec![Tile::suited(Suit::Man, 1)])
.with_ura_dora(vec![Tile::suited(Suit::Pin, 4)]);
assert_eq!(count_dora(&counts, &context), 6);
}
#[test]
fn test_count_dora_ura_only_with_riichi() {
let tiles = parse_hand("222m555p789s11122z").unwrap();
let counts = to_counts(&tiles);
let context = GameContext::new(WinType::Tsumo, Honor::East, Honor::East)
.with_dora(vec![Tile::suited(Suit::Man, 1)])
.with_ura_dora(vec![Tile::suited(Suit::Pin, 4)]);
assert_eq!(count_dora(&counts, &context), 3);
}
#[test]
fn test_count_dora_with_aka() {
let tiles = parse_hand("123m456p789s11122z").unwrap();
let counts = to_counts(&tiles);
let context = GameContext::new(WinType::Tsumo, Honor::East, Honor::East).with_aka(2);
assert_eq!(count_dora(&counts, &context), 2);
}
#[test]
fn test_value_wind() {
let context = GameContext::new(WinType::Ron, Honor::East, Honor::South);
assert!(context.is_value_wind(Honor::East)); assert!(context.is_value_wind(Honor::South)); assert!(!context.is_value_wind(Honor::West));
assert!(!context.is_value_wind(Honor::North));
}
#[test]
fn test_builder_pattern() {
let context = GameContext::new(WinType::Tsumo, Honor::South, Honor::West)
.riichi()
.ippatsu()
.with_dora(vec![Tile::suited(Suit::Man, 1)])
.with_aka(1);
assert!(context.is_riichi);
assert!(context.is_ippatsu);
assert_eq!(context.dora_indicators.len(), 1);
assert_eq!(context.aka_count, 1);
assert!(context.is_closed());
}
#[test]
fn test_winning_tile_builder() {
let context = GameContext::new(WinType::Tsumo, Honor::East, Honor::East)
.with_winning_tile(Tile::suited(Suit::Man, 5));
assert_eq!(context.winning_tile, Some(Tile::suited(Suit::Man, 5)));
}
}