稀にしか書かない掘木です。

タイトル通りです。はい。
窓の杜の週末ゲームの項でHSPコンテスト2013の作品の中から数点紹介とのようです。
紹介はこちら

ナ、ナンダッテー
思わず内容4回くらい見直しましたよええ。
選んで下さった関係者方には感謝しきれないです・・・。感激のあまり目から水が(´;ω;)

まさか最終選考の前にこんなサプライズがあるなんて思ってなかった。。。不意打ちにもほどが・・・。




あと、先日zzzさんに紹介ページを拵えてもらったLnks5.02ですが・・・

地味に開発が進んで5.04になりました。(5.03はどこかへ行ってしまった模様)
じきに5.04が落とせるようになるはずです。HSPコンテストページ側も更新しておかねば・・・。

大きな更新内容としては
・時計を非表示にできるようになった。
・マウスの動きでランチャー窓が復帰できる機能が付いた

の2点でしょうか。


以下は開発の現状になります。
... 続きを読む
 zzzです。

 掘木さんが作った時計一体型ランチャーソフト「Lnks502」を公開しました。DLはこちらから。

http://mathyippee.web.fc2.com/lnks/lnks.html

 掘木さんが作ったものということで、我々の中で最も精度が高く妥協を許さない安定したランチャーとなっています(泥縄のように作るのは私です。。)
 exeの登録やフォルダ登録はもちろん完備、時計も邪魔にならない程度の大きさに変更可能でアラーム機能があり、シャットダウンなども行えます。
 何より安定性が高いです。頑強に作ってあるので大抵の動作は大丈夫です。掘木さんはホントこの点が違います。
 時計はアナログ、デジタル時計を採用しております。時計部分にマウスカーソルを当てればランチャーが起動します。
 個人的にデジタル時計がいい感じです。シンプルながらスタイリッシュな気がします。

 ランチャーソフトを使ったことがない方にはぜひお勧めできる代物です。色々な機能があるのでDL後、manual.htmlを読んでいただければ幸いです。
2013.11.15 艦これE4
KOMARIです。

艦これのE4、無事攻略完了しました!

kmr037.jpg

詳細なデータが見たい方は続きでも見やがって下さい!
... 続きを読む
 zzzです。前回の記事の続きです。まず前回の記事を読んでください。
 さて、ポーカーの役判定が出来ました。が、本当に正しいかどうか、デバッグついでにポーカーの役が出る確率を求めてみましょう。まず理論的に出します。が私は不真面目なので、自力で計算せずこのPDFを見ました。ジョーカー抜きの、52枚でやる通常のトランプです。
 ただこのPDFも若干間違っており、このPDFではA2345もストレート扱いですが、それでもストレートフラッシュの数は32となります。ごめんなさい。このPDFは正しいです。A2345がストレートなら、ストレートフラッシュの数は36です。

 次は先ほど作った役判定を用いて確率を計算します。52枚のトランプから5枚抜いてきて、それを手札にすればよいです。組み合わせのアルゴリズムについては省略させていただきます。でそれを実行した結果がこちらです。

poker_test

 pdfと照らし合わせると……

poker_pdf

 合ってます。ストレート系列に関してあっていないのですが、この論文とストレートに関しては定義が違うからです。

 これでこのソースはほぼ大丈夫だと思いました。ついでにジョーカー有りの53枚での確率も貼っておきます。

poker_test2

 なんとなく大丈夫そうです。配布しているソースコードはこれです。
 これにてポーカーのアルゴリズムを終了します。前回よりはマシになったのではないでしょうか。
 zzzです。 
 改めてポーカーの役判定を考えることがあったので、昔のソースコードを読んでいたのですが、あまりにひどすぎました。最初までは合ってるのですが(というか最初のほうのアルゴリズムは掘木さんが考えたものを流用している)その後がひどすぎます。ということで再度考え直します。かなり前に書いたものと似通っていますが、御了承ください。

 ソースコードのほうを先に公開しておきます。こちらです。

 さて、まずポーカーといっても、いろいろルールがあります。なのでルールをまず決定します。とはいっても何がスタンダートなのか分からないので、汎用性があるように決めました。

・手札は5枚以下である。5枚を下回った場合、残った手札で役を決める(7ハンドポーカーなどに対応可)
・同一の数値、同一のマークが入っていてもよい(クローバーの2が5枚の手札でもいいし、ジョーカー5枚でもよい)
・ジョーカーはワイルドカード扱いである。最も高い役となるカードに化ける
A2345はストレートと見做さずハイカードとする
マークに強弱は存在しない
手札がジョーカーのみの場合、そのジョーカーはAとなる(最も高い役となるカードに化けるというルールを遵守)
手札0枚はハイカードにも劣り、最弱となる


 概ねこのルールで網羅しきれると思いますが、いわゆるバックストレートを実装したければソースコードに若干の修正が必要です。ご注意ください。尤もすぐに出来ると思いますが。またマークに強弱が存在しないので、引き分けとなる可能性があります。
 また今回は文章の削減を出来るだけ行いたかったので、C++の標準ライブラリのSTLのコンテナ、アルゴリズムを多用しています。ある程度説明はしますが、まったく無知である方には厳しいかもしれませんので、K. Y様C++(標準ライブラリ)を見ればすぐに分かります。

 さて、まずは手札を表すものを用意します。今回手札は5枚以下となるため、サイズ5のunsigned int型配列を用意すればいい……のですが、それでもメンドくさいので、vectorクラスに手札を格納します。vectorとは動的配列のようなもので、通常の配列とは違い、自らサイズを変えてくれるものです。

 std::vector hand;

 が手札です。後はこのクラスに数値を代入するだけです。このhandのサイズで手札枚数が分かります。具体的には

 hand.size()

 で手札枚数が返ります。
 次に数値とカードの対応を行います。以下のようにやると上手くいくでしょう。

0:ジョーカー
1~13:スペードの2からA
15~27:クローバーの2からA
29~41:ハートの2からA
43~55:ダイヤの2からA

 マークについては好きに割り当ててよいでしょうが、赤か黒か判断したいときもあるかもしれませんので、このようにすればよいでしょう。
 数値の1をスペードの2に対応させていますが、これは2はポーカーにおいて最弱であるためです。のちのち役判定で生きてくるため、こうしています。そして数値の2はスペードの3に、……、数値の12はスペードのKに、数値の13はスペードのAに対応させておきます。

 このように割り振ったのは、カードのナンバーとマークをすぐに取り出せるからです。例えばhand[0]のナンバーは

 hand[0] % 14

 で分かり、マークは

 hand[0] / 14

 で分かります。ジョーカーはナンバーが0のときです。14刻みで割り振っていくと上手くいくと思います。

 次に、役について説明します。ポーカーの役は11種類ありますので、enum型にしておきます。ただ手札が0枚のときの最弱役も必要なため結局12種類としており、それをNONEという役に当てはめています。

enum POKER_HANDS{
    NONE, HIGH_CARD, ONE_PAIR, TWO_PAIR, THREE_CARD,STRAIGHT, FLUSH, FULL_HOUSE,
    FOUR_CARD, STRAIGHT_FLUSH, ROYAL_STRAIGHT, FIVE_CARD,
};

 また、役を決定するためには、数値も同時に保存しなければなりません。なぜなら、同一役のとき、手札の数値を見ないといけないからです。例えば、ともにファイブカードだったとすると、誰がどのナンバーのファイブカードかどうかが重要です。それによって勝敗が変わりますので。
 また例えば互いにスリーカードだった場合は、まず3枚の組となっているナンバー同士を比較し、それからばらばらのナンバー同士を比較しあいます。実に面倒くさそうです。
 がこれはビット演算を駆使し、ビットに情報を格納していけば解決できそうです。

 つまり4ビットずつ区切り、20~23ビット目に役を代入し, 19~16ビット目に役の中での最強のカードのナンバーを代入し、15~12ビット目に役の中で2番目に強いカードのナンバーを代入し, 11~8ビット目に役の中で3番目に強いカードのナンバーを代入し、7~4ビット目に役の中で4番目に強いカードのナンバーを代入し、3~0ビット目に最も弱いカードのナンバーを代入します。

 例えば

 まったく意味が分からないかと思われるので例をだしましょう。持ちカードが

 2, 3, 4, 5, 6

 だとします。この場合ストレートフラッシュとなっているためpoker_handsの中身を

 0x965432

 とする。ということになります。

 何が利点かといいますと、役の比較をする際大小関係だけで勝ち負けが分かります。
 例えば

 0x965432 対 0x9A9876

 の場合役は同じですが、最強の数値が6 < Aなので、⇒の勝ちです。これは

 0x965432 < 0x9A9876

 に相当しています。引き分けは==で分かります。

 さて、これらは最低24bit必要なので、unsigned longあたりにしておけば十分でしょう(intでもほとんどのPCで大丈夫だと思いますが)
 ここで、このルールに従った数値を出力してくれる関数

unsigned long pokerhand(const std::vector& hand);

 を作りましたのでそのソースコードを貼ります。引数に手札を入れることを想定しています。

unsigned long pokerhand(const vector<int>& hand)
{
    int i, cnt, size = hand.size(), numberofjoker = 0;
    bool isflush = false, isstraight = false, isroyal = false;
    vector<int> num, mark;
    vector<unsigned short> pair;
    if(hand.empty()) return 0;
    for(i = 0; i < size; ++i){
        if(hand[i] % 14) num.push_back(hand[i]  % 14);
    }
    for(i = 0; i < size; ++i) {
        if(hand[i] % 14) mark.push_back(hand[i]  / 14);
    }
    sort(num.begin(), num.end());
    sort(mark.begin(), mark.end());
    for(i = 0; i < size; ++i){
        if(hand[i] % 14 == 0) ++numberofjoker;
    }
    vector<int>::iterator start, end = num.end();
    //同一数値の数を求める
    for(start = num.begin(); start != end; ){
        cnt = count(start, end, *start);
        pair.push_back(((cnt & 0xFF) << 8) | *start);
        for(i = 0; i < cnt; ++i) ++start;
    }
    //ストレートの確認, pairのsort前に行う
    for(i = 0; i < pair.size(); ++i){
        if( (pair[i] & 0xFF) - (pair[0] & 0xFF) > 4 ) break;
    }
    if(i + numberofjoker == 5){
        isstraight = true;
        if(!pair.empty() && (pair[0] & 0xFF) >= 9) isroyal = true;
    }
    sort(pair.begin(), pair.end(), greater<unsigned short>());
    //フラッシュの確認
    for(i = 0; i < mark.size(); ++i){
        if(mark[i] != mark[0]) break;
    }
    if(i + numberofjoker == 5) isflush = true;
    if(pair.empty()) pair.push_back(0);    //pairが空だと面倒
    //上で整理した情報から役が何なのかを判定
    if((pair[0] >> 8) + numberofjoker >= 5)
        return CreatePokerHandlong(FIVE_CARD, pair, numberofjoker);
    else if(isroyal && isflush)
        return CreatePokerHandlong(ROYAL_STRAIGHT, pair, numberofjoker);
    else if(isstraight && isflush)
        return CreatePokerHandlong(STRAIGHT_FLUSH, pair, numberofjoker);       
    else if((pair[0] >> 8) + numberofjoker >= 4)
        return CreatePokerHandlong(FOUR_CARD, pair, numberofjoker);
    else if(pair.size() >= 2 && ((pair[0] >> 8) + numberofjoker) >= 3 && (pair[1] >> 8) >= 2)
        return CreatePokerHandlong(FULL_HOUSE, pair, numberofjoker);
    else if(isflush)
        return CreatePokerHandlong(FLUSH, pair, numberofjoker);
    else if(isstraight)
        return CreatePokerHandlong(STRAIGHT, pair, numberofjoker);
    else if((pair[0] >> 8) + numberofjoker >= 3)
        return CreatePokerHandlong(THREE_CARD, pair, numberofjoker);
    else if(pair.size() >= 2 && (pair[0] >> 8) >= 2 && (pair[1] >> 8) >= 2)
        return CreatePokerHandlong(TWO_PAIR, pair, numberofjoker);
    else if((pair[0] >> 8) + numberofjoker >= 2)
        return CreatePokerHandlong(ONE_PAIR, pair, numberofjoker);
    else return CreatePokerHandlong(HIGH_CARD, pair, numberofjoker);
}

unsigned long CreatePokerHandlong(POKER_HANDS p, vector<unsigned short>& pair, int numberofjoker)
{
    int max_number, i;
    unsigned long t;
    t = p << 20;
    if(pair[0] == 0){    //全てがジョーカーで構成されている場合エースで埋める
        for(int i = 0; i < numberofjoker; ++i) t |=  13 << (16 - i * 4);
    }
    else if(p == ROYAL_STRAIGHT || p == STRAIGHT_FLUSH || p == STRAIGHT){
        if((max_number = (*pair.rbegin() & 0xFF) + 4) > 13) max_number = 13;
        for(i = 0; i < 5; ++i) t |= (max_number - i) << (16 - i * 4);
    }
    else if(p == FLUSH){
        for(i = 0; i < numberofjoker; ++i) t |=  13 << (16 - i * 4);
        for(; i < pair.size(); ++i) t |= (pair[i - numberofjoker] & 0xFF) << (16 - i * 4);
    }
    else for(int i = 0; i < pair.size(); ++i) t |= (pair[i] & 0xFF) << (16 - i * 4);
    return t;
}

 結構長いですがやっていることは簡単ですのでご安心ください。

 手札が0枚だと0を返します(手札がないならハイカードにも劣る)

 それから手札から、ナンバー, マークのvectorを生成し、STLのアルゴリズムにある、sortを使います。ソートをしておかないとかなり面倒だからです。またこれは昇順(最も大きい数字が0番目に入り、以下大きい順に並ぶ)という点に注意してください。また、num, markともにジョーカーは入っていません。特別にジョーカーだけカウントしています。
 
 ソートし終わった後は、同一ナンバーがいくつあるか計算します。それをpairに格納するのですが、その際第8~15番目のビットに同一ナンバーの数を、第0~7番目のbitにナンバーを格納します。例えばnumが

1, 1, 1, 2, 3

 となっていた場合、まずpairは一番左の数値をSTLのアルゴリズムにあるcountで、1の数値がいくつあるか求めます(ここでは3が返る)そしてpair[0]には

 0x0301

 となります。

 実はこのpairを降順で並び替えると、pair[0]のナンバーが役の中での最強のナンバーとなります。次に強いのはpair[1]、……と続きます。
 なぜならストレート系列とフラッシュ系列を除いて、先に比較しなければならないナンバーは、そろっている数が最も多いナンバーからです。そろっている数も同じなら、ナンバーを比較します。
 それはビット演算の大小関係で比較するといっぺんに出来ます。例えばnumが

 2, 2, 4, 5, 5

 になっていたとすると、ソート後のpairは

 0x0205, 0x0202, 0x0104

 となります。これは
・そろっている数が最も多いナンバーが強い
・そろっている数が同じならば、ナンバー自身の強さを見る
 ということを行っています。ですのでpairを見るだけで、ストレート系列とフラッシュ系列を除く役は決定できます。

 あとはストレートとフラッシュの比較を行っています。ストレートの比較は、pairの降順ソート前に行っています。ソート前のpairのナンバーが昇順になっているほうが都合がいいからです。また、同一の数値、同一のマークが入っていてもよい、というルールがあるため、やはりpairを見たほうが楽ということもあります。

 ストレートの判定法ですが、手札のナンバーの最大値 - 最小値が4以下ならストレート……と思いたかったのですが、同一の数値、同一のマークが入っていてもよい、手札枚数は5枚以下、というルールがあるため、どうもダメなので、1枚ずつ、ナンバーと最小値の差が4以下になった数を計算し、その数 + ジョーカー枚数が5ならばストレート、という判定を取っています。ジョーカーは空いている穴を埋める感じです。
 またロイヤルストレートフラッシュになるかどうかも判定しておきます。手札のナンバーの最小値が9以上のストレートならばロイヤルストレートフラッシュになる可能性があります(あとはフラッシュかどうかで決まる)。isstraightがtrueなら連番になっており、isroyalがtrueなら10 J Q K Aの手札となります(マークが一致しているかは不明。またジョーカーが成り代わっている可能性あり)

 次にフラッシュの判定ですが、最も楽です。markに格納された数値のうち、1つでも数値が違っていればフラッシュではありません。ソースコードでは1つずつチェックして、またチェックを通った数 + jokerが5ならばフラッシュです。isflushがtrueならマークが全一致です。

 上までの情報から全ての役を求めることができます。がpairが空の場合(すなわち手札が全てジョーカーで構成されているとき)少し面倒なのでpairに0を放り込んでおきます。これで手札が全てジョーカーかどうか判定します。

 先ほど解説したとおり、pair[0] >> 8が手札の中で最も大きい揃っている数です。これにjokerの数を足した値が5ならファイブカードです。以下4ならフォーカード、3ならスリーカード、2ならツーペアなのですが、もしかするともっと強い役になれるかもしれませんので、役の強さ順に他の役も判定しなければなりません。 
 ロイヤルストレートフラッシュはisroyal && isflushが真のとき、ストレートフラッシュはisstraight && isflushが真のとき、フラッシュは上を判定してから、isflushが真のとき、ストレートなのは上を判定してからisstraightが真のときです。この順番に比較していかないとダメです。
 あとはフルハウスですが、手札の中で最も大きい、揃っている数 + jokerの数が3以上、手札の中で2番目に大きい、揃っている数が2以上ならばフルハウスです。それを((pair[0] >> 8) + numberofjoker) >= 3 && (pair[1] >> 8) >= 2で表現してます。ただpair[1]が存在しない場合、つまりpairのサイズが1という可能性があるため、最初にpair.size() >= 2をつけています(pairのサイズが1ならフルハウスはありえない)
 ツーペアもほぼ同様です。ただツーペアは、手札にジョーカーが1枚でもあれば絶対に起こらないので、ジョーカーの数を足す必要はありません。

 以上の判定で、役が分かりました。ですが役を上手くunsigned long型に変えるのが面倒なので、関数CreatePokerHandlongに渡しています。

 pair[0] = 0のとき、手札は全てジョーカーで構成されています。このとき、全てをAに変えると最大の役になるので、ジョーカーはAとします。フラッシュのときも、Aに書き換えておきます。
 ストレート系列のときが少々面倒です。まず手札のナンバーの最小値を取ってきて、そこに5を足します。それが13以下なら手札の最大値となり、それが13を越えるなら、13が手札の最大値です。あとは最大値から順に構成すればよいです。
 あとはpairのときに説明したとおり、pairの0番目から順に入れていけばよいです。

 これにて役判定は終了です。

 さて、せっかくポーカーの役判定が出来たので、次はポーカーの役が出る確率を求めたいと思います。が予想外に文章が長くなったため、次回に持ち越します。