challenge 麻雀の和了判定

麻雀の手牌が和了形か否かを判定するコードを書いてください。

手牌を表すのは、mn+h 枚を表す配列です。フォーマットは A, B の2つから任意に選んだものを受け取ってください。以下の2つは同じものです:
handA = [2,1,1,1,1,2,2,1,3] # 牌1が2個、牌2が1個、牌3が1個、牌4が1個、牌5が1個、牌6が2個、牌7が2個、牌8が1個、牌9が3個
handB = [1,1,2,3,4,5,6,6,7,7,8,9,9,9] # 牌1、牌1、牌2、牌3、牌4、牌5、牌6、牌7、牌7、牌8、牌9、牌9、牌9

ある配列が和了形であるとは、手牌が「刻子」「順子」を合計 n 個と「対子」1 個との和であることをいうものとします。
「刻子」とは1種の牌が m 個あることをいいます。
「順子」とは連続したインデクスを持つ牌が m 種各1個あることをいいます。
「対子」とは1種の牌が h 個あることをいいます。

上の例は、和了形です。なぜなら、当該配列が
Bタイプで表記すると、刻子 [9,9,9], 順子 [2,3,4], [5,6,7], [6,7,8], 対子 [1,1] の、
Aタイプで表記すると、刻子 [0,0,0,0,0,0,0,0,3], 順子 [0,1,1,1,0,0,0,0,0], [0,0,0,0,1,1,1,0,0], [0,0,0,0,0,1,1,1,0], 対子 [2,0,0,0,0,0,0,0,0] の、
和だからです。

・ n は任意のものを受け取れるようにしてください。
・ 牌のインデクスの数 (Aタイプの長さ、Bタイプの要素の最大値) M も任意としてください。
・ 1種の牌の最大個数 (Aタイプの要素の最大値、Bタイプの1種の要素の最大重複度) L も任意としてください。
・ 一般の麻雀の場合は、m=3, n=4, h=2, M=9, L=4 です。この条件に特化した高速化が可能なら行ってもかまいません。

擬似コードで、ごく短い例を示します。
1
2
3
4
5
6
7
8
for 考えられる対子:
    対子を手牌から抜く
    for 考えられる刻子のパターン:
        刻子を手牌から抜く
        手牌が順子の和となっていれば、和了形として抜ける
        刻子を手牌へ戻す
    対子を手牌へ戻す
ここに到達していれば、和了形ではない

Posted feedbacks - Flatten

Nested Hidden

書いていたら頭がこんがらがって来ました。実行結果は下記です:

[1, 1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 9, 9, 9] True
[1, 2, 4, 4, 5, 6, 7, 8, 9, 7, 8, 9, 4, 4] False
[1, 1, 1, 3, 3, 3, 5, 5, 5, 7, 7, 7, 9, 9] True
[1, 1, 1, 3, 3, 3, 5, 5, 5, 7, 7, 7, 9, 7] False
[1, 1, 1, 3, 3, 3, 5, 5, 5, 7, 7, 7, 9, 8] True

参考: 麻雀 和了判定(役の判定) アルゴリズム

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- coding: utf-8 -*-

def get_set_candidate(hand, num):
  result = []
  hand = hand[:]
  hand.sort()
  for i in range(1, len(hand)+1):
    if hand.count(i) >= num:
      result.append(i)
  return result

def clear_set(hand, clear_tile, num):
  hand = hand[:]
  hand.sort()
  for i in range(0, num):
    hand.remove(clear_tile)
  return hand

def make_comb(list, num):
  if num <= 0: return [[]]
  size = len(list)
  result = []
  clonecker = lambda list1, list2: map((lambda i: list1 + i), list2)
  for i in range(size-num+1):
    result += clonecker([list[i]], make_comb(list[i+1:], num-1))
  return result

def all_comb(list):
  result = []
  for i in range(len(list)+1):
    result += make_comb(list, i)
  return result

def check_sequence(hand, m=3):
  result = []
  hand = hand[:]
  hand.sort()
  for i in range(0, len(hand)/m):
    j = hand.pop()
    try:
      hand.remove(j-1)
      hand.remove(j-2)
    except:
      return False
  return True

def check_winning(hand, m=3, h=2):
  for i in get_set_candidate(hand, h):
    hand2 = clear_set(hand, i, h)
    for j in all_comb(get_set_candidate(hand2, m)):
      hand3 = hand2[:]
      for k in j:
        hand3 = clear_set(hand3, k, m)
      if check_sequence(hand3, m): return True
  return False

hand1 = [1,1,2,3,4,5,6,6,7,7,8,9,9,9]
hand2 = [1,2,4,4,5,6,7,8,9,7,8,9,4,4]
hand3 = [1,1,1,3,3,3,5,5,5,7,7,7,9,9]
hand4 = [1,1,1,3,3,3,5,5,5,7,7,7,9,7]
hand5 = [1,1,1,3,3,3,5,5,5,7,7,7,9,8]

print hand1, check_winning(hand1)
print hand2, check_winning(hand2)
print hand3, check_winning(hand3)
print hand4, check_winning(hand4)
print hand5, check_winning(hand5)

Squeak Smalltalk で。

幅優先で探索して和了パターンが1つでも見つかれば true を返します。牌は1文字の種類とインデックス文字列の組み合わせで与えます。なお、インデックス文字列の文字数が最大値(M)の文字列の文字数に満たない場合、あらかじめ左側を「0」で埋めて文字数を合わせて与える必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| m n h M L 対子群 手牌 刻子群 同種牌群 和了パターン 残り 待ち行列 刻順子群 |
m := 3. n := 4. h := 2. M := 9. L := 4.
手牌 := #(a1 a1 a2 a3 a4 a5 a6 a6 a7 a7 a8 a9 a9 a9).

self assert: (手牌 allSatisfy: [:each | each size = (M printString size + 1)]).
self assert: 手牌 size = (m * n + h).
self assert: 手牌 asBag sortedCounts first key <= L.
対子群 := (手牌 groupBy: [:each | each] having: [:group | group size >=  h]) values 
    collect: [:group | (group first: h) asArray].
刻子群 := (手牌 groupBy: [:each | each] having: [:group | group size >=  m]) values 
    collect: [:group | (group first: m) asArray].
同種牌群 := (手牌 groupBy: [:each | each first] having: [:group | group size >= m]) values.
刻順子群 := OrderedCollection withAll: 刻子群.
同種牌群 do: [:group | 
    | 牌種 インデックス列 |
    牌種 := group anyOne first.
    インデックス列 := (group collect: [:each | each allButFirst asInteger]) asSet asSortedArray.
    ((1 to: M - m + 1) 
            collect: [:start | start to: start + m - 1] 
            thenSelect: [:range | インデックス列 includesAllOf: range]) 
        do: [:range | 
            刻順子群 add: (range collect: [:each | 
                ((each printPaddedWith: $0 to: M printString size) 
                    copyWithFirst: 牌種) asSymbol])]].
和了パターン := Set new.
対子群 do: [:対子 |
    残り := 手牌 asOrderedCollection.
    対子 do: [:各々 | 残り remove: 各々].
    待ち行列 := OrderedCollection with: {対子. 残り}.
    [待ち行列 notEmpty] whileTrue: [
        | 状態 |
        状態 := 待ち行列 removeFirst.
        状態 last ifEmpty: [和了パターン add: 状態 allButLast asBag] ifNotEmpty: [
            刻順子群 do: [:刻順子 |
                (刻順子 inject: 1 into: [:prev :牌 | 
                        (状態 last indexOf: 牌 startingAt: prev ifAbsent: [状態 last size + 1]) + 1]) 
                    <= (状態 last size + 1) ifTrue: [
                        | 次 | 
                        次 := 状態 last copy.
                        刻順子 do: [:各々 | 次 remove: 各々].
                        待ち行列 add: (状態 allButLast, {刻順子 asArray. 次})]]]]].
和了パターン notEmpty

"=> true "

受け付けるフォーマットはBタイプです。 以下のようなxml文書を入力として与えると、hand要素に"和了形"属性(true/false)を追加して出力します。:

<hands>
  <hand>1,1,2,3,4,5,6,6,7,7,8,9,9,9</hand>
  <hand>1,1,2,2,2,3,3,3,3,4,4,5,6,6</hand>
  <hand>1,1,2,2,4,4,5,5,6,6,7,7,9,9</hand>
  <hand>1,1,2,2,4,4,5,5,6,7,8,7,8,9</hand>
</hands>

後、お題にはなかったのですが、七対子にも対応してみました。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:my="uri:ja.doukaku.org:my-functions"
  exclude-result-prefixes="my"
  >

  <xsl:output method="xml" />

  <xsl:variable name="debug" as="xs:boolean" select="false()" />
  <xsl:variable name="can七対子" as="xs:boolean" select="true()" />

  <xsl:variable name="m" as="xs:integer" select="3" />
  <xsl:variable name="n" as="xs:integer" select="4" />
  <xsl:variable name="h" as="xs:integer" select="2" />
  <xsl:variable name="M" as="xs:integer" select="9" />
  <xsl:variable name="L" as="xs:integer" select="4" />

  <xsl:template match="/hands">
    <hands>
      <xsl:apply-templates select="./hand" />
    </hands>
  </xsl:template>

  <xsl:template match="hand">
    <xsl:variable name="pai" as="xs:integer*">
      <xsl:variable name="pai-unordered" as="xs:integer*">
        <xsl:for-each select="fn:tokenize(text(), ',')">
          <xsl:sequence select="xs:integer(.)" />
        </xsl:for-each>
      </xsl:variable>
      <xsl:for-each select="$pai-unordered">
        <xsl:sort />
        <xsl:sequence select="." />
      </xsl:for-each>
    </xsl:variable>
    <xsl:if test="fn:max($pai) &gt; $M">
      <xsl:message terminate="yes">
        <xsl:text>invalid max value of pai. expected </xsl:text>
        <xsl:value-of select="$M" />
        <xsl:text> but </xsl:text>
        <xsl:value-of select="fn:max($pai)" />
      </xsl:message>
    </xsl:if>
    <xsl:for-each select="fn:distinct-values($pai)">
      <xsl:if test="fn:count(fn:index-of($pai, .)) &gt; $L">
        <xsl:message terminate="yes">
          <xsl:text>invalid count per a kind, expected </xsl:text>
          <xsl:value-of select="$L" />
          <xsl:text> but </xsl:text>
          <xsl:value-of select="fn:count(fn:index-of($pai, .))" />
          <xsl:text> for </xsl:text>
          <xsl:value-of select="." />
        </xsl:message>
      </xsl:if>
    </xsl:for-each>

    <xsl:element name="hand">
      <xsl:attribute name="和了形">
        <xsl:value-of select="my:check-和了形($pai)" />
      </xsl:attribute>
      <xsl:value-of select="text()" />
    </xsl:element>
  </xsl:template>

  <xsl:function name="my:check-和了形" as="xs:boolean">
    <xsl:param name="pai" as="xs:integer*" />

    <xsl:variable name="result" as="xs:boolean*">
      <xsl:sequence select="my:check-対子($pai,0)" />
    </xsl:variable>

    <xsl:value-of select="fn:count($result)&gt;0" />
  </xsl:function>

  <xsl:function name="my:check-対子" as="xs:boolean*">
    <xsl:param name="pai" as="xs:integer*" />
    <xsl:param name="depth" as="xs:integer" />

    <xsl:if test="$debug">
      <xsl:message>
        <xsl:text>check1: </xsl:text>
        <xsl:value-of select="$pai" />
      </xsl:message>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="fn:empty($pai)">
        <xsl:sequence select="true()" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:if test="$depth=0 or $can七対子">
          <xsl:for-each select="1 to (fn:count($pai) - ($h - 1))">
            <xsl:variable name="i" as="xs:integer" select="." />
            <xsl:if test="fn:count(fn:distinct-values(fn:subsequence($pai,$i, $h)))=1">
              <xsl:variable name="next-pai" as="xs:integer*"
                select="$pai[position() &lt; $i or $i + ($h - 1) &lt; position()]" />
              <xsl:sequence select="my:check-対子($next-pai, $depth+1)" />
            </xsl:if>
          </xsl:for-each>
        </xsl:if>
        <xsl:if test="$depth=1">
          <xsl:sequence select="my:check-刻子($pai, $depth)" />
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>

  <xsl:function name="my:check-刻子" as="xs:boolean*">
    <xsl:param name="pai" as="xs:integer*" />
    <xsl:param name="depth" as="xs:integer" />

    <xsl:if test="$debug">
      <xsl:message>
        <xsl:text>check2: </xsl:text>
        <xsl:value-of select="$pai" />
      </xsl:message>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="fn:count($pai)=0">
        <xsl:if test="$depth - 1!=$n">
          <xsl:message terminate="yes">
            <xsl:text> invalid count of 刻子/順子. expected </xsl:text>
            <xsl:value-of select="$n" />
            <xsl:text> but </xsl:text>
            <xsl:value-of select="$depth - 1" />
          </xsl:message>
        </xsl:if>
        <xsl:sequence select="true()" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:for-each select="1 to (fn:count($pai) - ($m - 1))">
          <xsl:variable name="i" as="xs:integer" select="." />
          <xsl:if test="fn:count(fn:distinct-values(fn:subsequence($pai,$i, $m)))=1">
            <xsl:variable name="next-pai" as="xs:integer*"
              select="$pai[position() &lt; $i or $i + ($m - 1) &lt; position()]" />
            <xsl:sequence select="my:check-刻子($next-pai, $depth+1)" />
          </xsl:if>
        </xsl:for-each>
        <xsl:sequence select="my:check-順子($pai, $depth)" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>

  <xsl:function name="my:check-順子" as="xs:boolean*">
    <xsl:param name="pai" as="xs:integer*" />
    <xsl:param name="depth" as="xs:integer" />

    <xsl:if test="$debug">
      <xsl:message>
        <xsl:text>check3: </xsl:text>
        <xsl:value-of select="$pai" />
      </xsl:message>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="fn:count($pai)=0">
        <xsl:if test="$depth - 1 != $n">
          <xsl:message terminate="yes">
            <xsl:text> invalid count of 刻子/順子. expected </xsl:text>
            <xsl:value-of select="$n" />
            <xsl:text> but </xsl:text>
            <xsl:value-of select="$depth - 1" />
          </xsl:message>
        </xsl:if>
        <xsl:sequence select="true()" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:for-each select="1 to (fn:count($pai) - ($m - 1))">
          <xsl:variable name="i" as="xs:integer" select="." />
          <xsl:variable name="indexes" as="xs:integer*">
            <xsl:variable name="values" as="xs:integer*">
              <xsl:sequence select="$pai[$i]" />
              <xsl:for-each select="1 to ($m - 1)">
                <xsl:sequence select="$pai[$i] + ." />
              </xsl:for-each>
            </xsl:variable>
            <xsl:for-each select="$values">
              <xsl:variable name="idx" as="xs:integer*"
                select="fn:index-of($pai, .)" />
              <xsl:if test="fn:exists($idx)">
                <xsl:sequence select="$idx[1]" />
              </xsl:if>
            </xsl:for-each>
          </xsl:variable>
          <xsl:if test="fn:count($indexes)=$m">
            <xsl:variable name="next-pai" as="xs:integer*"
              select="$pai[fn:empty(fn:index-of($indexes, position()))]" />
            <xsl:sequence select="my:check-順子($next-pai, $depth+1)" />
          </xsl:if>
        </xsl:for-each>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>
</xsl:stylesheet>

 Aタイプ向けに書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#! /usr/bin/ruby -Ks

class CheckFinished
    PAIR = 2
    SET = 3
    ORDER = 3
    def self.check_finished(pieces)
        check_pair(pieces)
    end
    def self.check_pair(pieces)
        pieces.each_index do |i|
            if pieces[i] >= PAIR
                (s = pieces.clone)[i] = s[i] - PAIR
                if check_set((0..s.size - 1).to_a.select { |j| s[j] >= SET }, s)
                    return true
                end
            end
        end
        false
    end
    def self.check_set(indexes, pieces)
        if indexes.empty?
            check_order(pieces)
        else
            i = (x = indexes.clone).shift
            (0..pieces[i] / SET).each do |w|
                (s = pieces.clone)[i] = s[i] - SET * w
                if check_order(s) || check_set(x, s)
                    return true
                end
            end
            false
        end
    end
    def self.check_order(pieces)
        if pieces.reject { |p| p == 0 }.empty?
            true
        else
            c = (0..pieces.size - ORDER).find { |i| pieces[i] > 0 }
            unless c && (c..c + ORDER - 1).select { |j| pieces[j] > 0 }.size == ORDER
                false
            else
                check_order(pieces.clone.fill(c, ORDER) { |j| pieces[j] - 1 })
            end
        end
    end
    private_class_method :check_pair, :check_set, :check_order
end

pieces = [2,1,1,1,1,2,2,1,3]
puts "[#{pieces.join(',')}] => " + CheckFinished.check_finished(pieces).to_s

フォーマットAで、m,hも指定可能にしてみました。使用言語はF#です。

m=3でh=2の場合

> isFinished 3 2 [|2;1;1;1;1;2;2;1;3|];;

val it : bool = true

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
let isMoreEqM m  (arr : int []) (index : int) =
    arr.[index] >= m

//配列arrにおいてindexから始まる連続したm個の要素が全部1以上か
let isConsecMoreEq1 m  (arr : int []) (index : int) =
    let mutable res = true
    for i in 0 .. (m - 1) do
        if arr.[index + i] = 0 then
            res <- false
    res

//後ろにk個の0を付け加えた配列を返す
let addEle k (arr : int []) =
    let t = Array.zeroCreate (arr.Length + k)
    for i in 0 .. (arr.Length - 1) do
        t.[i] <- arr.[i]
    t

let isFinished mIn hIn (arrIn : int []) =
    let targetArr = addEle (mIn - 1) arrIn
    let isRemoveAll m (arr : int []) =
        let searchIndexEnd = arr.Length - m
        let result = ref false
        let rec reMoveM (arr : int []) index =
            if index = searchIndexEnd + 1 then
                    result := true
            elif arr.[index] = 0 then
                    reMoveM arr (index + 1) 
            elif  (isMoreEqM m arr index = true) && (isConsecMoreEq1 m arr index = true) then
                    reMoveKousi m arr index
                    reMoveJyunsi m arr index  
            elif  isMoreEqM m arr index = true then
                    reMoveKousi m arr index
            elif  isConsecMoreEq1 m arr index = true then
                    reMoveJyunsi m arr index
            else
                    ()     
        and reMoveKousi mA (arrA :int []) indexA =
             arrA.[indexA] <- arrA.[indexA] - mA; 
             reMoveM arrA indexA 
             arrA.[indexA] <- arrA.[indexA] + mA; 
        
        and reMoveJyunsi mB (arrB :int []) index =
             for i in 0 .. (mB-1) do
                 arrB.[index+i] <- arrB.[index+i] - 1
             reMoveM arrB index 
             for i in 0 .. (mB-1) do
                 arrB.[index+i] <- arrB.[index+i] + 1
  
        reMoveM arr 0
        !result                  
   
    let rec reMoveH m h (arr : int []) index =
        let searchIndexEnd = arr.Length - m
        if index = searchIndexEnd + 1 then
            false
        else
            if arr.[index] >= h then
                arr.[index] <- arr.[index] - h
                if isRemoveAll m arr = true then
                    true
                else
                    arr.[index] <- arr.[index] + h
                    reMoveH m h arr (index + 1)
            else
              reMoveH m h arr (index + 1)  
    
    reMoveH mIn hIn targetArr 0

F#の勉強中で文法よくわかってないですが、多分、こんな感じかな。

<実行結果>
[3; 3; 3; 3; 0; 0; 0; 0; 2] - 上がっています。
[1; 1; 2; 1; 1; 3; 3; 0; 2] - 上がっています。
[0; 1; 1; 3; 1; 2; 2; 1; 3] - 上がっています。
[2; 2; 2; 2; 2; 2; 0; 0; 2] - 7対子で上がっています。
[3; 1; 2; 1; 1; 3; 3; 0; 0] - 上がっています。
[1; 1; 2; 1; 1; 3; 4; 0; 1] - 上がっていません。
[2; 1; 1; 1; 1; 2; 2; 1; 3] - 上がっています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
open System

let sampleHands = [
                    [3;3;3;3;0;0;0;0;2];
                    [1;1;2;1;1;3;3;0;2];
                    [0;1;1;3;1;2;2;1;3]; 
                    [2;2;2;2;2;2;0;0;2];
                    [3;1;2;1;1;3;3;0;0];
                    [1;1;2;1;1;3;4;0;1];
                    [2;1;1;1;1;2;2;1;3]; 
                  ]

type Hand = list<int>
type Mahjong() =
    let m, n, h, M, L = 3, 4, 2, 9, 4
    let GetRestOfHand (f:int->int->int option) (hand:Hand) : (Hand option) =
        let rec GetRestOfHand' (hand':Hand) =
            if hand'.Length = M then
                Some hand'
            else
                let thisKind = hand'.Length
                match f thisKind hand.[thisKind] with
                | Some(i) -> GetRestOfHand' (hand' @ [i])
                | _ -> None
        GetRestOfHand' []
    // 手牌から各要素を取り除いた手牌を返す。
    let TakeToitsu (fromThisKind:int) (hand:Hand) : (Hand option) =
        GetRestOfHand (fun thisKind i ->
            match thisKind with
            | k when k < 0 || hand.Length <= k -> None
            | k when k = fromThisKind -> if i >= h then Some(i-h) else None
            | k -> Some(i)) hand
    let TakeKotsu (fromThisKind:int) (hand:Hand) : (Hand option) =
        GetRestOfHand (fun thisKind i ->
            match thisKind with
            | k when k < 0 || hand.Length <= k -> None
            | k when k = fromThisKind -> if i >= m then Some(i-m) else None
            | k -> Some(i)) hand
    let TakeShuntsu (fromThisKind:int) (hand:Hand) : (Hand option) =
        GetRestOfHand (fun thisKind i ->
            match thisKind with
            | k when k < 0 || hand.Length <= k -> None
            | k when fromThisKind <= k && k < fromThisKind+m -> if i > 0 then Some(i-1) else None
            | k -> Some(i)) hand
    // 残りの手牌が空になれば上がっている。
    let IsEmpty (hand:Hand) = List.forall (fun i -> i = 0) hand
    // 残りの手牌が刻子と順子で構成されているか調べる。
    let rec CheckKotsuOrShuntsu (fromThisKind:int) (hand:Hand) =
        match fromThisKind with
        | k when k < 0 || hand.Length < k -> failwith "CheckKotsuOrShuntsu: 不正な値(fromThisKind=%d)です" k
        | k when k = hand.Length -> IsEmpty hand
        | k -> match TakeKotsu k hand with
               | Some(x) -> CheckKotsuOrShuntsu k x || match TakeShuntsu k hand with
                                                       | Some(x) -> CheckKotsuOrShuntsu k x
                                                       | None -> CheckKotsuOrShuntsu (k+1) hand
               | None -> match TakeShuntsu k hand with
                         | Some(x) -> CheckKotsuOrShuntsu k x
                         | None -> CheckKotsuOrShuntsu (k+1) hand
    // 手牌から頭を取り除き、残りの手牌を調べる。
    let rec CheckToitsu (fromThisKind:int) (hand:Hand) =
        match fromThisKind with
        | k when k < 0 || hand.Length < k -> failwith "CheckToisu: 不正な値(fromThisKind=%d)です" k
        | k when k = hand.Length -> false
        | k -> match TakeToitsu k hand with
               | None -> CheckToitsu (k+1) hand
               | Some(x) -> if CheckKotsuOrShuntsu 0 x then true
                            else CheckToitsu (k+1) hand
    member this.CheckHand (hand:Hand) =
        if hand.Length <> M || List.exists (fun i -> i > L) hand then
            printfn "%A - 不正な手牌です。条件(M=%d, L=%d)を満たしていません。" hand M L
        elif List.sum hand <> m*n+h then
            printfn "%A - 不正な手牌です。条件(m=%d, n=%d, h=%d)を満たしていません。" hand m n h
        elif List.forall (fun i -> i = h || i = 0) hand then
            printfn "%A - %d対子で上がっています。" hand ((m*n+h)/h)
        elif CheckToitsu 0 hand then
            printfn "%A - 上がっています。" hand
        else
            printfn "%A - 上がっていません。" hand

[<STAThread>]
[<EntryPoint>]
let main args =
    let m = new Mahjong()
    List.iter (fun hand -> m.CheckHand(hand)) sampleHands
#if DEBUG
    Console.ReadKey() |> ignore
#endif
    0

Index

Feed

Other

Link

Pathtraq

loading...