Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 32 additions & 28 deletions mahjong/agari.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@


class Agari:
def is_agari(self, tiles_34: Sequence[int], open_sets_34: Optional[Collection[Sequence[int]]] = None) -> bool:
@staticmethod
def is_agari(tiles_34: Sequence[int], open_sets_34: Optional[Collection[Sequence[int]]] = None) -> bool:
"""
Determine was it win or not
:param tiles_34: 34 tiles format array
Expand Down Expand Up @@ -102,47 +103,48 @@ def is_agari(self, tiles_34: Sequence[int], open_sets_34: Optional[Collection[Se
return False

nn0 = (n00 * 1 + n01 * 2) % 3
m0 = self._to_meld(tiles, 0)
m0 = Agari._to_meld(tiles, 0)
nn1 = (n10 * 1 + n11 * 2) % 3
m1 = self._to_meld(tiles, 9)
m1 = Agari._to_meld(tiles, 9)
nn2 = (n20 * 1 + n21 * 2) % 3
m2 = self._to_meld(tiles, 18)
m2 = Agari._to_meld(tiles, 18)

if j & 4:
return (
not (n0 | nn0 | n1 | nn1 | n2 | nn2)
and self._is_mentsu(m0)
and self._is_mentsu(m1)
and self._is_mentsu(m2)
and Agari._is_mentsu(m0)
and Agari._is_mentsu(m1)
and Agari._is_mentsu(m2)
)

if n0 == 2:
return (
not (n1 | nn1 | n2 | nn2)
and self._is_mentsu(m1)
and self._is_mentsu(m2)
and self._is_atama_mentsu(nn0, m0)
and Agari._is_mentsu(m1)
and Agari._is_mentsu(m2)
and Agari._is_atama_mentsu(nn0, m0)
)

if n1 == 2:
return (
not (n2 | nn2 | n0 | nn0)
and self._is_mentsu(m2)
and self._is_mentsu(m0)
and self._is_atama_mentsu(nn1, m1)
and Agari._is_mentsu(m2)
and Agari._is_mentsu(m0)
and Agari._is_atama_mentsu(nn1, m1)
)

if n2 == 2:
return (
not (n0 | nn0 | n1 | nn1)
and self._is_mentsu(m0)
and self._is_mentsu(m1)
and self._is_atama_mentsu(nn2, m2)
and Agari._is_mentsu(m0)
and Agari._is_mentsu(m1)
and Agari._is_atama_mentsu(nn2, m2)
)

return False

def _is_mentsu(self, m: int) -> bool:
@staticmethod
def _is_mentsu(m: int) -> bool:
a = m & 7
b = 0
c = 0
Expand Down Expand Up @@ -180,31 +182,33 @@ def _is_mentsu(self, m: int) -> bool:

return a == 0 or a == 3

def _is_atama_mentsu(self, nn: int, m: int) -> bool:
@staticmethod
def _is_atama_mentsu(nn: int, m: int) -> bool:
if nn == 0:
if (m & (7 << 6)) >= (2 << 6) and self._is_mentsu(m - (2 << 6)):
if (m & (7 << 6)) >= (2 << 6) and Agari._is_mentsu(m - (2 << 6)):
return True
if (m & (7 << 15)) >= (2 << 15) and self._is_mentsu(m - (2 << 15)):
if (m & (7 << 15)) >= (2 << 15) and Agari._is_mentsu(m - (2 << 15)):
return True
if (m & (7 << 24)) >= (2 << 24) and self._is_mentsu(m - (2 << 24)):
if (m & (7 << 24)) >= (2 << 24) and Agari._is_mentsu(m - (2 << 24)):
return True
elif nn == 1:
if (m & (7 << 3)) >= (2 << 3) and self._is_mentsu(m - (2 << 3)):
if (m & (7 << 3)) >= (2 << 3) and Agari._is_mentsu(m - (2 << 3)):
return True
if (m & (7 << 12)) >= (2 << 12) and self._is_mentsu(m - (2 << 12)):
if (m & (7 << 12)) >= (2 << 12) and Agari._is_mentsu(m - (2 << 12)):
return True
if (m & (7 << 21)) >= (2 << 21) and self._is_mentsu(m - (2 << 21)):
if (m & (7 << 21)) >= (2 << 21) and Agari._is_mentsu(m - (2 << 21)):
return True
elif nn == 2:
if (m & (7 << 0)) >= (2 << 0) and self._is_mentsu(m - (2 << 0)):
if (m & (7 << 0)) >= (2 << 0) and Agari._is_mentsu(m - (2 << 0)):
return True
if (m & (7 << 9)) >= (2 << 9) and self._is_mentsu(m - (2 << 9)):
if (m & (7 << 9)) >= (2 << 9) and Agari._is_mentsu(m - (2 << 9)):
return True
if (m & (7 << 18)) >= (2 << 18) and self._is_mentsu(m - (2 << 18)):
if (m & (7 << 18)) >= (2 << 18) and Agari._is_mentsu(m - (2 << 18)):
return True
return False

def _to_meld(self, tiles: list[int], d: int) -> int:
@staticmethod
def _to_meld(tiles: list[int], d: int) -> int:
result = 0
for i in range(0, 9):
result |= tiles[d + i] << i * 3
Expand Down
39 changes: 8 additions & 31 deletions mahjong/hand_calculating/divider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import hashlib
import itertools
import marshal
from collections.abc import Collection, Sequence
from functools import reduce
from typing import Optional
Expand All @@ -11,14 +9,8 @@


class HandDivider:
divider_cache = None
cache_key = None

def __init__(self) -> None:
self.divider_cache = {}

@staticmethod
def divide_hand(
self,
tiles_34: Sequence[int],
melds: Optional[Collection[Meld]] = None,
use_cache: bool = False,
Expand All @@ -32,11 +24,6 @@ def divide_hand(
if not melds:
melds = []

if use_cache:
self.cache_key = self._build_divider_cache_key(tiles_34, melds)
if self.cache_key in self.divider_cache:
return self.divider_cache[self.cache_key]

closed_hand_tiles_34 = list(tiles_34)

# small optimization, we can't have a pair in open part of the hand,
Expand All @@ -45,7 +32,7 @@ def divide_hand(
for open_item in open_tile_indices:
closed_hand_tiles_34[open_item] -= 1

pair_indices = self.find_pairs(closed_hand_tiles_34)
pair_indices = HandDivider.find_pairs(closed_hand_tiles_34)

# let's try to find all possible hand options
hands: list[list[list[int]]] = []
Expand All @@ -59,13 +46,13 @@ def divide_hand(
local_tiles_34[pair_index] -= 2

# 0 - 8 man tiles
man = self.find_valid_combinations(local_tiles_34, 0, 8)
man = HandDivider.find_valid_combinations(local_tiles_34, 0, 8)

# 9 - 17 pin tiles
pin = self.find_valid_combinations(local_tiles_34, 9, 17)
pin = HandDivider.find_valid_combinations(local_tiles_34, 9, 17)

# 18 - 26 sou tiles
sou = self.find_valid_combinations(local_tiles_34, 18, 26)
sou = HandDivider.find_valid_combinations(local_tiles_34, 18, 26)

honor: list = []
for x in HONOR_INDICES:
Expand Down Expand Up @@ -119,12 +106,10 @@ def divide_hand(

result = sorted(hands)

if use_cache:
self.divider_cache[self.cache_key] = result

return result

def find_pairs(self, tiles_34: Sequence[int], first_index: int = 0, second_index: int = 33) -> list[int]:
@staticmethod
def find_pairs(tiles_34: Sequence[int], first_index: int = 0, second_index: int = 33) -> list[int]:
"""
Find all possible pairs in the hand and return their indices
:return: array of pair indices
Expand All @@ -140,8 +125,8 @@ def find_pairs(self, tiles_34: Sequence[int], first_index: int = 0, second_index

return pair_indices

@staticmethod
def find_valid_combinations(
self,
tiles_34: Sequence[int],
first_index: int,
second_index: int,
Expand Down Expand Up @@ -248,11 +233,3 @@ def is_valid_combination(possible_set: tuple[int, int, int]) -> bool:
combinations_results.append(results)

return combinations_results

def clear_cache(self) -> None:
self.divider_cache = {}
self.cache_key = None

def _build_divider_cache_key(self, tiles_34: Sequence[int], melds: Collection[Meld]) -> str:
prepared_array = list(tiles_34) + [x.tiles for x in melds] if melds else list(tiles_34)
return hashlib.md5(marshal.dumps(prepared_array)).hexdigest()
7 changes: 4 additions & 3 deletions mahjong/hand_calculating/fu.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class FuCalculator:
CLOSED_TERMINAL_KAN = "closed_terminal_kan"
OPEN_TERMINAL_KAN = "open_terminal_kan"

@staticmethod
def calculate_fu(
self,
hand: Collection[Sequence[int]],
win_tile: int,
win_group: Sequence[int],
Expand Down Expand Up @@ -154,9 +154,10 @@ def calculate_fu(
else:
fu_details.append({"fu": 30, "reason": FuCalculator.BASE})

return fu_details, self.round_fu(fu_details)
return fu_details, FuCalculator.round_fu(fu_details)

def round_fu(self, fu_details: Collection[dict[str, Any]]) -> int:
@staticmethod
def round_fu(fu_details: Collection[dict[str, Any]]) -> int:
# 22 -> 30 and etc.
fu = sum([x["fu"] for x in fu_details])
return (fu + 9) // 10 * 10
Loading