こんにちは。traP Advent Calender 2017、†12月25日†担当の幼卒です。10月31日も担当したので是非見てください。
さて、今は12月24日なのですが、上の記事で最後にこんなことが書いてありますね・・・?
実はAdCを書き始めたのが3日前だったのでクソ記事が出来上がりました。次回はこのようなことがないように2ヶ月ぐらい前から準備したいですね。
大体今日の2ヶ月前ってこの記事を投稿したぐらいなんです。それでこのときはGame A Weekという1週間に1つゲームを作るという勉強法をやってはいたのですが、色々あって2週間ぐらいで頓挫したのでネタがありません。今回は書き始めたのが1日前なので更にひどい。
折角なのでクリスマスに纏わる何かを書こうと思います。
クリスマスといえば・・・?
自分のクリスマスの出来事を思い出してみましょう。まず去年は・・・受験勉強してました。当然ですね。一昨年は・・・?全く思い出せません。3年前は・・・麻雀をしてましたね。今クリスマス=麻雀となったので麻雀について書きたいと思います。
待ち牌探索のプログラム
しかし全素の僕は1日で麻雀ゲームを作ることは出来ません。待ち牌探索ぐらいなら作れそうなので車輪の再開発に挑戦することにしました。面倒くさいので門前の清一色だけとします。競プロ風に書けばこんな感じでしょうか。
問題文
全てが同種の牌であるようなの待ちを求めてください。
入力
入力は以下の形式で標準入力から与えられる。
出力
の待ちを数字が昇順になるように、空白区切りで出力せよ。もしテンパイしていない場合は0を出力せよ。
入力例 1
1112256888999
出力例 1
4 7
入力例 2
1133445566779
出力例 2
9
入力例 3
1112345678999
出力例 3
1 2 3 4 5 6 7 8 9
どうしようか
まずマサカリをおしまいください。
今国士無双の可能性がないので、七対子を除けばテンパイしていないか、次の3つのいずれかに必ず該当します。
- 雀頭があり順子n個と刻子3-n個(n=0,1,2,3)で、残った2牌が両面・嵌張・辺張
 - 雀頭がなく順子n個と刻子3-n個(n=0,1,2,3)で、残った4牌がシャボ
 - 雀頭がなく順子n個と刻子4-n個(n=0,1,2,3,4)で、残った1牌が単騎
 
よってこれらを総当りで調べればいいわけですが、一つ問題が生じます。一番簡単な例は1122334とあるとき、小さい方から順子を考えるだけでは234という順子を見つけることができません。
これを踏まえると次のようなプログラムを組めば良いことがわかります。
- 七対子かどうか確認する。
 - 雀頭になり得る2牌を抜く。このとき抜いた後の形としてなり得る全ての牌姿を格納した配列として返す。
 - 上の配列の全ての要素について、順子になり得る3牌を抜く。このとき抜いた後の形としてなり得る全ての牌姿を格納した配列として返す。同様に刻子に対しても行う。
 - 3を返される牌姿の枚数が2牌になるまで繰り返す。
 - その2牌が両面・嵌張・辺張かどうか確認する。
 
しかしこれではシャボと単騎待ちを認識できないので追加して
- 最初の牌姿について3を返される牌姿の枚数が4牌になるまで繰り返す。
 - その4牌についてシャボであるか確認する。また。順子あるいは刻子になり得る3牌を抜けるなら、それは単騎待ちである。
 
つくりました
その辺にパイソンがいたのでpythonで書きました。
import copy
# 牌姿と牌情報が書かれた配列を受け取り、牌姿からその牌を抜いた牌姿を返す。
def pull(tehai,pai):
  temptehai = copy.deepcopy(tehai)
  for i in pai:
    temptehai.remove(i)
  return temptehai
# 七対子ならその待ちを,そうでないならFalseを返す。
def isChitoitsu(tehai):
  j = 2
  machi = 0
  for i in tehai:
    if not tehai.count(i) == 2:
      machi = i
      j = j - 1
      if j == 0:
        return False
  return i
# 牌姿を受け取り、その中の雀頭候補を除いたときの考えうる全ての牌姿を出力する。
def searchAtama(tehai):
  atamaList = []
  
  for i in range(1,10):
    if tehai.count(i) > 1:
      atamaList.append(pull(tehai,[i,i]))
  
  return atamaList
# 牌姿を受け取り、その中の順子候補1つを除いたときの考えうる全ての牌姿を出力する。
def shuntsu(tehai):
  shuntsuList = []
  for i in range(1,8):
    if (i in tehai) and (i+1 in tehai) and (i+2 in tehai):
      shuntsuList.append(pull(tehai,[i,i+1,i+2]))
  
  return shuntsuList
# 牌姿を受け取り、その中の刻子候補1つを除いたときの考えうる全ての牌姿を出力する。
def kotsu(tehai):
  kotsuList = []
  for i in range(1,10):
    if tehai.count(i) > 2:
      kotsuList.append(pull(tehai,[i,i,i]))
  
  return kotsuList
# 2ないし4枚の牌姿を受け取って両面・嵌張・辺張・シャボを確認する
def machi(tehai):
  machiList = []
  if len(tehai) == 2:
    #両面・辺張か
    for i in range(1,9):
      if i in tehai and i + 1 in tehai:
        machiList.append([i,i+1])
    
    #嵌張か
    for i in range(1,8):
      if i in tehai and i + 2 in tehai:
        machiList.append([i,i+2])
    
  if len(tehai) == 4:
    #単騎か
    if not shuntsu(tehai) == []:
      machiList.append(shuntsu(tehai))
    if not kotsu(tehai) == []:
      machiList.append(kotsu(tehai))
    #シャボか
    if tehai[0] == tehai[2]:
      machiList.append([tehai[0],tehai[0],tehai[2],tehai[2]])
  return machiList
def main(tehai):
  machipai = []
  #雀頭を抜いたものと抜いてないものを考える。
  hand = searchAtama(tehai)+[tehai]
  for h in hand:
    for i in shuntsu(h):
      for j in shuntsu(i):
        for k in shuntsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
        for k in kotsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
      for j in kotsu(i):
        for k in shuntsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
        for k in kotsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
    for i in kotsu(h):
      for j in shuntsu(i):
        for k in shuntsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
        for k in kotsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
      for j in kotsu(i):
        for k in shuntsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
        for k in kotsu(j):
          if not machi(k) == []:
            machipai.append(machi(k))
  po = []
  for i in machipai:
    po = po + i
  return po
      
def unique(array):
  for i in array:
    while True:
      if array.count(i) > 1:
        array.remove(i)
      else:
        break
  return array
if __name__ == '__main__':
  print("手牌をください")
  tehai = input("")
  tehai = list(tehai)
  for i in range(0,13):
    tehai[i] = int(tehai[i])
  a = []
  if not (isChitoitsu(tehai) == False):
    a.append(isChitoitsu(tehai))
  
  machi = main(tehai)
  machi = unique(unique(machi))
  #machiは待ちになっている部分なので、何待ちなのかここで整理する。
  for i in machi:
    if len(i) == 2:
      if isinstance(i[0],list):
        a.append(i[0][0])
        a.append(i[1][0])
      elif i[0] + 1 == i[1]:
        if i[0] == 1:
          a.append(3)
        elif i[0] == 8:
          a.append(7)
        else:
          a.append(i[0]-1)
          a.append(i[0]+2)
      elif i[0] + 2 == i[1]:
        a.append(i[0]+1)
  a = sorted(unique(a))
  a = map(str,a)
  print(' '.join(a))
一応動くと思います。
あとがき
麻雀の待ちを出力するプログラムは難しそうだなぁと思っていましたが、待ちだけなら意外とあっけなかったです。ゲームにするためにはここから点数処理を付け加えなければいけませんがそれはまた別のお話ということで。
ところで調べてみたらワンライナーで書いてるものもありまだまだだなぁと実感しました。
それではメリークリスマス!!!