背景
麻雀の待ちって難しくないですか?特にメンチンとか。
自分はメンチン張ったときとか結構考え込んでしまうのですがまあバレバレですよね(泣)
こういったところは小さいながらも麻雀の実力に直結するところなので練習したいと思いつつも後回しになりがち…
そこで最近勉強してるPython(計算機の力)を使って待ち判定できるの?って気になったので実装しながら備忘録的にまとめていきます。
ただ単純にプログラミングの問題として捉えても面白そうだなと思ったので挑戦しました。
この記事はPythonで待ち判定をするプログラムを書きたい人向けであり麻雀を実際に打つ人がメンチンを瞬時に判別できるようになりたい人向けではありません。ご了承ください。
また、ブログを書きながら実装を行ったので話の展開が普通と逆になってます。
なんとなく考えていること、調査
まずこの試み自体はインターネッツでたくさんの先人が挑戦していました。

とまあ割と手垢のついた課題という感じですね。気になる人は見てみてください。
とりあえず私は何もヒントがない状態でスタートして考えたことをまとめますね。
待ち判定=和了判定
当然の話ですが待ち判定をするということは任意の牌で和了れるかを確認することと同義になります。1〜9のそれぞれで和了っているか確認すればそれで待ち判定になります。
じゃあ和了ってるかどうかってどうやって確認するのという話になります。
基本的に和了の形は4面子+1雀頭です。(七対子を除く)
写真のように種類がバラバラの場合はメンツを見分けやすいのですがこれが
こんな感じになっちゃうと何が何だかということです。原因はメンツが刻子でも順子でもいいという点ですね。
以下のように
同じ牌を使っても組み合わせ方で待ち方が複数あるためこれを瞬時に判別するのが難しいというわけですね。上の例では3,4,6待ちとなります。これをさらに3メンツ追加でやるともっともっと難しくなるわけですね。
これをプログラムの計算力でゴリ押しするのか法則を捉えてルールベースで解くのかということを考えるのがこれからの仕事になりそうです。
なんで人間は判定できてるの?
人間が判定できているということは人間が考えてることそのままプログラムにすれば良くない?という疑問が生まれてきます。
これは実際に麻雀をプレイしているときにも気になることですが、人間はどうやってメンチンを判定しているのでしょうか?
これからの情報は以下のサイトを参考にしました。
ネットで調べてみると3つくらいポイントがありそうです。(筆者がメンチンできないため調べないとわかりません)
- 筋を追う
- ー盃口
- 刻子を抜き出す
1つずつ軽く解説します。
筋を追う
これは解説するまでもなく1つでも待ちを確認したらその筋は待ちになってることが多いという考えのもと候補を探すという感じですね。5連形の三面待ちのときのイメージです。
ー盃口なところを抜き出す
ここでいう一盃口的なところとは順子が2つ重なったところのことです。
上の写真では233445だけでなく556677という風にも抜き出すことができます。
刻子を抜き出す
変則待ちを炙り出すための操作ですね。以下のように
雀頭候補になりそうなのがぱっと見66しかありません。66を雀頭として考えたとき、待ちは47待ちとなります。しかし、666を抜き出してしまえば5,8ののべたん待ちが見えます。よってこの待ちは4758になります。
とまあここまでやってることといえば少ない枚数にしてしまえば待ちがわかるからどんどんその候補を調べるために抜き出しちゃえというのが人間のやり方である。
これはコンピューターが一番苦手としている部分で人間がよしなにしている部分はコンピュータにはできないのです。
なのでプログラムを組む方針として2つあって
- 人間のやり方を模倣しながらルールを追加して実装する。
- 計算機の計算能力を生かしてゴリ押す。
一旦は人間のやり方でやりたい思います。
実装編
こういう問題の時は一番簡単な事例で試してみることが多いです。形としてはこんな感じです。
プログラム的には3メンツ完成(考慮しない) + 残りの4枚 + 1〜9のどれかで和了というのを検証します。
def check_4(self,nyuryoku): a = range(1,10) k = str(nyuryoku) tehai = [] hora = [] for i in k: tehai.append(int(i)) if len(set(tehai)) == 1: return [] else: for i in a: kari_tehai = tehai + [i] if len(set(kari_tehai)) != len(kari_tehai): c = collections.Counter(kari_tehai) for key,value in c.items(): if value > 1: kari_tehai.remove(key) kari_tehai.remove(key) if len(set(kari_tehai)) == 1: hora.append(i) if self.renban_check(kari_tehai): hora.append(i) kari_tehai.append(key) kari_tehai.append(key) return set(hora)
こんな感じで実装ができました。コードの汚さと実行時間を考慮していません。
とりあえず4枚での手牌で13枚の手牌から3枚のメンツを抜き出すという操作を3回繰り返すことで4枚に絞るというやり方で待ちを判定していこうと思います。
手順は以下の通りになっています。
それを実装したのが以下のコードになります。
class hantei: def __init__(self): self.hantei_time = 0 def renban_check(self, x): x.sort() if (x[0] + 1 == x[1]) and (x[1] + 1 == x[2]): return True else: return False def check_4(self,nyuryoku): a = range(1,10) k = str(nyuryoku) tehai = [] hora = [] for i in k: tehai.append(int(i)) if len(set(tehai)) == 1: return [] else: for i in a: kari_tehai = tehai + [i] if len(set(kari_tehai)) != len(kari_tehai): c = collections.Counter(kari_tehai) for key,value in c.items(): if value > 1: kari_tehai.remove(key) kari_tehai.remove(key) if len(set(kari_tehai)) == 1: hora.append(i) if self.renban_check(kari_tehai): hora.append(i) kari_tehai.append(key) kari_tehai.append(key) return set(hora) def remove_3_syuntsu(self, x, i): x.remove(i) x.remove(i+1) x.remove(i+2) return x def remove_3_kotsu(self, x, i): x.remove(i) x.remove(i) x.remove(i) return x def list_to_int(self, x): k = "" for j in x: k += str(j) l = int(k) return l def sweap_3(self, x): tehai = [] sweap_list = [] for i in str(x): tehai.append(int(i)) tehai.sort() c = collections.Counter(tehai) syurui = set(tehai) for i in range(1,8): kari_tehai = copy.deepcopy(tehai) if (i in syurui) and (i + 1 in syurui) and (i + 2 in syurui): k = self.remove_3_syuntsu(kari_tehai, i) sweap_list.append(self.list_to_int(k)) for i in range(1,10): kari_tehai = copy.deepcopy(tehai) if c[i] > 2: k = self.remove_3_kotsu(kari_tehai, i) sweap_list.append(self.list_to_int(k)) return sweap_list def hanteikun(self, x): start = time.time() kouho_1 = self.sweap_3(x) hora = set() for k in kouho_1: kouho_2 = self.sweap_3(k) for j in kouho_2: kouho_3 = self.sweap_3(j) for l in kouho_3: hora = hora | self.check_4(l) self.hantei_time = time.time() - start return hora view rawmatihantei.py hosted with ❤ by GitHub
テストケースを作るのがめんどくさくて3種類しか作らなかったのですがとりあえずは判定できてそう。
判定時間が0.0168秒ですが早いのかもよく分からないという。
まとめ
今回の試みではメンチンの待ち判定を行いましたがアルゴリズムには工夫もなく誰もが思いつくような形で実装を行いましたが早くするにはまだまだやり方がありそうです。
もし何か方法を思いつけば追記していこうと思います。
コメント