http://qiita.com/arc279/items/7894d582a882906b94c7 Continuation of this.
It was a lot more annoying than I expected.
After some trial and error, it turned out to be disastrous. That's interesting, so I'll post it while leaving the trial process.
I'm writing quite roughly, so I'd be happy if you could tell me if it's strange.
Here is the reference. Since it is only Chiniisou judgment, I started from here and rewrote it quite a bit with my super interpretation. http://d.hatena.ne.jp/staebchen/20100403/1270256158
I've used up closures and generators If you try to rewrite it with something other than python, it may be difficult, but that's okay.
It's the same as the one before the basics, but I've reworked it as it is, so I'll put it all again.
I personally don't like putting code outside # gist.
mj2.py
#!/usr/bin/env python
# -*- coding: utf8 -*-
import itertools
import random
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):
u'''King'''
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 Tsuhai:
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 all(cls):
u'''All tiles'''
return [cls(suit, num)
for suit in cls.Suit.NAMES
for num in range(1, cls.NUM_OF_EACH_NUMBER_PAIS+1)
if suit != cls.Suit.J
] + [ cls(cls.Suit.J, num) for num in cls.Tsuhai.NAMES.keys() ]
@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.Tsuhai.E),
cls(cls.Suit.J, cls.Tsuhai.S),
cls(cls.Suit.J, cls.Tsuhai.W),
cls(cls.Suit.J, cls.Tsuhai.N),
cls(cls.Suit.J, cls.Tsuhai.HAK),
cls(cls.Suit.J, cls.Tsuhai.HAT),
cls(cls.Suit.J, cls.Tsuhai.CHU),
]
@classmethod
def chuchanpai(cls):
u'''Nakahari tile'''
yaochupai = cls.yaochupai()
return [ x for x in cls.all() if x not in yaochupai ]
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 - 1
def is_next(self, other, index=1):
u'''Whether it is the next number tile'''
if self.suit != self.Suit.J: #Not a tile
if self.suit == other.suit: #The tiles are the same
if other.num == (self.num + index): #Serial number
return True
return False
def is_prev(self, other, index=1):
u'''Whether it is the previous number tile'''
return self.is_next(other, -index)
@classmethod
def is_syuntsu(cls, first, second, third):
u'''Whether it is Junko'''
#return second.is_prev(first) and second.is_next(third)
return first.is_next(second) and first.is_next(third, 2)
def __repr__(self):
#return str((self.suit, self.num)) #Tuple display
if self.suit == self.Suit.J:
return self.Tsuhai.NAMES[self.num].encode('utf-8')
else:
return (self.Num.NAMES[self.num] + self.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):
u'''Get from 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)
@classmethod
def from_name(cls, name):
u'''Get from name'''
for x in cls.all():
if name == repr(x):
return x
return None
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
@classmethod
def aggregate(cls, tehai):
u'''{Tile seed:Number of sheets}Aggregate in the form of'''
hash = { x[0]: len(list(x[1])) for x in itertools.groupby(tehai.rihai()) }
#Return the key (sorted tiles) with it
return hash, sorted(hash.keys(), cmp=cls.sorter)
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.Tsuhai.NAMES[pai.num] + u"|"
line2 += u" |"
print line1.encode("utf-8")
print line2.encode("utf-8")
@classmethod
def search_syuntsu(cls, pais, keys):
u'''Find Junko
The argument is aggregate()Pass in the same form as the return value of.'''
for i in range( len(keys)-2 ): #No need to check the last 2 sheets
tmp = pais.copy()
first = keys[i]
if tmp[first] >= 1:
try:
second = keys[i+1]
third = keys[i+2]
except IndexError as e:
#There are no remaining 2 types
continue
if not Pai.is_syuntsu(first, second, third):
continue
if tmp[second] >= 1 and tmp[third] >= 1:
tmp[first] -= 1
tmp[second] -= 1
tmp[third] -= 1
#Junko found,The remaining tiles
yield (first, second, third), tmp
@classmethod
def search_kohtu(cls, pais, keys):
u'''Find the engraving
The argument is aggregate()Pass in the same form as the return value of.'''
for i, p in enumerate(keys):
tmp = pais.copy()
if tmp[p] >= 3:
tmp[p] -= 3
#Found engraving,The remaining tiles
yield (p, p, p), tmp
It was a terrible thing to write in good condition. But no. It's annoying to fix it anymore.
It might be a little suspicious, such as waiting for 9 faces. I can't check it properly because there are too many types of waiting ...
It's just a waiting form and no role judgment is included.
Also, I'm exhausted, so I can't wait for Chiitoitsu and Kokushi Musou. Well, I think it can be done with a little care to distinguish the Agari type.
mj2.py
def check_tenpai(tehai):
u'''Check the shape of the tenpai'''
# TODO:Waiting for Chiitoitsu and Kokushi Musou not checked
assert(len(tehai) == 13)
# (Atama,face,Wait)Form of
candidate = set()
def check_machi(mentsu, tartsu):
u'''Examine the shape of the wait'''
assert(len(mentsu) == 3)
keys = sorted(tartsu.keys(), cmp=Tehai.sorter)
#print mentsu, tartsu, keys
def check_tanki():
u'''Single horse waiting check'''
for i, p in enumerate(keys):
tmp = tartsu.copy()
if tmp[p] == 3:
#The remaining face is engraved
assert(len(tmp) == 2)
tmp[p] -= 3
tanki = { pai: num for pai, num in tmp.items() if num > 0 }.keys()
#Plunge into the face
ins = tuple( sorted(mentsu + [(p, p, p)]) )
candidate.add( ((), ins, tuple(tanki)) )
else:
#The remaining face is Junko
first = p
try:
second = keys[i+1]
third = keys[i+2]
except IndexError as e:
continue
if not Pai.is_syuntsu(first, second, third):
continue
tmp[first] -= 1
tmp[second] -= 1
tmp[third] -= 1
tanki = { pai: num for pai, num in tmp.items() if num > 0 }.keys()
#Plunge into the face
ins = tuple( sorted(mentsu + [(first, second, third)]) )
candidate.add( ((), ins, tuple(tanki)) )
def check_non_tanki():
u'''Waiting check other than single horse'''
for i, p in enumerate(keys):
tmp = tartsu.copy()
#Sparrow head check
if not tmp[p] >= 2:
continue
tmp[p] -= 2
atama = (p, p)
for j, q in enumerate(keys):
#Double-sided, edge
try:
next = keys[j+1]
if q.is_next(next):
ins = tuple( sorted(mentsu) )
candidate.add( (atama, ins, (q, next) ) )
break
except IndexError as e:
pass
#Fitting
try:
next = keys[j+1]
if q.is_next(next, 2):
ins = tuple( sorted(mentsu) )
candidate.add( (atama, ins, (q, next) ) )
break
except IndexError as e:
pass
#Sogo
if tmp[q] >= 2:
ins = tuple( sorted(mentsu) )
candidate.add( (atama, ins, (q, q) ) )
break
check_tanki()
check_non_tanki()
#Search for 3 faces
pais, keys = Tehai.aggregate(tehai)
#print pais, keys
if True:
#I wonder if it's done recursively
def search_mentsu(depth, proc):
searchers = [Tehai.search_syuntsu, Tehai.search_kohtu]
#Junko/Search for engraving
def inner(pais, mentsu = [], nest = 0):
if nest < depth:
for search in searchers:
for m, a in search(pais, keys):
inner(a, mentsu + [m], nest+1)
else:
proc(mentsu, pais)
inner(pais)
search_mentsu(3, lambda mentsu, pais:
check_machi(mentsu, { x[0]:x[1] for x in pais.items() if x[1] > 0 })
)
else:
#If you write solidly like this
searchers = [Tehai.search_syuntsu, Tehai.search_kohtu]
for p1 in searchers:
for p2 in searchers:
for p3 in searchers:
#Application
for m1, a1 in p1(pais, keys):
for m2, a2 in p2(a1, keys):
for m3, a3 in p3(a2, keys):
mentsu = [m1, m2, m3]
#The remaining tiles
tartsu = { x[0]:x[1] for x in a3.items() if x[1] > 0 }
check_machi(mentsu, tartsu)
return candidate
That for debugging.
mj2.py
class Debug:
u'''for debug'''
TEST_HOHRA = [
[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
]
TEST_TENPAI = [
[0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8], #Genuine Churenpoto
[1, 2, 3, 4, 5, 5, 5, 6, 20, 20, 21, 22, 23], #Kan-chan Shabo(Junko is entwined)
[13, 14, 15, 18, 19, 19, 20, 21, 24, 24, 24, 31, 31], #Ryanmen Kan-chan
[25, 25, 25, 1, 2, 3, 11, 12, 13, 11, 23, 23, 23], #Ryanmen Tanki
[25, 25, 25, 1, 2, 3, 11, 12, 13, 11, 12, 23, 24], #Ryanmen
[1, 2, 3, 4, 4, 6, 7, 8, 9, 10, 11, 29, 29], #Shabo
]
@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_hohra(cls, idx = None):
u'''Achievement test'''
return cls.tehai_from_indexes(cls.TEST_HOHRA[idx])
@classmethod
def test_tenpai(cls, idx = 0):
u'''Tenpai test'''
return cls.tehai_from_indexes(cls.TEST_TENPAI[idx])
@classmethod
def gen_tehai(cls, num = 14):
u'''Make an appropriate arrangement'''
assert(num == 13 or num == 14)
yama = Yama()
return Tehai([ yama.tsumo() for x in range(num) ])
@classmethod
def gen_hohra(cls):
u'''Make an appropriate agari shape'''
tehai = Tehai()
def gen_syuntsu():
u'''Make Junko'''
first = Pai.from_index(random.choice(range(Pai.TOTAL_KIND_NUM)))
if first.suit == Pai.Suit.J:
#Character tiles cannot be Junko
return None
if first.num > 7:
# (7 8 9)The above cannot be Junko
return None
second = Pai(first.suit, first.num+1)
third = Pai(first.suit, first.num+2)
if tehai.count(first) == 4 or tehai.count(second) == 4 or tehai.count(third) == 4:
#Insufficient remaining number
return None
return [first, second, third]
def gen_kohtu():
u'''Make engraving'''
pai = Pai.from_index(random.choice(range(Pai.TOTAL_KIND_NUM)))
if tehai.count(pai) >= 2:
#Insufficient remaining number
return None
return [pai, pai, pai]
def gen_atama():
u'''Make a sparrow head'''
pai = Pai.from_index(random.choice(range(Pai.TOTAL_KIND_NUM)))
if tehai.count(pai) >= 3:
#Insufficient remaining number
return None
return [pai, pai]
tehai.extend(gen_atama()) #Sparrow head
#If Junko and Kokuko have the same probability of appearance, we will weight them.
weighted_choices = [(gen_syuntsu, 3), (gen_kohtu, 1)]
population = [val for val, cnt in weighted_choices for i in range(cnt)]
while len(tehai) < 14:
ret = random.choice(population)()
if ret is not None:
tehai.extend(ret)
return tehai
@classmethod
def gen_tenpai(cls):
u'''Make a tenpai shape appropriately'''
tehai = cls.gen_hohra()
assert(len(tehai) == 14)
#Pull out one piece from the Agari shape
tehai.pop(random.randrange(len(tehai)))
return tehai
class Test:
u'''for test'''
@classmethod
def check_tenho(cls):
u'''Tenwa check'''
import sys
for cnt in (x for x in itertools.count()):
print >>sys.stderr, cnt
yama = Yama()
oya, _, _, _ = yama.haipai()
ret = check_hohra(oya)
if ret:
print "---------------------------------------------"
print cnt
oya.show()
for atama, mentsu in ret:
print atama, mentsu
break
@classmethod
def check_machi(cls, times = 100):
u'''Check a lot of waiting'''
for x in range(times):
tehai = Debug.gen_tenpai()
ret = check_tenpai(tehai.rihai())
if not ret:
#When I come here, I'm not tempered. The point is a malfunction. Tehai to be modified.
print oya
print [ Pai.from_name(repr(x)).index for x in oya ]
print "complete."
if __name__ == '__main__':
Test.check_machi()
For the time being, even if you check a lot of tenpai shapes, you probably won't miss them ... The question is whether I can list all the waits properly ...
I think there is room for improvement because I'm doing a lot of wasteful things.
Please let me know if there are any bugs.
If you check the waiting of genuine Kuren Baotou, it looks like this.
The first line is Tehai.
2nd and subsequent lines
Waiting for the sparrow head face
In the form of.
[Ichiman, Ichiman, Ichiman,Niman,Sanman,Shima,Goman,Rokuman,Nanaman,Hachiman,Kuman,Kuman,Kuman]
() ((Ichiman, Ichiman, Ichiman), (Sanman,Shima,Goman), (Rokuman,Nanaman,Hachiman), (Kuman, Kuman, Kuman)) (Niman,)
() ((Ichiman, Ichiman, Ichiman), (Niman,Sanman,Shima), (Goman,Rokuman,Nanaman), (Kuman, Kuman, Kuman)) (Hachiman,)
(Kuman, Kuman) ((Ichiman,Niman,Sanman), (Shima,Goman,Rokuman), (Nanaman,Hachiman, Kuman)) (Ichiman, Ichiman)
(Ichiman, Ichiman) ((Ichiman,Niman,Sanman), (Rokuman,Nanaman,Hachiman), (Kuman, Kuman, Kuman)) (Shima,Goman)
(Kuman, Kuman) ((Ichiman, Ichiman, Ichiman), (Niman,Sanman,Shima), (Goman,Rokuman,Nanaman)) (Hachiman, Kuman)
(Kuman, Kuman) ((Ichiman, Ichiman, Ichiman), (Shima,Goman,Rokuman), (Nanaman,Hachiman, Kuman)) (Niman,Sanman)
(Kuman, Kuman) ((Ichiman, Ichiman, Ichiman), (Niman,Sanman,Shima), (Nanaman,Hachiman, Kuman)) (Goman,Rokuman)
(Ichiman, Ichiman) ((Sanman,Shima,Goman), (Rokuman,Nanaman,Hachiman), (Kuman, Kuman, Kuman)) (Ichiman,Niman)
(Ichiman, Ichiman) ((Ichiman,Niman,Sanman), (Shima,Goman,Rokuman), (Nanaman,Hachiman,Kuman)) (Kuman,Kuman)
() ((Ichiman, Ichiman, Ichiman), (Niman,Sanman,Shima), (Rokuman,Nanaman,Hachiman), (Kuman, Kuman, Kuman)) (Goman,)
(Ichiman, Ichiman) ((Ichiman,Niman,Sanman), (Shima,Goman,Rokuman), (Kuman, Kuman, Kuman)) (Nanaman,Hachiman)
Does this really suit you?