プログラム
2014年05月23日
PAIFUN ファイルの形式 (1)
PAIFUN ファイルの形式 (0) ではわかっていなかった新しい情報がいくつかある。
- 打牌の6バイト後にはツモ切りなら1が格納されている
- ポン牌の6バイト後に0は上家から、1は対面から、2は下家からを表す
- 誰から鳴いたかを示すのは取牌部と最終形との両方に付され、最終形なら面子全体に付される
- 最終形で加槓になっていても誰から鳴いたかは残ったまま
- 河の8バイト後には放銃なら2が、流局なら4が格納されている
- カンで増えた手牌は副露数に対応する位置に置かれる (最も右の副露が槓子ならアガリ牌の手前、最も左の副露が槓子なら13枚目の直後)
- 最終形で横にする牌の 0x10 バイト後に1が格納されている
2014年05月17日
2013年09月01日
最高到達段位の確率分布
うたたねこ氏によるシミュレーションの純粋数値計算による追試。 といえば聞こえがよいが、まあ、ただの二番煎じのようなもの。 鳳凰卓で打てなくなる場合の処理 (0試合で七段に復帰) は、シミュレーションの条件と合わせてある。 ただし、実力八段の順位分布は確率が等差数列となる [0.264705882, 0.254901961, 0.245098039, 0.235294118] とした。 数値計算では、まず (現在の段位およびpt, 最高到達段位) でインデクスされた存在確率ベクトルを作り、推移行列を乗じることを繰り返す。 ただし、インデクスは1次元にしない方が書き易く、推移行列は疎であることを利用しているので、実装は普通の行列乗算とは大きく異なる。
結果を表にする。 量が多すぎるような気もするが、気にしない。 有効数字は8桁から10桁だと思うが、確率の106倍を四捨五入してある。 丸め誤差によって合計が最大2ずれうる。
南七 | 七 | 八 | 九 | 十 | 天 |
---|---|---|---|---|---|
100 | 921216 | 78712 | 72 | 0 | 0 |
200 | 768101 | 227615 | 4281 | 3 | 0 |
300 | 639004 | 344800 | 16123 | 73 | 0 |
400 | 531679 | 435792 | 32209 | 319 | 0 |
500 | 442398 | 506822 | 50015 | 763 | 2 |
600 | 368113 | 562172 | 68343 | 1367 | 4 |
700 | 306303 | 604942 | 86663 | 2084 | 8 |
800 | 254871 | 637495 | 104746 | 2875 | 14 |
900 | 212075 | 661696 | 122494 | 3715 | 20 |
1000 | 176465 | 679047 | 139874 | 4586 | 28 |
2000 | 28077 | 664227 | 293846 | 13737 | 114 |
3000 | 4467 | 555735 | 416690 | 22905 | 203 |
4000 | 711 | 452597 | 514412 | 31987 | 293 |
5000 | 113 | 366697 | 591824 | 40983 | 382 |
6000 | 18 | 296800 | 652816 | 49894 | 472 |
7000 | 3 | 240178 | 700537 | 58720 | 562 |
8000 | 0 | 194351 | 737535 | 67463 | 651 |
9000 | 0 | 157266 | 765870 | 76123 | 741 |
10000 | 0 | 127258 | 787212 | 84701 | 830 |
20000 | 0 | 15317 | 816832 | 166126 | 1725 |
30000 | 0 | 1844 | 755395 | 240141 | 2620 |
40000 | 0 | 222 | 688851 | 307414 | 3513 |
50000 | 0 | 27 | 627016 | 368551 | 4406 |
60000 | 0 | 3 | 570593 | 424106 | 5298 |
70000 | 0 | 0 | 519231 | 474579 | 6189 |
80000 | 0 | 0 | 472490 | 520430 | 7080 |
90000 | 0 | 0 | 429957 | 562074 | 7969 |
100000 | 0 | 0 | 391252 | 599890 | 8858 |
東七 | 七 | 八 | 九 | 十 | 天 |
---|---|---|---|---|---|
100 | 990966 | 9034 | 0 | 0 | 0 |
200 | 929977 | 69991 | 32 | 0 | 0 |
300 | 856024 | 143447 | 529 | 0 | 0 |
400 | 785858 | 212077 | 2065 | 0 | 0 |
500 | 721246 | 274077 | 4674 | 3 | 0 |
600 | 661952 | 329925 | 8112 | 10 | 0 |
700 | 607547 | 380309 | 12120 | 24 | 0 |
800 | 557622 | 425837 | 16497 | 45 | 0 |
900 | 511803 | 467019 | 21106 | 72 | 0 |
1000 | 469751 | 504288 | 25856 | 105 | 0 |
2000 | 199311 | 725729 | 74387 | 573 | 0 |
3000 | 84566 | 793425 | 120915 | 1092 | 1 |
4000 | 35881 | 797448 | 165055 | 1614 | 1 |
5000 | 15224 | 775715 | 206924 | 2136 | 2 |
6000 | 6459 | 744245 | 246636 | 2657 | 2 |
7000 | 2741 | 709776 | 284302 | 3179 | 3 |
8000 | 1163 | 675109 | 320025 | 3700 | 3 |
9000 | 493 | 641377 | 353905 | 4220 | 4 |
10000 | 209 | 609010 | 386036 | 4741 | 4 |
20000 | 0 | 361439 | 628622 | 9930 | 9 |
30000 | 0 | 214436 | 770459 | 15091 | 14 |
40000 | 0 | 127221 | 852533 | 20226 | 19 |
50000 | 0 | 75478 | 899163 | 25334 | 24 |
60000 | 0 | 44780 | 924775 | 30416 | 29 |
70000 | 0 | 26567 | 937928 | 35471 | 34 |
80000 | 0 | 15762 | 943700 | 40499 | 39 |
90000 | 0 | 9351 | 945103 | 45501 | 44 |
100000 | 0 | 5548 | 943926 | 50477 | 49 |
南八 | 七 | 八 | 九 | 十 | 天 |
---|---|---|---|---|---|
100 | 844780 | 154872 | 347 | 0 | 0 |
200 | 578219 | 402091 | 19647 | 43 | 0 |
300 | 393067 | 534877 | 71098 | 957 | 1 |
400 | 267257 | 592966 | 135486 | 4277 | 14 |
500 | 181732 | 607720 | 200054 | 10418 | 75 |
600 | 123580 | 597461 | 259854 | 18885 | 221 |
700 | 84036 | 573009 | 313490 | 28996 | 469 |
800 | 57145 | 540922 | 360936 | 40177 | 820 |
900 | 38860 | 505233 | 402632 | 52012 | 1263 |
1000 | 26425 | 468435 | 439140 | 64216 | 1785 |
2000 | 559 | 190702 | 615410 | 184416 | 8914 |
3000 | 12 | 73892 | 620680 | 288622 | 16794 |
4000 | 0 | 28557 | 569470 | 377308 | 24665 |
5000 | 0 | 11035 | 503857 | 452631 | 32477 |
6000 | 0 | 4264 | 439050 | 516460 | 40226 |
7000 | 0 | 1648 | 380039 | 570401 | 47913 |
8000 | 0 | 637 | 327989 | 615836 | 55539 |
9000 | 0 | 246 | 282696 | 653955 | 63103 |
10000 | 0 | 95 | 243513 | 685785 | 70607 |
20000 | 0 | 0 | 54531 | 803050 | 142419 |
30000 | 0 | 0 | 12207 | 779112 | 208682 |
40000 | 0 | 0 | 2732 | 727443 | 269825 |
50000 | 0 | 0 | 612 | 673145 | 326243 |
60000 | 0 | 0 | 137 | 621560 | 378303 |
70000 | 0 | 0 | 31 | 573630 | 426340 |
80000 | 0 | 0 | 7 | 529328 | 470665 |
90000 | 0 | 0 | 2 | 488433 | 511565 |
100000 | 0 | 0 | 0 | 450695 | 549305 |
東八 | 七 | 八 | 九 | 十 | 天 |
---|---|---|---|---|---|
100 | 974752 | 25248 | 0 | 0 | 0 |
200 | 818461 | 181223 | 316 | 0 | 0 |
300 | 649719 | 345218 | 5062 | 1 | 0 |
400 | 510688 | 469853 | 19438 | 21 | 0 |
500 | 400773 | 555847 | 43236 | 145 | 0 |
600 | 314457 | 611497 | 73534 | 512 | 0 |
700 | 246738 | 644613 | 107403 | 1246 | 1 |
800 | 193609 | 661284 | 142696 | 2409 | 2 |
900 | 151923 | 666036 | 178030 | 4006 | 6 |
1000 | 119214 | 662189 | 212581 | 6003 | 13 |
2000 | 10554 | 472837 | 478922 | 37400 | 287 |
3000 | 934 | 296637 | 628172 | 73502 | 755 |
4000 | 83 | 183090 | 706780 | 108789 | 1259 |
5000 | 7 | 112746 | 742728 | 142750 | 1770 |
6000 | 1 | 69405 | 752932 | 175381 | 2281 |
7000 | 0 | 42723 | 747755 | 206730 | 2792 |
8000 | 0 | 26298 | 733555 | 236845 | 3302 |
9000 | 0 | 16188 | 714226 | 265773 | 3813 |
10000 | 0 | 9965 | 692151 | 293561 | 4323 |
20000 | 0 | 78 | 472862 | 517650 | 9411 |
30000 | 0 | 1 | 318567 | 666960 | 14472 |
40000 | 0 | 0 | 214585 | 765907 | 19508 |
50000 | 0 | 0 | 144542 | 830940 | 24518 |
60000 | 0 | 0 | 97362 | 873135 | 29503 |
70000 | 0 | 0 | 65582 | 899956 | 34462 |
80000 | 0 | 0 | 44176 | 916429 | 39395 |
90000 | 0 | 0 | 29756 | 925940 | 44304 |
100000 | 0 | 0 | 20044 | 930769 | 49187 |
2012年12月29日
昇降段率数値計算機 (1)
前の記事で「まだ公開していないが」と書いた「昇段するまでに何ゲームを消化するかの期待値」について書くことにする。 これを年内に書くモチベーションを得ることができたのは、@enkaism さんがツールを公開されたことによるので感謝したい。 なお、12/29版のツールは私の記事とは独立であったようだ。
確率変数 X を「昇段する場合は、それまでにプレイする試合数。降段する場合は0」とおく。 すると、その期待値 E[X] を昇段する確率 W で割れば、昇段する条件でのプレイする試合数の期待値が求められる。 もちろん E[X] は現在の段位ポイント x の関数である。 境界条件は「0未満やM以上では0」となる。
W(x) と同じように考えることによって、E[X](x) の新しい漸化式が E[X](x) = W(x) + Σ[1 ≦ r ≦ 4]prE[X](x+Vr) とわかる。 第1項は、現在のポイントから昇段する場合は最初のゲームが算入されるということを表している。
連立方程式を行列の形で表すとき、係数行列は W(x) を求めるためのものと全く同じで、定数部分だけを上の式に従って変えればよい。
つまり、前の記事のコードでいう GaussElimination
の b
にあたる部分に、W(x) を入力すればよいのである。
2012年12月21日
昇降段率数値計算機 (0)
天鳳の段位戦はレーティング以上に段位ポイントが重視され、シミュレーションなどによる遊びや研究の対象となってきた。 順位分布から段位変動をシミュレートするツールとしてkansenzako氏によるものやなめとん氏によるものが、具体的なテーマを定めた研究にはみ〜にん氏によるものがある。 個人的にはレーティングには興味があるが、段位は「レーティングの差を正しく解釈できるほど賢くないプレイヤーにも、パッと見て「格の違い」をわかりやすく提示するツール」と軽く見ていた。
私の主観に変更はないが、段位の変動はシミュレートによらなくても簡単な計算で明らかにすることができると気付いた。 段位ポイントが離散値を取ることを利用し、昇段や降段の確率や、それまでにプレイするゲーム数の期待値を求めることついて記しておくことにする。
まず前提となる条件をまとめておく。 プレイする卓のランク・ゲームの種類は任意に1種とって固定する。 ランクは簡単のため、特上 (四段以上) と鳳凰 (七段以上) とに限る。 ゲームの種類は、東南と東風との区別のみを問題とする。 ただしこれは「東南に決めたならそのどのルールを打ってもよい」という意味ではなく「東南のルールを任意にとって固定したことにするが、どれに固定したかは計算上は不問となる」という意味である。 プレイしている間、実力の変化や流れ的な現象は起こらないとする。 つまり、各ゲームの結果は確率変数として独立同分布 (i.i.d.) である。
1位から4位を獲得する確率をそれぞれ p1, p2, p3, p4 とする。 もちろんこれらは [0..1] の値をとり、p3 = 1 - (p1+p2+p4) である。 現在の段位で1位から4位を獲得した場合の段位ポイント変動を V1, V2, V3, V4 とする。
段位ポイントが 0 未満で降段、現在の段位によって決まる定数 M 以上で昇段する。 有限ゲーム後、降段するより先に昇段する確率を W とし、昇段するより先に降段する確率を L とする。 これらはともに現在の段位ポイント x の関数である。
一般的な設定では獲得段位ポイントの絶対値の期待値は正である。 すなわち Σ[1 ≦ r ≦ 4]pr|Vr| > 0 となる。 この仮定がみたされないのは、特上卓や鳳凰卓では「必ず3位を取る」場合のみである。 この仮定がみたされるとき、確率1で有限ゲーム後に昇段または降段する。 すなわち W(x) + L(x) = 1 である。 したがって、以下 W(x) のみを考えれば十分である。
ポイントが 0未満や M以上であるときは、昇段と降段のどちらが先であるか確定している。 つまり x < 0 ⇒ W(x) = 0 および M ≦ x ⇒ W(x) = 1 が成り立つ。
x が上記の範囲外であるとする。 このとき昇段や降段の確率は、最初にプレイするゲームの結果によって変化する。 具体的には、ゲームの結果が r位であれば昇段の確率は W(x+Vr) に変化する。 r位になる確率は pr であったから、結局 W(x) = Σ[1 ≦ r ≦ 4]prW(x+Vr) となる。
このままでは厄介な関数方程式であるが、よい性質が2つある。 まずxは離散的である (具体的には5の倍数のみをとる) から、上の方程式は有限個である。 次に関係式は線型なものだけからなる。 したがって、問題は連立一次方程式に帰着される。 変数は十段であっても 4000/5 = 800個 しかないため、特に工夫をしなくてもガウス消去法で解くことができる。
以下は上記の式を行列で表し、標準的なアルゴリズムで解くコードである。 プログラミング言語は書き易くて高速に動作するD言語を用いた。
More...2011年05月24日
2010年02月25日
平和天和におけるドラ指標牌枚数の確率分布
放置しすぎていたので、最近書いたプログラムでも。 以下に示すのは「平和天和におけるドラ指標牌枚数の確率分布」を求めるための基礎データを出力するプログラムである。 プログラムは4つの部分からなる。
RANK, IDENTICAL = 9, 4 # tile condition
PAIR, SET, SETS = 2, 3, 4 # winning condition
USEPAIR = 1 # if zero, no pair
ONESEQ = RANK-SET+2 # Frequently used constant
まずは設定を行う。 Python では複数の変数へ同時に代入を行うことができる。 上3行は順に数牌の定義、和牌型の定義、雀頭を含む組合せを出力するかどうかの設定で、第4行は順子の種類数を表す定数である。 順子にはプログラム上、ランクの小さいものから順に 0, 1, ... が割り振られるが、順子を使わない場合を -1 で表す。
SP = set([tuple(sorted(filter(lambda seq: seq != -1, [idx // ONESEQ**seti % ONESEQ - 1 for seti in range(SETS)]))) for idx in range(ONESEQ**SETS)])
# all -> filter -> sort -> set
SP = [[sum(map(lambda seq: seq <= rank < seq+SET, P)) for rank in range(RANK)] for P in SP]
# sequence index -> tile count
SP = filter(lambda P: max(P) <= IDENTICAL, [[P[i]+PAIR*(i==pair) for i in range(RANK)] for P in SP for pair in range(-1, RANK*USEPAIR)])
# with or without pair
この3行で順子および雀頭からなるパターンをすべて生成している。
各 idx に対して [idx // ONESEQ**seti % ONESEQ - 1 for seti in range(SETS)] は第 idx 番目の順子使用パターンを与えるが、これを idx in range (ONESEQ**SETS) に対して並べるだけでは「順子を使わないという意味の -1 を順子番号として含んでいる」「順子を使う順番を区別している」という問題がある。 そこで「順子を使わないという意味の -1 を除去する」「順子の番号をソートする」「集合とすることによって重複を除く」という操作を行っているのだが、リストを集合に変換する際に tuple を使い、その要素をタプルに変換している。 これはリストが hashable ではないため集合の要素として使えない (一方、タプルは変更不可能なため hashable で、集合の要素として使える) という仕様に基づいている。
次に順子番号のタプルを、各牌を使用する枚数のリスト (牌式) へ変換している。
最後に対子を加えた組み合わせを生成するのだが、ここでも対子インデクス -1 を「対子なし」として処理している。
def uradora(P):
return [sum([IDENTICAL-P[rank-1] for rank in range(RANK) if P[rank] == identical]) for identical in range(IDENTICAL+1)]
def prob(P):
return reduce(lambda a, b: a*[1, 4, 6, 4, 1][b], P, 1)
F = [[tuple(uradora(P)), prob(P)] for P in SP]
# uradora pattern probability
uradora 関数は牌式 P を受け取って「裏ドラが n 枚乗るための指標牌は m 枚ある」というリスト (裏ドラ分布) を返す。 prob 関数は同様に、牌式 P を受け取って「牌式 P をみたす牌の組合せ数」を返す。
これらのリストは、必要とするデータのほぼすべてを含む。
for e in sorted([[RANK*IDENTICAL-sum(k)] + list(k) + [sum([P[1] for P in F if P[0] == k])] for k in set([P[0] for P in F])]):
print ' '.join(map(str, e))
最後に整形出力してプログラムは終了である。 それぞれの裏ドラ分布に対して、使用している牌の枚数、裏ドラ分布、それをみたす牌の組合せ数の和をリストとして生成し、これをソートする。 それらを1行ずつ、タブ文字でつないでテキストとして出力している。
行がはみだしても気にしない。
2009年03月08日
中麻平和何切る (1)
前稿では、平和の点数を評価するべき手の総数について上界を求めた。 実際にプログラムを組む際には、この上界を減らす工夫が重要である。
麻雀において、ある意味で「同じ形」と考えることのできる手牌には3つのパターンがある:
- 反転
- 全ての牌のランクを、10から引いたものに変更する。ex. 12344m345567p234s → 66789m345567p678s
- 変色
- スートごとに、牌式を交換する。ex. 12344m345567p234s → 12344m234p345567s
- 平行移動
- すべての牌のランクに、定数を加える。ex. 12344m345567p234s → 23455m456678p345s
これらによってパターンを削減することを考えよう。 ただし、操作「変色」においては定色役*1について、「平行移動」においては偏数役*2について、それぞれ「同じ」ではなくなることがある。*3 変色で緑一色の有無が変わるような手は、ほとんど清一色だから、8点を作るという観点からは問題として重要ではない。 また、推不倒が狙える手はそれをわかっていれば済むので、推不倒を無視しても大きな影響はない。 平行移動については、8点を作るという観点から、8点以下の役、すなわち断幺の有無については注意する必要がある。 また、非和了形において両搭を平行移動すると辺搭が現れたり、その逆が起こるので、やはり平行移動については特に注意しなければならない。
*1 定色役: 限られたスートの数牌で成立する役。推不倒・緑一色 の2種を指す。
*2 偏数役: 数牌のランクによる全体役。 断幺・大于五・小于五・全大・全中・全小 の6種を指す。
*3 反転は偏数役に影響を及ぼすが、断幺および全中は結果として変化がなく、他4役は大小が反転するだけなので、注意するに及ばない。
More...2009年03月03日
中麻平和何切る (0)
中国麻将の「何切る」は、日本麻雀のような「この手は攻めるべきか降りるべきか」より、純粋に「この手を和了に結びつけるためにはどちらを切るべきか」が難しく、また重要であることが多い。 理由の1つには8点縛りがある。 なんとしてでも8点を作らなければならず、そのためにはまだ関連牌すらないところに面子を見出したり、鳴きを駆使したりするのだ。
中国麻将を始めた人が最初に出会う中麻独自の役は、三色三歩高だろう。 「数牌の雀頭で順子4組なら、待ちがどんなのでも平和だよ」- そう言われてまず「ぴんふさんぷーこー、はちはじゅうろく」を目指すのではないか。
三色三歩高の特殊性は、もう1つの順子によって成立する役が自在に変化することだ。 たとえば 123m234p34sXX という聴牌は、5s で三色三歩高が成立するが、2s では点数がたりない片和了りだ。 しかし、ここに第4の順子を加えると状況は一変する。 それがもし 234m であれば三色三同順、345p であればまた別の三色三歩高が成立するのだ。
このような、両面待ち (以上) で、和了牌によって別々の軸手役*1が成立して和了となるような聴牌を「両和了り」ということがある。 三色三歩高を狙う際には、この「両和了り」聴牌を目指して打牌することになる。
これに関してきんとん氏はすでに非常に多くの成果を出され、その一部を公開されているが、私はプログラムによる力技で全解を求めてみようと思う。
*1 軸手役: 和了が8点縛りを満たす中心となる手役の意。6点以上の手役を指す。
More...2009年02月10日
正規表現
Python CGI で大会集計をやるので、開発のついでに要素を切り分けてここに書いておこうと思う。第1回は正規表現。
Python を含めた多くのプログラミング言語で強力な文字列検索/置換機能を提供しているのが、正規表現。天鳳のログにマッチするもの。
- 四麻
- \d{2}:(\d{2} \| ){2}四(般|上|特|鳳)(東|南)(喰|−)(−|赤) \|( [^ ]+\((\+|-)?\d+\)){4}
- 三麻
- \d{2}:(\d{2} \| ){2}三(般|上|特|鳳)(東|南)(喰|−)(−|赤) \|( [^ ]+\((\+|-)?\d+\)){3}
正規表現は、初めて見たときは暗号にしか見えないが、初めて理解したときは感動するものである。
2008年08月16日
Real Mahjong Rating & Ranking System
研究やプログラムに没頭しているときほど、逆に記事を書く暇もないとは皮肉なことである。 今回は後者、ちょうど佳境に入ったところだ。
プログラムの内容であるが、麻雀研究と直接は関係がない。 麻雀店用の成績管理システムで、いくつかの評価基準をもとにしたレーティング (詳しくは 麻雀評価論 (2) を参照のこと) などを出せるようにする予定である。
ネット麻雀のように「正しい試合結果が即時に入力される」のではないのが、少し難しかったところだ。 入力ミスなどを修正できる必要があるため、入力と同時にレーティング値の更新などをしてはいけないのだ。 そこでゲームデータを「未確定」と「確定」の2つに分け、ある程度「未確定」データが集まったら手動 (といっても、もちろんワンクリックである) で「確定」の操作をすることによってレーティング変化を追い、ランキングや個人戦績を更新することにした。
現時点で、未確定ゲームデータに関する入力 / 修正などの操作を実装が完了した。 また、成績出力用の XSL がほぼ完成しているので、残っているのはメインの「確定」「出力」となる。
# 発注元以外でも、使ってみたいという麻雀店があればメールで連絡いただきたい。 発注元である程度動くようになった時点で返答いたす。
2008年04月05日
天鳳におけるデータ麻雀 (1)
マイミクシィのマイミクシィのマイミクシィのある方が書いた日記で「天鳳の牌譜解析プログラム ver0.1.17」というスクリーンショットを見つけた。 東風荘においては、とつげき東北氏による「できすぎくん」が広く使われているが、それに近い機能をもっているようだ。
しかし検索してもダウンロードできるところが見つからない。どういうわけなのだろうか。 有料版天鳳の付属スクリプトが短期間のうちにここまで進化したとは考えにくいことから、有志の方が出力ファイルを読んでデータを吐くプログラムを作ったのだと思うのだが。
これを見て、特上に行って打ちたいという気持ちが少し出てきた。
まずはツールについての情報を知りたい。何か知っている方はコメントしていただければ幸いである。