What is "mahjong" in the Python library? ??

「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!

Library details

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.)

  1. Inspector Gourmet (with or without)
  2. Red dora (with or without)
  3. Double Yakuman (with or without)
  4. Counting Yakuman (13 or more is full, 13 or more is tripled, 13 or more is full, 26 or more is double)
  5. Round-up manganese (with or without)
  6. Pinf (1 translation 20 notes or 1 translation 30 notes)
  7. Pinfutsumo (with or without +2 mark of Tsumo)
  8. Renho (5 translations or full role)
  9. Daisharin, Daiichikrin, Daisuulin (with or without)

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

I tried using mahjong!

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

Example 1 (Ron or Tsumo)

IMG_3852.jpg ** 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`.

Example 2 (Dora, Wind)

IMG_3853.jpg

** 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 settingplayer_wind = EAST`.

Example 3 (squeal, option rule, red dora)

IMG_3856.jpg ** 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)

Example 4 (Chinitsu, Counting Yakuman, Saki-san)

IMG_3900.jpg

** 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.

Number of chanten

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

Summary

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

What is "mahjong" in the Python library? ??
What is wheezy in the Docker Python image?
[Python] What is @? (About the decorator)
[python] What is the sorted key?
What is the python underscore (_) for?
What is python
What is Python
What to do when the value type is ambiguous in Python?
[Python] What is Pipeline ...
[Python] What is virtualenv
How to use the C library in Python
What is "functional programming" and "object-oriented" in Python?
About the difference between "==" and "is" in python
Use the LibreOffice app in Python (3) Add library
[Modint] Decoding the AtCoder Library ~ Implementation in Python ~
What kind of book is the best-selling "Python Crash Course" in the world?
What is the fastest way to create a reverse dictionary in python?
Download the file in Python
What is the activation function?
Find the difference in Python
What is the Linux kernel?
What is the domain attribute written in Plotly's Layout?
Overriding library functions in Python
Find the part that is 575 from Wikipedia in Python
[Python] Python and security-① What is Python?
[Python] * args ** What is kwrgs?
Using the National Diet Library Search API in Python
What is the interface for ...
What I learned in Python
What is the Callback function?
What is a python map?
What does the last () in a function mean in Python?
[Internal_math version (2)] Decoding the AtCoder Library ~ Implementation in Python ~
Python Basic Course (1 What is Python)
What to do if the progress bar is not displayed in tqdm of python
Test.py is not reflected on the web server in Python3.
How to debug the Python standard library in Visual Studio
What you can do with the Python standard library statistics
Automatically get the port where Arduino is stuck in Python
What is the default TLS version of the python requests module?
What beginners learned from the basics of variables in python
What is Python? What is it used for?
[Python] What is a zip function?
[Python] What is a with statement?
What is labeling in financial forecasting?
Getting the arXiv API in Python
Difference between == and is in python
Use fabric as is in python (fabric3)
Python in the browser: Brython's recommendation
Save the binary file in Python
Hit the Sesami API in Python
Python is UnicodeEncodeError in CodeBox docker
In the python command python points to python3.8
Implement the Singleton pattern in Python
Windows10: Install MeCab library in python
Python for statement ~ What is iterable ~
[Python] How to import the library
What is the X Window System?
Hit the web API in Python
There is no switch in python
Determine if the library is installed.