「mahjong」 Did you know that Python has a library called "** mahjong **"?
↓ Python library "** mahjong **" https://pypi.org/project/mahjong/
As the name implies, this library is a ** Mahjong ** library! (English translation of mahjong is "mahjong")
This time, I summarized the contents of the above URL and actually used it!
In a word, what mahjong can do
「Mahjong hands calculation」 In other words, you can do mahjong hand calculations.
Let's read the Project description part in the URL article!
Python2.7 and 3.5+ are supported. We support the Japanese version of mahjong only (riichi mahjong).
If you use the latest version of Python, there should be no problem. And it is written that only Japanese reach mahjong is supported.
Chinese mahjong has more roles than Japanese reach mahjong, It's complicated because Furiten is okay ...
Riichi mahjong hands calculation
This library can calculate hand cost (han, fu with details, yaku, and scores) for riichi mahjong(Japanese version).
In this library, you can calculate "** translation ", " mark number (including details) ", " role ", and " score **" of reach mahjong. That's right!
Great ... it looks horribly hard to implement normally ...
In addition, it seems that it also supports the following rule changes as an option. (Simplified the contents of the listed table.)
I am surprised that it supports quite a few options.
Counting Yakuman and Pinfu are confusing, If you don't touch it, you can follow the basic rules.
The code was validated on tenhou.net phoenix replays in total on 11,120,125 hands. So, we can say that our hand calculator works the same way that tenhou.net hand calculation.
And this library contains ** 11,120,125 ** Agari hands from the famous mahjong game "** Tenhou **". It seems that you can confirm it!
You can use a library that can perform calculations similar to Tenhou for free ... God
Let's try various things!
First, get ready! If Python is the latest, there is no problem! Like any other library
pip install mahjong
And you're ready to go!
Let's see if we can calculate it right away!
#Calculation
from mahjong.hand_calculating.hand import HandCalculator
#Mahjong tiles
from mahjong.tile import TilesConverter
#Role,Optional rules
from mahjong.hand_calculating.hand_config import HandConfig, OptionalRules
#Squeal
from mahjong.meld import Meld
#Wind(Place&Self)
from mahjong.constants import EAST, SOUTH, WEST, NORTH
#HandCalculator(Calculation class)Instantiate
calculator = HandCalculator()
#For result output
def print_hand_result(hand_result):
#Transliteration,Number of marks
print(hand_result.han, hand_result.fu)
#Score(In the case of Tsumoagari[Left: Parental goal,right:Child goal],In the case of Ron Agari[left:Gunner conceded,right:0])
print(hand_result.cost['main'], result.cost['additional'])
#Role
print(hand_result.yaku)
#Details of the number of marks
for fu_item in hand_result.fu_details:
print(fu_item)
print('')
After that, information such as Agari shape
caluculator.estimate_hand_value()
You can calculate it by giving it as an argument to!
The argument of caluculator.estimate_hand_value ()
is
・ Tiles (** Mahjong tiles Agari form )
・ Win_tile ( Agari tile )
・ Melds ( squeal )
・ Dora_indicators ( Dora )
-There is a config ( option **).
https://github.com/MahjongRepository/mahjong/blob/master/mahjong/hand_calculating/hand.py
** If you aggress with Ron ** Player: Child 3 transliteration 40 marks Gunner: 5200 points Role: Tanyao, Sanshokudokou
example01_ron.py
#Agari shape(man=Mans, pin=Pins, sou=Swords, honors=Tile)
tiles = TilesConverter.string_to_136_array(man='234555', pin='555', sou='22555')
#Agari tile(Swords 5)
win_tile = TilesConverter.string_to_136_array(sou='5')[0]
#Squeal(None)
melds = None
#Dora(None)
dora_indicators = None
#option(None)
config = None
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
print_hand_result(result)
Result output
>3 40
5200 0
[Tanyao, Sanshoku Doukou]
{'fu': 30, 'reason': 'base'} #Menzenron
{'fu': 4, 'reason': 'closed_pon'} #Imprint
{'fu': 4, 'reason': 'closed_pon'} #Imprint
{'fu': 2, 'reason': 'open_pon'} #Tomorrow
** If you aggress with Tsumo ** Player: Child 6 translations 40 notes Parent: 6000 points, Child 3000 points Roles: Menzentsumo, Tanyao, San Ankou, Sanshoku Dokou
example01_tsumo.py
#Agari shape(Same as above)
tiles = TilesConverter.string_to_136_array(man='234555', pin='555', sou='22555')
#Agari tile(Same as above)
win_tile = TilesConverter.string_to_136_array(sou='5')[0]
#Squeal(None)
melds = None
#Dora(None)
dora_indicators = None
#option(Add Tsumo,If False, Ron)
config = HandConfig(is_tsumo=True)
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
print_hand_result(result)
Output result
>6 40
6000 3000
[Menzen Tsumo, Tanyao, San Ankou, Sanshoku Doukou]
{'fu': 20, 'reason': 'base'}
{'fu': 4, 'reason': 'closed_pon'} #Imprint
{'fu': 4, 'reason': 'closed_pon'} #Imprint
{'fu': 4, 'reason': 'closed_pon'} #Imprint
{'fu': 2, 'reason': 'tsumo'} #Tsumo
The difference between Ron and Tsumo is
It is an argument of caluculator.estimate_hand_value ()
Whether or not there is config = HandConfig (is_tsumo = True)
in config
(optional).
Besides Tsumo
・ Reach → ʻis_riichi ・ Ippatsu → ʻis_ippatsu
・ Rinshan Kaihou → ʻis_rinshan ・ Chankan → ʻis_chankan
・ High Tay → ʻis_haitei ・ Hotei → ʻis_houtei
・ Double reach → ʻis_daburu_riichi ・ Sinking manganese → ʻis_nagashi_mangan
・ Tenhou → ʻis_tenhou ・ Renho → ʻis_renhou
・ Chiho → ʻis_chiihou`
Exists, so you can set it by doing the same as ʻis_tsumo = True`.
** If the wind is east and the wind is south ** Player: Child, Self-Wind: South 4 transliteration 40 marks Gunner: 8000 points Roles: Reach, Yakuhai (self-winding), Dora 2
example02_south.py
#Agari shape(honors=1:east, 2:South, 3:West, 4:North, 5:White, 6:發, 7:During ~)
tiles = TilesConverter.string_to_136_array(man='677889', pin='88', sou='456', honors='222')
#Agari tile(Man's 8)
win_tile = TilesConverter.string_to_136_array(man='8')[0]
#Squeal(None)
melds = None
#Dora(Display tile,裏Dora)
dora_indicators = [
TilesConverter.string_to_136_array(pin='7')[0],
TilesConverter.string_to_136_array(sou='9')[0],
]
#option(reach,Self-wind,Field wind)
config = HandConfig(is_riichi=True, player_wind=SOUTH, round_wind=EAST)
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>4 40
8000 0
[Riichi, Yakuhai (wind of place), Dora 2]
{'fu': 30, 'reason': 'base'} #Menzenron
{'fu': 8, 'reason': 'closed_terminal_pon'} #Imprint of Yaochu tiles
** If the wind is east and the wind is east ** Player: Parent, Self-Wind: East 3 transliteration 40 marks Gunner: 7700 points Role: Reach, Dora 2
example02_east.py
#Agari shape(honors=1:east, 2:South, 3:West, 4:North, 5:White, 6:發, 7:During ~)
tiles = TilesConverter.string_to_136_array(man='677889', pin='88', sou='456', honors='222')
#Agari tile(Man's 8)
win_tile = TilesConverter.string_to_136_array(man='8')[0]
#Squeal(None)
melds = None
#Dora(Display tile,裏Dora)
dora_indicators = [
TilesConverter.string_to_136_array(pin='7')[0],
TilesConverter.string_to_136_array(sou='9')[0],
]
#option(reach,Self-wind,Field wind)
config = HandConfig(is_riichi=True, player_wind=EAST, round_wind=EAST)
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>3 40
7700 0
[Riichi, Dora 2]
{'fu': 30, 'reason': 'base'} #Menzenron
{'fu': 8, 'reason': 'closed_terminal_pon'} #Imprint of Yaochu tiles
Dora can be set by writing a display tile in dora_indicators
.
Self-wind is player_wind
of config
, field wind is round_wind
of config
It can be set by specifying ʻEAST(east),
SOUTH (south),
WEST(west),
NORTH(north). In other words, you can set the player as a parent by setting
player_wind = EAST`.
** In the case of Linshan Kaihou ** Player: Child 3 translations 40 notes Parent: 2600 points, Child: 1300 points Roles: Linshan Kaihou, Tanyao, Red Dora 1
example03_rinshan.py
#Agari shape(Red dora is 0,Or use r(Any order is OK), has_aka_dora=Change to True)
tiles = TilesConverter.string_to_136_array(man='022246', pin='333', sou='33567', has_aka_dora=True)
#Agari tile(Manz 6)
win_tile = TilesConverter.string_to_136_array(man='6')[0]
#Squeal(Qi:CHI,Pong:PON,Can:KAN(True:Minkan,False:Ankan),Kakan:CHANKAN,Nukidora:NUKI)
melds = [
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False),
Meld(Meld.PON, TilesConverter.string_to_136_array(pin='333')),
Meld(Meld.CHI, TilesConverter.string_to_136_array(sou='567'))
]
#Dora(None)
dora_indicators = None
#option(Tsumo,Rinshan Kaihou,Added Inspector Gourmet / Red Dora Rule)
config = HandConfig(is_tsumo=True,is_rinshan=True, options=OptionalRules(has_open_tanyao=True, has_aka_dora=True))
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>3 40
2600 1300
[Rinshan Kaihou, Tanyao, Aka Dora 1]
{'fu': 20, 'reason': 'base'}
{'fu': 16, 'reason': 'closed_kan'} #Can sign(Ankan)
{'fu': 2, 'reason': 'open_pon'}
{'fu': 2, 'reason': 'tsumo'}
** For Ron Agari ** Player: Child 2 transliteration 40 marks Gunner: 2600 points Role: Tanyao, Red Dora 1
example03_ron.py
#Agari shape(Same as above)
tiles = TilesConverter.string_to_136_array(man='022246', pin='333', sou='33567', has_aka_dora=True)
#Agari tile(Same as above)
win_tile = TilesConverter.string_to_136_array(man='6')[0]
#Squeal(Same as above)
melds = [
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False),
Meld(Meld.PON, TilesConverter.string_to_136_array(pin='333')),
Meld(Meld.CHI, TilesConverter.string_to_136_array(sou='567'))
]
#Dora(None)
dora_indicators = None
#option(Added Inspector Gourmet / Red Dora Rule)
config = HandConfig(options=OptionalRules(has_open_tanyao=True, has_aka_dora=True))
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>2 40
2600 0
[Tanyao, Aka Dora 1]
{'fu': 20, 'reason': 'base'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 2, 'reason': 'open_pon'}
Because of Inspector Gourmet, even if ʻis_tsumo = True` is added There is no Tsumo in the role, and there is Tanyao properly.
If red dora is included, Don't forget to set the Agari shape and the options respectively!
Other optional rules besides Inspector Gourmet and Red Dora
It can be set with HandConfig (options)
of config
.
・ Double Yakuman → has_double_yakuman (T or F)
・ Counting Yakuman → kazoe_limit (kazoe_limit = HandConfig.KAZOE_LIMITED, HandConfig.KAZOE_SANBAIMAN, HandConfig.KAZOE_NO_LIMIT)
-Round-up manganese → kiriage (T or F)
・ Pinfu → fu_for_open_pinfu (T or F)
・ Pinfutsumo → fu_for_pinfu_tsumo (T or F)
・ Renho → renhou_as_yakuman (T or F)
・ Daisharin → has_daisharin (T or F)
・ Daichikrin & Daisuurin → has_daisharin_other_suits (T or F)
** When 13 or more translations are used as Yakuman (normal counting Yakuman) ** Player: Child 29 translation 80 points Parent: 16000 points, Child: 8000 points Roles: Rinshan Kaihou, Toy Toy, San Ankou, Sankantsu, Chinitsu, Red 1, Dora 16
example04_limited.py
#Agari shape(If there is only one type of tile, has_aka_No need for dora)
tiles = TilesConverter.string_to_136_array(man='22244455777999')
#Agari tile(Manz Red 5,However, the meaning is the same as 5 of ordinary Manz)
win_tile = TilesConverter.string_to_136_array(man='5')[0]
#Squeal(Daiminkan:true,Ankan:False)
melds = [
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='7777'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='9999'), True)
]
#Dora(Display tiles for the number of tiles)
dora_indicators = [
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='6')[0],
TilesConverter.string_to_136_array(man='8')[0],
]
#option(Optional Rules kazoe_limit to KAZOE_NO_To LIMIT,If there is red, set here)
config = HandConfig(is_tsumo=True,is_rinshan=True,options=OptionalRules(kazoe_limit=HandConfig.KAZOE_LIMITED, has_aka_dora=True))
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>29 80
16000 8000
[Rinshan Kaihou, Toitoi, San Ankou, San Kantsu, Chinitsu, Dora 16, Aka Dora 1]
{'fu': 20, 'reason': 'base'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'open_terminal_kan'}
{'fu': 4, 'reason': 'closed_pon'}
{'fu': 2, 'reason': 'pair_wait'}
{'fu': 2, 'reason': 'tsumo'}
** When 13 or more translations are made as Sunbaiman ** Player: Child 29 translation 80 points Parent: 12000 points, Child: 6000 points Roles: Rinshan Kaihou, Toy Toy, San Ankou, Sankantsu, Chinitsu, Red 1, Dora 16
example04_sanbaiman.py
#Agari shape(Same as above)
tiles = TilesConverter.string_to_136_array(man='22244455777999')
#Agari tile(Same as above)
win_tile = TilesConverter.string_to_136_array(man='5')[0]
#Squeal(Same as above)
melds = [
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='7777'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='9999'), True)
]
#Dora(Same as above)
dora_indicators = [
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='6')[0],
TilesConverter.string_to_136_array(man='8')[0],
]
#option(Optional Rules kazoe_limit to KAZOE_To SANBAIMAN)
config = HandConfig(is_tsumo=True,is_rinshan=True,options=OptionalRules(kazoe_limit=HandConfig.KAZOE_SANBAIMAN, has_aka_dora=True))
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>29 80
12000 6000
[Rinshan Kaihou, Toitoi, San Ankou, San Kantsu, Chinitsu, Dora 16, Aka Dora 1]
{'fu': 20, 'reason': 'base'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'open_terminal_kan'}
{'fu': 4, 'reason': 'closed_pon'}
{'fu': 2, 'reason': 'pair_wait'}
{'fu': 2, 'reason': 'tsumo'}
** When 13 or more translations are Yakuman and 26 or more translations are Double Yakuman ** Player: Child 29 translation 80 points Parent: 32000 points, Child: 16000 points Roles: Rinshan Kaihou, Toy Toy, San Ankou, Sankantsu, Chinitsu, Red 1, Dora 16
example04_no_limit.py
#Agari shape(Same as above)
tiles = TilesConverter.string_to_136_array(man='22244455777999')
#Agari tile(Same as above)
win_tile = TilesConverter.string_to_136_array(man='5')[0]
#Squeal(Same as above)
melds = [
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='7777'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False),
Meld(Meld.KAN, TilesConverter.string_to_136_array(man='9999'), True)
]
#Dora(Same as above)
dora_indicators = [
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='1')[0],
TilesConverter.string_to_136_array(man='6')[0],
TilesConverter.string_to_136_array(man='8')[0],
]
#option(Optional Rules kazoe_limit to KAZOE_NO_To LIMIT)
config = HandConfig(is_tsumo=True,is_rinshan=True,options=OptionalRules(kazoe_limit=HandConfig.KAZOE_NO_LIMIT, has_aka_dora=True))
#Calculation
result = calculator.estimate_hand_value(tiles, win_tile,melds,dora_indicators, config)
print_hand_result(result)
>29 80
32000 16000
[Rinshan Kaihou, Toitoi, San Ankou, San Kantsu, Chinitsu, Dora 16, Aka Dora 1]
{'fu': 20, 'reason': 'base'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'closed_kan'}
{'fu': 16, 'reason': 'open_terminal_kan'}
{'fu': 4, 'reason': 'closed_pon'}
{'fu': 2, 'reason': 'pair_wait'}
{'fu': 2, 'reason': 'tsumo'}
As a bonus, it has an agari shape like Saki-san.
If the chiniisou contains red dora,
Has_aka_dora = True
is not required for Tiles
(Agari form)
Only config
(optional) is fine.
(If Tiles
has has_aka_dora = True
, an error will be returned.)
Add as many tiles as you see to dora_indicators
.
mahjong is not just a manual calculation You can also calculate the number of chantens!
example04_shanten.py
#Number of chanten
from mahjong.shanten import Shanten
#Shanten(Chanten number calculation class)Instantiate
shanten = Shanten()
#14 Tehai
tiles = TilesConverter.string_to_34_array(man='13569', pin='123459', sou='443')
#Calculation
result = shanten.calculate_shanten(tiles)
print(result)
#result
>2
This time, I summarized it as a memorandum for myself. I think I almost made up for the lack of explanation. (I haven't tried Nukidora or special option rules, but w)
This library is irresistible as a mahjong lover! (Lol) I would like to develop a new mahjong app using this library.
Recommended Posts