Since the probability of Tenwa seems to be once in 330,000 times, I wanted to try it, so I wrote it in python, This was more annoying than I expected.
The algorithm is here. http://www.onionsoft.net/hsp/mahjong.txt
The role or waiting is okay ... If I'm fine ~~ I'll write it next time ~~.
I wrote it @ [2014/9/25] http://qiita.com/arc279/items/1a7853ad8e2dc35961d1
Confirmed operation with python2.7.6.
I wrote it honestly. I have inherited list of builtin because it is troublesome, but I think that it is better to delegate it.
Wash the tiles to random.
mj.py
#!/usr/bin/env python
# -*- coding: utf8 -*-
import itertools
import random
from collections import OrderedDict
class Yama(list):
u'''Wall'''
WANPAI_NUM = 14
class TsumoDoesNotRemain(Exception):
u'''Only the king is left'''
pass
def __init__(self):
pais = [ Pai.from_index(i)
for i in range(Pai.TOTAL_KIND_NUM * Pai.NUM_OF_EACH_KIND) ]
#Washing tiles
random.shuffle(pais)
super(Yama, self).__init__(pais)
def tsumo(self):
u'''Self-reliance'''
if len(self) <= self.WANPAI_NUM:
raise self.TsumoDoesNotRemain
return self.pop(0)
def wanpai(self):
return self[-self.WANPAI_NUM:]
def haipai(self):
u'''Distribution'''
tehais = [ Tehai(), Tehai(), Tehai(), Tehai() ] #east(parent)South West North
# 4*3 rounds
for j in range(0, 3):
for tehai in tehais:
for i in range(0, 4):
pai = self.tsumo()
tehai.append(pai)
#Choncho
for tehai in tehais:
pai = self.tsumo()
tehai.append(pai)
pai = self.tsumo()
tehais[0].append(pai)
return tehais
class Pai(object):
u'''Tile'''
TOTAL_KIND_NUM = 34 # M/P/S +All types of tiles
NUM_OF_EACH_KIND = 4 #4 sheets per type
NUM_OF_EACH_NUMBER_PAIS = 9 # M/P/The number tile of S is 1..Up to 9
class Suit:
M = 0 #Man
P = 1 #Tube
S = 2 #Measure
J = 3 #Character
NAMES = {
M: u"Man",
P: u"Tube",
S: u"Measure",
J: u" ",
}
class Num:
NAMES = {
1: u"one",
2: u"two",
3: u"three",
4: u"four",
5: u"Five",
6: u"Six",
7: u"Seven",
8: u"Eight",
9: u"Nine",
}
class Jihai:
E = 1
S = 2
W = 3
N = 4
HAK = 5
HAT = 6
CHU = 7
NAMES = {
E: u"east",
S: u"South",
W: u"West",
N: u"North",
HAK: u"White",
HAT: u"Repellent",
CHU: u"During ~",
}
@classmethod
def yaochupai(cls):
u'''Tanyao chuu'''
return [
cls(cls.Suit.M, 1),
cls(cls.Suit.M, 9),
cls(cls.Suit.P, 1),
cls(cls.Suit.P, 9),
cls(cls.Suit.S, 1),
cls(cls.Suit.S, 9),
cls(cls.Suit.J, cls.Jihai.E),
cls(cls.Suit.J, cls.Jihai.S),
cls(cls.Suit.J, cls.Jihai.W),
cls(cls.Suit.J, cls.Jihai.N),
cls(cls.Suit.J, cls.Jihai.HAK),
cls(cls.Suit.J, cls.Jihai.HAT),
cls(cls.Suit.J, cls.Jihai.CHU),
]
def __init__(self, suit, num):
self.suit = suit
self.num = num
@property
def index(self):
return self.suit * self.NUM_OF_EACH_NUMBER_PAIS + self.num
def __repr__(self):
#return str((self.suit, self.num)) #Tuple display
if self.suit == Pai.Suit.J:
return Pai.Jihai.NAMES[self.num].encode('utf-8')
else:
return (Pai.Num.NAMES[self.num] + Pai.Suit.NAMES[self.suit]).encode('utf-8')
def __eq__(self, other):
return self.suit == other.suit and self.num == other.num
@classmethod
def from_index(cls, index):
kind = index % cls.TOTAL_KIND_NUM
if True:
suit = kind / cls.NUM_OF_EACH_NUMBER_PAIS
num = kind % cls.NUM_OF_EACH_NUMBER_PAIS + 1
else:
if 0 <= kind < 9:
suit = cls.Suit.M
num = kind - 0 + 1
elif 9 <= kind < 18:
suit = cls.Suit.P
num = kind - 9 + 1
elif 18 <= kind < 27:
suit = cls.Suit.S
num = kind - 18 + 1
elif 27 <= kind < 34:
suit = cls.Suit.J
num = kind - 27 + 1
assert(cls.Suit.M <= suit <= cls.Suit.J)
assert(1 <= num <= cls.NUM_OF_EACH_NUMBER_PAIS)
return cls(suit, num)
class Tehai(list):
u'''Tehai'''
@staticmethod
def sorter(a, b):
u'''How to tile'''
return a.suit - b.suit if a.suit != b.suit else a.num - b.num
def rihai(self):
u'''Tile'''
self.sort(cmp=self.sorter)
return self
def aggregate(self):
u'''{Tile seed:Number of sheets}Aggregate in the form of'''
hash = { x[0]: len(list(x[1])) for x in itertools.groupby(self.rihai()) }
ret = OrderedDict()
#Key tiles now remain sorted
for x in sorted(hash.keys(), cmp=self.sorter):
ret[x] = hash[x]
return ret
def show(self):
u'''Display in an easy-to-read form'''
line1 = u"|"
line2 = u"|"
for pai in self.rihai():
if pai.suit != Pai.Suit.J:
line1 += Pai.Num.NAMES[pai.num] + u"|"
line2 += Pai.Suit.NAMES[pai.suit] + u"|"
else:
line1 += Pai.Jihai.NAMES[pai.num] + u"|"
line2 += u" |"
print line1.encode("utf-8")
print line2.encode("utf-8")
Do Chiitoitsu and Kokushi Musou have sparrow heads? This area depends on the interpretation, so it's good. Here, Chiitoitsu has no sparrow head, and Kokushi Musou has two sparrow heads.
It was troublesome to cut out the judgment part to the class, so I used a closure.
mj.py
def check_hohra(tehai):
u'''Check the shape of the agari'''
assert(len(tehai) == 14)
pais = tehai.aggregate()
keys = pais.keys()
length = len(keys)
#print pais, keys, length
def check_chitoitsu(pais):
u'''Chiitoitsu check'''
if all([ num == 2 for pai, num in pais.items()]):
return (), [ (pai, pai) for pai, num in pais.items() ]
return None
def check_kokushimusou(pais):
u'''Kokushi Musou Check'''
if length != 13:
return None
yaochupai = Pai.yaochupai()
mentsu = []
for pai, num in pais.items():
if pai not in yaochupai:
return None
# TODO:Is it okay to have two sparrow heads here?
if num == 2:
atama = (pai, pai)
else:
assert(num == 1)
mentsu.append(pai)
return atama, mentsu
def search_syuntu(pais):
u'''Find Junko'''
for i in range(length):
if pais[keys[i]] >= 1:
first = keys[i]
try:
second = keys[i+1]
third = keys[i+2]
except IndexError as e:
#There are no remaining 2 types
continue
if first.suit == Pai.Suit.J:
#Character tiles cannot be Junko
continue
if not (first.suit == second.suit and first.suit == third.suit):
#Different tiles
continue
if not ((second.num == first.num+1) and (third.num == first.num+2)):
#Not a serial number
continue
if pais[second] >= 1 and pais[third] >= 1:
pais[first] -= 1
pais[second] -= 1
pais[third] -= 1
return (first, second, third)
return None
def search_kohtu(pais):
u'''Find the engraving'''
for j in range(length):
if pais[keys[j]] >= 3:
pais[keys[j]] -= 3
return (keys[j], keys[j], keys[j])
return None
#Chiitoitsu
tmp = pais.copy()
ret = check_chitoitsu(tmp)
if ret:
return [ ret ]
#Kokushi Musou
tmp = pais.copy()
ret = check_kokushimusou(tmp)
if ret:
return [ ret ]
#Uninflected word
candidate = []
for i in range(length):
#Find the head
if not (pais[keys[i]] >= 2):
continue
tmp = pais.copy()
atama = (keys[i], keys[i])
tmp[keys[i]] -= 2
mentsu = []
while True:
#print tmp
ret = search_syuntu(tmp) or search_kohtu(tmp)
if ret is None:
ret = search_kohtu(tmp) or search_syuntu(tmp)
if ret is None:
#I can't do Junko or Kiko
break
mentsu.append(ret)
#print atama, mentsu, tmp
if len(mentsu) == 4:
#4 facets 1 sparrow head shape
candidate.append( (atama, mentsu) )
return candidate
It would be nice if it was shaped like an agari by the parent's arrangement, so it would be like this.
mj.py
def check_tenho():
for cnt in (x for x in itertools.count()):
yama = Yama()
oya, _, _, _ = yama.haipai()
ret = check_hohra(oya)
if ret:
print cnt
oya.show()
for atama, mentsu in ret:
print atama, mentsu
break
if __name__ == '__main__':
#Try about 100 times
for x in range(100):
check_tenho()
If it doesn't come out even after trying 600,000 times, it doesn't come out, and if it comes out, it comes out in about 20,000 times.
Well, as the number of trials increases, I think that the law of large numbers will settle down to 330,000. maybe. It takes a long time and it's annoying, so I've only tried it about 10 times. I was satisfied when I wrote it.
If you want to distinguish between roles and waiting, you probably have to rewrite it ...
That for debugging.
mj.py
class Debug:
u'''for debug'''
TEST_TEHAIS = [
[2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7],
[0, 0, 8, 8, 13, 13, 20, 20, 25, 25, 29, 29, 31, 31], #When
[0, 8, 9, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33, 9], #Kokushi Musou
[33, 33, 33, 32, 32, 32, 31, 31, 31, 0, 0, 0, 2, 2], #Daisangen
[0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 1], #Churenpoto
[19, 19, 20, 20, 21, 21, 23, 23, 23, 25, 25, 32, 32, 32], #Ryu-so
[0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 5, 0, 1, 2], #Chinitsu Ittsu Epaco
]
@classmethod
def tehai_from_indexes(cls, indexes):
assert(len(indexes) == 13 or len(indexes) == 14)
return Tehai([ Pai.from_index(x) for x in indexes ])
@classmethod
def test_tehai(cls, idx = None):
if not idx:
#14 tiles
yama = Yama()
return Tehai([ yama.tsumo() for x in range(14) ])
else:
return cls.tehai_from_indexes(cls.TEST_TEHAIS[idx])
Check it out with your own hands!
Recommended Posts