[PYTHON] Distinguishing the agari shape of mahjong

Distinguishing the agari shape of mahjong

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.

The basics

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

Agari type check

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

Check of Tenwa

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

And the result is

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.

Still 20,000 times ... or __ If you don't think __ Maybe something is paralyzed.

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

bonus

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

If you are interested

Check it out with your own hands!

Recommended Posts

Distinguishing the agari shape of mahjong
The shape of the one-dimensional array of numpy was complicated
Judging the finish of mahjong by combinatorial optimization
The beginning of cif2cell
The meaning of self
the zen of Python
The story of sys.path.append ()
[Note] Contents of shape [0], shape [1], shape [2]
Generate that shape of the bottom of a PET bottle
Understanding the Tensor (2): Shape
Revenge of the Types: Revenge of types
Create a shape on the trajectory of an object
Align the version of chromedriver_binary
Scraping the result of "Schedule-kun"
10. Counting the number of lines
The story of building Zabbix 4.4
Discrimination of mahjong waiting form
Towards the retirement of Python2
[Apache] The story of prefork
Compare the fonts of jupyter-themes
About the ease of Python
Get the number of digits
Explain the code of Tensorflow_in_ROS
Reuse the results of clustering
GoPiGo3 of the old man
Calculate the number of changes
Change the theme of Jupyter
The popularity of programming languages
Change the style of matplotlib
Visualize the orbit of Hayabusa2
About the components of Luigi
Connected components of the graph
Filter the output of tracemalloc
About the features of Python
Simulation of the contents of the wallet
The Power of Pandas: Python