麻雀で天和に遭遇する確率をシミュレート@C++
ランダムに牌を選択し、和了の形ができているかどうかチェックするプログラム。
10億回まわしてみた。
ソース
#include <cstdio> #include <iostream> #include <vector> #include <algorithm> using namespace std; const char* tile_names[] = { "m1","m2","m3","m4","m5","m6","m7","m8","m9", "p1","p2","p3","p4","p5","p6","p7","p8","p9", "s1","s2","s3","s4","s5","s6","s7","s8","s9", "wa","so","we","no","wt","gt","rt"}; unsigned long xor128(){ static unsigned long x=123456789,y=362436069,z=521288629,w=88675123; unsigned long t; t=(x^(x<<11));x=y;y=z;z=w; return( w=(w^(w>>19))^(t^(t>>8)) ); } void new_hand(vector<int> &hand) { int l = 0; while(l < 14) { int new_til_num = ((unsigned int)xor128())%136; bool flag = true; for(int i=0;i<l;i++) { if(new_til_num == hand.at(i)) { flag = false; } } if(flag) { hand.at(l) = new_til_num; l++; } } for(int i=0;i<14;i++) { hand.at(i) = hand.at(i) % 34; } sort(hand.begin(),hand.end()); return; } void print_hand(vector<int> &hand) { for(int i=0;i<14;i++) { cout << tile_names[hand.at(i)] << ", "; } cout << endl; } int orphans[] = {-1,0,8,9,17,18,26,27,28,29,30,31,32,33,-1}; bool is_thirteen_orphans(vector<int> &hand) { int bindex = 0; int eindex = 13; while(hand.at(bindex) == orphans[bindex+1]) { bindex++; } while(hand.at(eindex) == orphans[eindex]) { eindex--; } return bindex == eindex + 1; } bool is_seven_pairs(vector<int> &hand) { for(int i=0;i<7;i++) { if(hand.at(i*2) != hand.at(i*2+1)) { return false; } } return true; } bool stdhora_chk(vector<int> &hist, bool on_shu, bool on_head) { if(on_head) { bool result = false; for(unsigned int i=0;i<hist.size();i++) { if(hist.at(i)>=2) { hist.at(i)-=2; result = result || stdhora_chk(hist, on_shu, false); hist.at(i)+=2; } } return result; } else { vector<int> histcp = hist; for(unsigned int i=0;i<histcp.size();i++) { if(histcp.at(i)>=3) { histcp.at(i)-=3; } while(histcp.at(i)>0) { if(!on_shu)return false; if(i+2>=histcp.size())return false; histcp.at(i)--; histcp.at(i+1)--; histcp.at(i+2)--; if(histcp.at(i+1)<0)return false; if(histcp.at(i+2)<0)return false; } } return true; } } bool stdhora_check(vector<int> &hand, int offset, int length, int bas, int siz, bool on_shu, bool on_head) { vector<int> hist(siz); for(int i=offset;i<offset+length;i++) { hist.at(hand.at(i)-bas)++; } return stdhora_chk(hist, on_shu, on_head); } bool is_standard_hora(vector<int> &hand) { int b_m = 0; int m_p = lower_bound(hand.begin(), hand.end(), 9) - hand.begin(); int p_s = lower_bound(hand.begin(), hand.end(), 18) - hand.begin(); int s_t = lower_bound(hand.begin(), hand.end(), 27) - hand.begin(); int t_e = hand.size(); int mlen = m_p - b_m; int plen = p_s - m_p; int slen = s_t - p_s; int tlen = t_e - s_t; if(mlen%3==2 && plen%3==0 && slen%3==0 && tlen%3==0) { return stdhora_check(hand, b_m, mlen, 0, 9, true, true) && stdhora_check(hand, m_p, plen, 9, 9, true, false) && stdhora_check(hand, p_s, slen, 18, 9, true, false) && stdhora_check(hand, s_t, tlen, 27, 7, false, false); } else if(mlen%3==0 && plen%3==2 && slen%3==0 && tlen%3==0) { return stdhora_check(hand, b_m, mlen, 0, 9, true, false) && stdhora_check(hand, m_p, plen, 9, 9, true, true) && stdhora_check(hand, p_s, slen, 18, 9, true, false) && stdhora_check(hand, s_t, tlen, 27, 7, false, false); } else if(mlen%3==0 && plen%3==0 && slen%3==2 && tlen%3==0) { return stdhora_check(hand, b_m, mlen, 0, 9, true, false) && stdhora_check(hand, m_p, plen, 9, 9, true, false) && stdhora_check(hand, p_s, slen, 18, 9, true, true) && stdhora_check(hand, s_t, tlen, 27, 7, false, false); } else if(mlen%3==0 && plen%3==0 && slen%3==0 && tlen%3==2) { return stdhora_check(hand, b_m, mlen, 0, 9, true, false) && stdhora_check(hand, m_p, plen, 9, 9, true, false) && stdhora_check(hand, p_s, slen, 18, 9, true, false) && stdhora_check(hand, s_t, tlen, 27, 7, false, true); } else { return false; } return false; } int main(int argc, char *argv[], char *envp[]) { vector<int> hand(14); /*{ //int hand_lit[] = {0,8,9,17,18,26,27,27,28,29,30,31,32,33}; int hand_lit[] = {0,1,2,13,13,13,15,15,15,21,22,23,31,31}; copy(&hand_lit[0],&hand_lit[14],hand.begin()); print_hand(hand); cout << is_standard_hora(hand) << endl; return 0; }*/ int output_period = 10000000; int calc_times = 100; int max_sample_size = output_period * calc_times; int count_thirteen_orphans = 0; int count_seven_pairs = 0; int count_standard_hora = 0; int count_all = 0; for(int i=0;i<max_sample_size;i++) { new_hand(hand); //print_hand(hand); if(is_thirteen_orphans(hand)) { count_thirteen_orphans++; count_all++; } if(is_seven_pairs(hand)) { count_seven_pairs++; count_all++; if(is_standard_hora(hand)) { count_standard_hora++; } } else { if(is_standard_hora(hand)) { count_standard_hora++; count_all++; } } int sample_size = i+1; if(sample_size % output_period == 0) { cout << "thirteen orphans: " << count_thirteen_orphans << "/" << sample_size << " = " << ((double)count_thirteen_orphans/sample_size) << endl; cout << " seven pairs: " << count_seven_pairs << "/" << sample_size << " = " << ((double)count_seven_pairs/sample_size) << endl; cout << " standard hora: " << count_standard_hora << "/" << sample_size << " = " << ((double)count_standard_hora/sample_size) << endl; cout << " all: " << count_all << "/" << sample_size << " = " << ((double)count_all/sample_size) << endl; } } return 0; }
結果
thirteen orphans: 0/10000000 = 0 seven pairs: 8/10000000 = 8e-07 standard hora: 27/10000000 = 2.7e-06 all: 35/10000000 = 3.5e-06 thirteen orphans: 0/20000000 = 0 seven pairs: 10/20000000 = 5e-07 standard hora: 57/20000000 = 2.85e-06 all: 67/20000000 = 3.35e-06 thirteen orphans: 0/30000000 = 0 seven pairs: 11/30000000 = 3.66667e-07 standard hora: 75/30000000 = 2.5e-06 all: 86/30000000 = 2.86667e-06 (中略) thirteen orphans: 0/980000000 = 0 seven pairs: 367/980000000 = 3.7449e-07 standard hora: 2532/980000000 = 2.58367e-06 all: 2898/980000000 = 2.95714e-06 thirteen orphans: 0/990000000 = 0 seven pairs: 373/990000000 = 3.76768e-07 standard hora: 2556/990000000 = 2.58182e-06 all: 2928/990000000 = 2.95758e-06 thirteen orphans: 0/1000000000 = 0 seven pairs: 376/1000000000 = 3.76e-07 standard hora: 2582/1000000000 = 2.582e-06 all: 2957/1000000000 = 2.957e-06
既に計算されている結果によると、遭遇確率は約0.000003025なので、若干低く見積もってしまったようだ。
あと、国士無双天和の確率は約0.0000000003なので、10億回の計算ではぎりぎり出現しない程度である。
それと、この計算では、国士無双天和+七対子天和+一般の形の天和と、全体の天和回数に1だけずれがある。これは、七対子でかつ一般ともとれる形(大車輪のような)があると考えられる。
ソースの説明
136枚の牌からの選択は普通にランダムに行っている。ただし、疑似乱数として使ったのはxorshift法である。特に理由はない。
選んだ配牌はソートしてある。そのほうが便利だからである。
国士無双の判定は、前と後ろから単純に照合するだけでよい。
七対子の判定は、2個ずつ判定すればよい。
問題は、一般のあがりの形である。
まず、4種類のセクション(萬子、筒子、索子、字牌)にわけ、セクションの大きさを3で割った余りに応じて処理を分岐する。0なら頭のないセクション、2なら頭のあるセクション。
まず、それぞれ、牌リストから、ヒストグラムへ変換する。
そして、頭が存在するセクションは、頭を走査し、頭なしの検索関数を再帰呼び出しする。
頭が存在しないセクションは、下から走査し、3つ以上なら刻子、そうでなければ順子として取り除くことで確認する。途中で余りが出た時点でアウト。
最後にすべてのセクションの結果を論理積で結合すればいい。
ここまで書いてミスに気づいた
槓子を含む七対子を和了の形として認めてしまった!
まあ、いいか。書き直すのも面倒くさいし。
まとめ
TopCoderか何かの問題として出しても遜色ない面倒くささであった。