(未完成)やってみた。麻雀の上がりチェックプログラム

洗濯の合間1時間ほどでできたところまでです。
問題の要件的には未完成で、重複がでる状況があり、コードの最後に書いてあります。

基本のアルゴリズムは簡単です(ただし七対子チェックは入ってません)。

  • 全体的には、各通りツモってみて、その14牌で上がりパターンかチェックして、上がってれば出力する
  • 上がりチェックは、まず頭の対子をすべて列挙し、各対子に対しそれを除いた12牌で4面子になるか検査する

コードはPythonで、generatorを多用しています。
一番時間かかったのは用語選びでしょうか。呼び方がわからないものもありました:

  • 手配+ツモ牌 のことをなんて呼ぶのだろうか
  • 面子と対子(や単騎、カンチャン、両面なども)といった組になるものをなんと呼ぶのだろうか

識別子は漢字で書けばよかったかな(とはいえgoogle入力も麻雀用語には強くないようで、読みからは変換できないので大変ですが)。

以下コード:

# -*- coding: utf-8 -*-

class Hai(object):
    def __init__(self, num, tsumo=False):
        self.num = num
        self.tsumo = tsumo
        pass
    def __eq__(self, other):
        return self.num == other.num and self.tsumo == other.tsumo
    pass

def main():
    tehai_str = raw_input("haipai> ").strip()
    tehai = parse(tehai_str)
    tsumo_list = [Hai(i, True) for i in range(1, 10)]
    # TBD: validate: count of same num <= 4
    for tsumo in tsumo_list:
        tsumotehai = rihai(tehai + [tsumo])
        for toitsu, rest in iter_toitsu(tsumotehai):
            for sumentsu in iter_allmentsu(rest):
                print(format_tenpai(toitsu, sumentsu))
                pass
            pass
        pass
    pass

def parse(tehai):
    if len(tehai) != 13: raise Exception("tehai should be 13 hai")
    return [Hai(int(tehai[i])) for i in range(len(tehai))]

def rihai(tsumo_and_tehai):
    return sorted(tsumo_and_tehai, lambda a, b: cmp(a.num, b.num))

def iter_toitsu(ritsumo):
    same = []
    for i in range(len(ritsumo) - 1):
        if ritsumo[i].num == ritsumo[i+1].num:
            toitsu = ritsumo[i:i+2]
            rest = ritsumo[:i] + ritsumo[i+2:]
            if toitsu in same: continue
            same.append(toitsu)
            yield (toitsu, rest)
            pass
        pass
    pass

def is_juntsu(mentsu):
    return (mentsu[0].num + 1 == mentsu[1].num and 
            mentsu[0].num + 2 == mentsu[2].num)
def is_kotsu(mentsu):
    return mentsu[0].num == mentsu[1].num == mentsu[2].num
def is_mentsu(mentsu):
    return is_juntsu(mentsu) or is_kotsu(mentsu)

def pick1(hailist):
    for i in range(len(hailist)):
        yield (hailist[i], hailist[:i] + hailist[i+1:])
        pass
    pass

def iter_allmentsu(rest):
    if len(rest) == 0:
        yield []
        return
    a = rest[0]
    r1 = rest[1:]
    same = []
    for b, r2 in pick1(r1):
        for c, r3 in pick1(r2):
            mentsu = [a, b, c]
            if mentsu in same: continue
            same.append(mentsu)
            if not is_mentsu(mentsu): continue
            for allmentsu in iter_allmentsu(r3):
                yield [mentsu] + allmentsu
                pass
            pass
        pass
    pass

def format_tenpai(toitsu, sumentsu):
    return (format_group(toitsu) + 
            "".join(format_group(mentsu) for mentsu in sumentsu))

def format_group(group):
    if any(hai.tsumo for hai in group):
        return "[%s]" % "".join(str(hai.num) for hai in group if not hai.tsumo)
    else:
        return "(%s)" % "".join(str(hai.num) for hai in group)
    pass

if __name__ == "__main__": main()

# TODO: unify tamen machi: 1113335557778 makes same two pattern
# TODO: unify juntsu/kotsu order: 1111222233339 makes same two pattern but different order