use crate::types::Meld;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum YakuPossibility {
Possible,
Impossible,
Unknown,
}
impl YakuPossibility {
pub fn to_f32(self) -> f32 {
match self {
YakuPossibility::Possible | YakuPossibility::Unknown => 1.0,
YakuPossibility::Impossible => 0.0,
}
}
}
pub fn check_tanyao(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type.is_multiple_of(9) || tile_type % 9 == 8 || tile_type >= 27 {
return YakuPossibility::Impossible;
}
}
}
YakuPossibility::Unknown
}
fn count_visible_tiles(tile_type: usize, discards: &[u32], dora_indicators: &[u32]) -> usize {
let mut count = 0;
for &tile in discards {
if (tile / 4) as usize == tile_type {
count += 1;
}
}
for &tile in dora_indicators {
if (tile / 4) as usize == tile_type {
count += 1;
}
}
count
}
pub fn check_yakuhai(
tile_type: usize,
melds: &[Meld],
discards: &[u32],
dora_indicators: &[u32],
) -> YakuPossibility {
for meld in melds {
if !meld.tiles.is_empty() {
let meld_tile_type = (meld.tiles[0] / 4) as usize;
if meld_tile_type == tile_type && meld.tiles.len() >= 3 {
return YakuPossibility::Possible;
}
}
}
let visible = count_visible_tiles(tile_type, discards, dora_indicators);
if visible >= 3 {
return YakuPossibility::Impossible;
}
YakuPossibility::Unknown
}
pub fn check_flush(melds: &[Meld]) -> (YakuPossibility, YakuPossibility) {
if melds.is_empty() {
return (YakuPossibility::Unknown, YakuPossibility::Unknown);
}
let mut has_man = false;
let mut has_pin = false;
let mut has_sou = false;
let mut has_honor = false;
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type < 9 {
has_man = true;
} else if tile_type < 18 {
has_pin = true;
} else if tile_type < 27 {
has_sou = true;
} else {
has_honor = true;
}
}
}
let num_suits = [has_man, has_pin, has_sou].iter().filter(|&&x| x).count();
let honitsu = if num_suits == 0 {
YakuPossibility::Unknown
} else if num_suits == 1 {
YakuPossibility::Possible
} else {
YakuPossibility::Impossible
};
let chinitsu = if num_suits == 0 {
YakuPossibility::Unknown
} else if num_suits == 1 && !has_honor {
YakuPossibility::Possible
} else if num_suits >= 2 || has_honor {
YakuPossibility::Impossible
} else {
YakuPossibility::Unknown
};
(honitsu, chinitsu)
}
pub fn check_toitoi(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
if meld.tiles.len() == 3 {
let t0 = (meld.tiles[0] / 4) as usize;
let t1 = (meld.tiles[1] / 4) as usize;
let t2 = (meld.tiles[2] / 4) as usize;
if t0 + 1 == t1 && t1 + 1 == t2 && t0 < 27 {
return YakuPossibility::Impossible;
}
}
}
if !melds.is_empty()
&& melds
.iter()
.all(|m| m.tiles.len() >= 3 && m.tiles[0] / 4 == m.tiles[1] / 4)
{
return YakuPossibility::Possible;
}
YakuPossibility::Unknown
}
pub fn check_chiitoitsu(melds: &[Meld]) -> YakuPossibility {
if !melds.is_empty() {
return YakuPossibility::Impossible;
}
YakuPossibility::Unknown
}
pub fn check_shousangen(
_melds: &[Meld],
discards: &[u32],
dora_indicators: &[u32],
) -> YakuPossibility {
let dragons = [31, 32, 33];
let mut impossible_count = 0;
for &dragon_type in &dragons {
let visible = count_visible_tiles(dragon_type, discards, dora_indicators);
if visible >= 4 {
impossible_count += 1;
}
}
if impossible_count >= 1 {
return YakuPossibility::Impossible;
}
YakuPossibility::Unknown
}
pub fn check_daisangen(
melds: &[Meld],
discards: &[u32],
dora_indicators: &[u32],
) -> YakuPossibility {
let dragons = [31, 32, 33];
let mut pon_count = 0;
for &dragon_type in &dragons {
let has_pon = melds.iter().any(|m| {
m.tiles.len() >= 3 && !m.tiles.is_empty() && (m.tiles[0] / 4) as usize == dragon_type
});
if has_pon {
pon_count += 1;
} else {
let visible = count_visible_tiles(dragon_type, discards, dora_indicators);
if visible >= 2 {
return YakuPossibility::Impossible;
}
}
}
if pon_count == 3 {
return YakuPossibility::Possible;
}
YakuPossibility::Unknown
}
pub fn check_tsuuiisou(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type < 27 {
return YakuPossibility::Impossible;
}
}
}
YakuPossibility::Unknown
}
pub fn check_chinroutou(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type >= 27 || (!tile_type.is_multiple_of(9) && tile_type % 9 != 8) {
return YakuPossibility::Impossible;
}
}
}
YakuPossibility::Unknown
}
pub fn check_honroutou(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type < 27 && !tile_type.is_multiple_of(9) && tile_type % 9 != 8 {
return YakuPossibility::Impossible;
}
}
}
YakuPossibility::Unknown
}
pub fn check_kokushi(melds: &[Meld], discards: &[u32], dora_indicators: &[u32]) -> YakuPossibility {
if !melds.is_empty() {
return YakuPossibility::Impossible;
}
let required_types = [0, 8, 9, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33];
for &tile_type in &required_types {
let visible = count_visible_tiles(tile_type, discards, dora_indicators);
if visible >= 4 {
return YakuPossibility::Impossible;
}
}
YakuPossibility::Unknown
}
pub fn check_chanta(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
if meld.tiles.is_empty() {
continue;
}
let mut has_terminal_or_honor = false;
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type.is_multiple_of(9) || tile_type % 9 == 8 || tile_type >= 27 {
has_terminal_or_honor = true;
break;
}
}
if !has_terminal_or_honor {
return YakuPossibility::Impossible;
}
}
YakuPossibility::Unknown
}
pub fn check_junchan(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
if meld.tiles.is_empty() {
continue;
}
let mut has_terminal = false;
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type >= 27 {
return YakuPossibility::Impossible;
}
if tile_type.is_multiple_of(9) || tile_type % 9 == 8 {
has_terminal = true;
}
}
if !has_terminal {
return YakuPossibility::Impossible;
}
}
YakuPossibility::Unknown
}
pub fn check_sanshoku_doujun(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type >= 27 {
continue;
}
}
}
YakuPossibility::Unknown
}
pub fn check_iipeikou(melds: &[Meld]) -> YakuPossibility {
if !melds.is_empty() {
return YakuPossibility::Impossible;
}
YakuPossibility::Unknown
}
pub fn check_ittsu(melds: &[Meld]) -> YakuPossibility {
for meld in melds {
for &tile in &meld.tiles {
let tile_type = (tile / 4) as usize;
if tile_type >= 27 {
continue;
}
}
}
YakuPossibility::Unknown
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::MeldType;
#[test]
fn test_tanyao() {
assert_eq!(check_tanyao(&[]), YakuPossibility::Unknown);
let meld = Meld {
meld_type: MeldType::Pon,
tiles: vec![0, 1, 2], opened: true,
from_who: -1,
called_tile: None,
};
assert_eq!(check_tanyao(&[meld]), YakuPossibility::Impossible);
let meld = Meld {
meld_type: MeldType::Pon,
tiles: vec![16, 17, 18], opened: true,
from_who: -1,
called_tile: None,
};
assert_eq!(check_tanyao(&[meld]), YakuPossibility::Unknown);
}
#[test]
fn test_toitoi() {
let meld = Meld {
meld_type: MeldType::Chi,
tiles: vec![0, 4, 8], opened: true,
from_who: -1,
called_tile: None,
};
assert_eq!(check_toitoi(&[meld]), YakuPossibility::Impossible);
let meld = Meld {
meld_type: MeldType::Pon,
tiles: vec![16, 17, 18], opened: true,
from_who: -1,
called_tile: None,
};
assert_eq!(check_toitoi(&[meld]), YakuPossibility::Possible);
}
}