麻雀の「待ち」を出力する その2

前回の続き。前回は他人のコードを一切参考にせずに書いて、試行錯誤してようやく出来上がった。見直してみると無駄が多い。

  • ループ回数は節約したと言っても、常に2100回も繰り返されてしまう。どんな単純な「待ち」であっても。
  • 問題は、手牌の状態に関係なく、1から9までの対子・順子・刻子をチェックしているところ。本来は手牌に無ければ、チェックの必要がないのに。
  • 自分がテンパイを認識する時は、頭・刻子・順子を、手牌の先頭から順に当てはめて、可能な限りの組み合わせを探していると思う。
  • 手牌に存在しない組み合わせは、当たり前だが全くチェックしていない。
  • 自分の思考と同じ仕組みでチェックすれば良いのだが、それをコードとしてどうやって実現すれば良いのか、結構悩ましい。
  • すでに多数の、麻雀の「待ち」を出力するプログラム、が発表されているので、そこからヒントを探すことにした。

再帰を利用して書いてみた。

      • 以下コード中の半角¥は、半角\に置き換える必要あり。
class Tehai
  def initialize(input)
    @in_text = input
    @outs = []
    @count = 0
  end
  
  # 対子を取り出す
  def toitsu(n, s, rest, pick)
    new_rest = rest.sub(/#{s}#{s}/, '')
    chitoitsu(n, new_rest, pick + ["(#{$&})"]) if $&
  end
  
  # 頭を取り出す
  def atama(n, s, rest, pick, head)
    new_rest = rest.sub(/#{s}#{s}/, '')
    generals(n, new_rest, pick + ["(#{$&})"], true) if $&
  end
  
  # 刻子を取り出す
  def koutsu(n, s, rest, pick, head)
    new_rest = rest.sub(/#{s}#{s}#{s}/, '')
    generals(n, new_rest, pick + ["(#{$&})"], head) if $&
  end
  
  # 順子を取り出す
  def juntsu(n, s, rest, pick, head)
    new_rest = rest.sub(/#{s}#{s+1}(#{s+1}*)#{s+2}/,'\1')
    generals(n, new_rest, pick + ["(#{s}#{s+1}#{s+2})"], head) if $&
  end
  
  # テンパイしているかどうか?
  def tenpai?(rest)
    case rest.size
    when 1
      true
    when 2
      d = rest[1] - rest[0]
      true if d <= 2
    end
  end
  
  # 待ちを出力する
  def machi
    generals(0, @in_text, [], false)
    chitoitsu(0, @in_text, [])
    p @in_text
    puts @outs.uniq, "ループ回数: " + @count.to_s, ''
  end
  
  # 一般的なテンパイを検索
  def generals(n, rest, pick, head)
    @count += 1
    if tenpai?(rest)
      @outs << (pick.sort << "[#{rest}]").to_s
    else
      s = rest[n, 1].to_i
      atama(n, s, rest, pick, head) unless head
      koutsu(n, s, rest, pick, head)
      juntsu(n, s, rest, pick, head)
      while rest[n, 1] == rest[n+1, 1]; n += 1; end
      generals(n+1, rest, pick, head) if n < 2
    end
  end
  
  # 七対子のテンパイを検索
  def chitoitsu(n, rest, pick)
    @count += 1
    if tenpai?(rest)
      @outs << (pick.sort << "[#{rest}]").to_s
    else
      s = rest[n, 1]
      toitsu(n, s, rest, pick)
      while rest[n, 1] == rest[n+1, 1]; n += 1; end
      chitoitsu(n+1, rest, pick) if n < 1
    end
  end
end

# テスト
Tehai.new('1112224588899').machi
Tehai.new('1122335556799').machi
Tehai.new('1112223335559').machi
Tehai.new('1223344888999').machi
Tehai.new('1112345678999').machi # 九蓮宝燈
Tehai.new('1112223334699').machi # 嵌張待ち([46])
Tehai.new('1122334455667').machi # 七対子
Tehai.new('1112223334445').machi # 多面待ち
Tehai.new('1112223334567').machi # ノベタン

実行してみる

$ ruby ~/Desktop/marjon.rb
"1112224588899"
(111)(222)(888)(99)[45]
ループ回数: 19

"1122335556799"
(123)(123)(55)(567)[99]
(123)(123)(555)(99)[67]
(123)(123)(567)(99)[55]
ループ回数: 27

"1112223335559"
(111)(222)(333)(555)[9]
(123)(123)(123)(555)[9]
ループ回数: 42

"1223344888999"
(123)(234)(888)(999)[4]
(123)(44)(888)(999)[23]
(234)(234)(888)(999)[1]
ループ回数: 34

"1112345678999"
(11)(123)(456)(789)[99]
(11)(123)(456)(999)[78]
(11)(123)(678)(999)[45]
(11)(345)(678)(999)[12]
(111)(234)(567)(99)[89]
(111)(234)(567)(999)[8]
(111)(234)(678)(999)[5]
(111)(234)(789)(99)[56]
(111)(345)(678)(999)[2]
(111)(456)(789)(99)[23]
(123)(456)(789)(99)[11]
ループ回数: 75

"1112223334699"
(111)(222)(333)(99)[46]
(123)(123)(123)(99)[46]
ループ回数: 55

"1122334455667"
(11)(234)(234)(567)[56]
(11)(234)(456)(567)[23]
(123)(123)(44)(567)[56]
(123)(123)(456)(456)[7]
(123)(123)(456)(567)[4]
(123)(234)(456)(567)[1]
(11)(22)(33)(44)(55)(66)[7]
ループ回数: 56

"1112223334445"
(11)(123)(234)(234)[45]
(11)(123)(234)(345)[24]
(11)(234)(234)(345)[12]
(111)(22)(234)(345)[34]
(111)(222)(33)(345)[44]
(111)(222)(33)(444)[35]
(111)(222)(333)(44)[45]
(111)(222)(333)(444)[5]
(111)(222)(345)(44)[33]
(111)(234)(234)(234)[5]
(111)(234)(234)(345)[2]
(123)(123)(123)(44)[45]
(123)(123)(123)(444)[5]
(123)(123)(345)(44)[12]
ループ回数: 85

"1112223334567"
(11)(123)(234)(567)[23]
(111)(22)(234)(567)[33]
(111)(22)(333)(567)[24]
(111)(222)(33)(345)[67]
(111)(222)(33)(567)[34]
(111)(222)(333)(456)[7]
(111)(222)(333)(567)[4]
(111)(234)(33)(567)[22]
(123)(123)(123)(456)[7]
(123)(123)(123)(567)[4]
(123)(123)(234)(567)[1]
ループ回数: 81
  • ループ回数は劇的に減った!
  • でも、七対子を検索するために、似たような処理を繰り返しているのは無駄なところ。
  • 頭・対子・順子・刻子を照合を、メソッドに分ける必要もなさそうな気がしてきた...。
  • メソッドに渡す引数ばかり増えて、かえって分かり難い気がする。

以上の対策を考えて、修正してみた。

      • 以下コード中の半角¥は、半角\に置き換える必要あり。
class Tehai
  def initialize(input)
    @in_text = input
    @outs = []
    @count = 0
  end
  
  # テンパイしているかどうか?
  def tenpai?(rest, head)
    case rest.size
    when 1
      true if head == 0 || head == 6
    when 2
      d = rest[1] - rest[0]
      true if head == 1 && d <= 2
    end
  end
  
  # 待ちを出力する
  def machi
    generals(0, @in_text, [], 0)
    p @in_text
    puts @outs.uniq, "ループ回数: " + @count.to_s, ''
  end
  
  # 一般的なテンパイを検索
  def generals(n, rest, pick, head)
    @count += 1
    if tenpai?(rest, head)
      @outs << (pick.sort << "[#{rest}]").to_s
    else
      s = rest[n, 1].to_i
      new_rest = rest.sub(/#{s}#{s}/, '')                   # 対子
      generals(n, new_rest, pick + ["(#{$&})"], head+1) if $&
      new_rest = rest.sub(/#{s}#{s}#{s}/, '')               # 刻子
      generals(n, new_rest, pick + ["(#{$&})"], head)   if $&
      new_rest = rest.sub(/#{s}#{s+1}(#{s+1}*)#{s+2}/,'\1') # 順子
      generals(n, new_rest, pick + ["(#{s}#{s+1}#{s+2})"], head) if $&
      while !rest[n, 1].nil? && rest[n, 1] == rest[n+1, 1]; n += 1; end
      generals(n+1, rest, pick, head) if n < 2
    end
  end
end

# テスト
Tehai.new('1112224588899').machi
Tehai.new('1122335556799').machi
Tehai.new('1112223335559').machi
Tehai.new('1223344888999').machi
Tehai.new('1112345678999').machi # 九蓮宝燈
Tehai.new('1112223334699').machi # 嵌張待ち([46])
Tehai.new('1122334455667').machi # 七対子
Tehai.new('1112223334445').machi # 多面待ち
Tehai.new('1112223334567').machi # ノベタン

実行してみる。

$ ruby /Users/bebe/Desktop/marjon6.rb 
"1112224588899"
(111)(222)(888)(99)[45]
ループ回数: 17

"1122335556799"
(123)(123)(55)(567)[99]
(123)(123)(555)(99)[67]
(123)(123)(567)(99)[55]
ループ回数: 67

"1112223335559"
(111)(222)(333)(555)[9]
(123)(123)(123)(555)[9]
ループ回数: 89

"1223344888999"
(123)(234)(888)(999)[4]
(123)(44)(888)(999)[23]
(234)(234)(888)(999)[1]
ループ回数: 43

"1112345678999"
(11)(123)(456)(789)[99]
(11)(123)(456)(999)[78]
(11)(123)(678)(999)[45]
(11)(345)(678)(999)[12]
(111)(234)(567)(99)[89]
(111)(234)(567)(999)[8]
(111)(234)(678)(999)[5]
(111)(234)(789)(99)[56]
(111)(345)(678)(999)[2]
(111)(456)(789)(99)[23]
(123)(456)(789)(99)[11]
ループ回数: 81

"1112223334699"
(111)(222)(333)(99)[46]
(123)(123)(123)(99)[46]
ループ回数: 90

"1122334455667"
(11)(22)(33)(44)(55)(66)[7]
(11)(234)(234)(567)[56]
(11)(234)(456)(567)[23]
(123)(123)(44)(567)[56]
(123)(123)(456)(456)[7]
(123)(123)(456)(567)[4]
(123)(234)(456)(567)[1]
ループ回数: 122

"1112223334445"
(11)(123)(234)(234)[45]
(11)(123)(234)(345)[24]
(11)(234)(234)(345)[12]
(111)(22)(234)(345)[34]
(111)(222)(33)(345)[44]
(111)(222)(33)(444)[35]
(111)(222)(333)(44)[45]
(111)(222)(333)(444)[5]
(111)(222)(345)(44)[33]
(111)(234)(234)(234)[5]
(111)(234)(234)(345)[2]
(123)(123)(123)(44)[45]
(123)(123)(123)(444)[5]
(123)(123)(345)(44)[12]
ループ回数: 184

"1112223334567"
(11)(123)(234)(567)[23]
(111)(22)(234)(567)[33]
(111)(22)(333)(567)[24]
(111)(222)(33)(345)[67]
(111)(222)(33)(567)[34]
(111)(222)(333)(456)[7]
(111)(222)(333)(567)[4]
(111)(234)(33)(567)[22]
(123)(123)(123)(456)[7]
(123)(123)(123)(567)[4]
(123)(123)(234)(567)[1]
ループ回数: 155
  • コードは短くなり、かなり凝縮された。
  • その分、所々に条件判定が増えて、見通しは悪くなった。
  • ループ回数は少し増えた。

所感

  • 自分にとって、再帰呼び出しは難解だ。コードの見通しが悪くなる。
  • 分かり易さでは、最初の2100回ループするコードが一番読み易かった。
x=256,y=65793;char*t,s[99];f(i,p,j,k){int*m=t=s+x+(k=i%x),*q=s+p;s[p-1]=41,j=i/x-3,i>768?!j&*t>21-p|j>0&p>19&*t&t[j]?*q=91+k*y-k+j*x,s[23]=93,puts(s+1):0:*m-(j=i<x?y:1-j)&y*8||(*m-=j,*q=40+k*y*x+(i<x)*258*x,f(i>>9?768:i,p+5-i/512),*m+=j);i-x*6&&f(i+1,p);}main(){for(t=gets(s);*t;s[x+*t++]++);f(0,1);}
kusano_prog
$ gcc -m32 ~/Desktop/marjon.c
~/Desktop/marjon.c:1: warning: data definition has no type or storage class
~/Desktop/marjon.c: In function ‘f’:
~/Desktop/marjon.c:1: warning: initialization from incompatible pointer type
~/Desktop/marjon.c:1: warning: initialization from incompatible pointer type
~/Desktop/marjon.c: In function ‘main’:
~/Desktop/marjon.c:1: warning: assignment makes pointer from integer without a cast

$ ./a.out
warning: this program uses gets(), which is unsafe.
1112345678999
(321)(654)(987)(11)[99]
(321)(654)(987)(99)[11]
(321)(654)(999)(11)[87]
(321)(876)(999)(11)[54]
(432)(765)(111)(999)[8]
(432)(765)(111)(99)[98]
(432)(876)(111)(999)[5]
(432)(987)(111)(99)[65]
(543)(876)(111)(999)[2]
(543)(876)(999)(11)[21]
(654)(987)(111)(99)[32]
  • いろいろ警告されるけど、ちゃんと待ち判定してくれた。すごい!