まっすぐな引き分けのためにポーカーハンドをテストするためのアルゴリズム(4からストレート)?

StackOverflow https://stackoverflow.com/questions/4040004

質問

私は楽しみのためにポーカー評価ライブラリを書くという苦しみにあり、特定のカードのドロー(オープンエンド、ガットショット)のテスト機能を追加したいと考えています。

このための「最先端」が何なのか疑問に思っているだけですか?私はメモリフットプリントを合理的に保とうとしているので、検索テーブルを使用するというアイデアはうまくいきませんが、必要な悪である可能性があります。

私の現在の計画は次の方針に沿っています。

  • セット内のすべてのカードのランクから最低ランクを減算します。
  • 特定のシーケンスIE:0,1,2,3または1,2,3,4(OESDSの場合)が修正コレクションのサブセットであるかどうかを確認してください。

7枚のカードまたは9枚のカードセットが私のアプローチを使用して物事を止めるために粉砕するので、私はより複雑な賢さをやりたいと思っています。

入力および/またはより良いアイデアは大歓迎です。

役に立ちましたか?

解決

おそらく各カードランクにビットマスクを割り当てる最速のアプローチ(例:Deuce = 1、3 = 2、4 = 4、5 = 8、6 = 16、7 = 32、8 = 64、Nine = 128、10 = 256 、Jack = 512、Queen = 1024、King = 2048、Ace = 4096)、または手のすべてのカードのマスク値を一緒に。次に、8192エレメントルックアップテーブルを使用して、手がストレート、オープンエンダー、ガットショット、または重要なものであるかどうかを示します(実行時間に影響を与えることなく、さまざまな種類のバックドアストレートドローも含めることができます)。

ちなみに、異なるビットマスク値を使用して、2つの類似の、3つのリンゴなどの他の有用な手をすばやく検出できます。上記のマスク(したがって、deuce = 1、3 = 8など。Ace= 2^36まで)を追加し、カードの値を一緒に追加します。結果が、0444444444444444(Octal)でゼロである場合、手は4種類のものです。それ以外の場合は、Plus 01111111111111を追加し、044444444444444を使用すると、ゼロではない場合、手は3つの系またはフルハウスです。それ以外の場合、結果が、02222222222222で「ゼロではない場合、手はペアまたは2ペアです。手に2つ以上のペアが含まれているかどうかを確認するには、「02222222222222のハンドバリューが含まれており、その値を保存します。 1、および 'および'の結果を保存した値で減算します。ゼロ以外の場合、手には少なくとも2つのペアが含まれています(したがって、3つのリンゴが含まれている場合、それは完全な家です。そうでなければ2ペアです)。

別れのメモとして、ストレートをチェックするために行われた計算により、カードのランクがいくつあるかを迅速に判断できます。 nカードとn異なるランクがある場合、手にはペア以下を含めることはできません(ただし、ストレートまたはフラッシュが含まれている場合があります)。 N-1異なるランクがある場合、手には正確に1つのペアが含まれています。異なるランクが少ない場合のみ、より洗練されたロジックを使用する必要があります(N-2がある場合、手は2ペアまたは3つの標本である可能性があります。N-3以下の場合、手は存在する可能性があります。 3ペア "(2ペアとしてスコア)、フルハウス、または4枚のリンキング)。

もう1つは、8192エレメントルックアップテーブルを管理できない場合は、512エレメントルックアップテーブルを使用できます。上記のようにビットマスクを計算してから、配列[BitMask&511]と配列[ビットマスク>> 4]のルックアップを実行します。正当なストレートまたはドローは、1つまたは他のルックアップに登録されます。これは異なるランクの数を直接与えないことに注意してください(カード6〜10は両方のルックアップでカウントされるため)が、同じアレイ(配列[BitMask >> 9]を使用)をもう1つ同じ配列を検索します。ジャックだけがカウントされますエースを介して。

他のヒント

メモリフットプリントを可能な限り小さく保ちたいと言っていますが、ポーカーハンド評価者で使用したメモリ効率の高いルックアップテーブルの最適化が1つあり、自分で使用しました。重いポーカーシミュレーションを行っていて、可能な限り最高のパフォーマンスが必要な場合は、これを検討したい場合があります。この場合は、まっすぐな引き分けのテストはそれほど高価な操作ではないため、違いはそれほど大きくありませんが、ポーカープログラミングのほぼあらゆる種類のハンド評価に同じ原則を使用できます。

アイデアは、次の特性を持つ一種のハッシュ関数を作成するということです。
1)カードランクの異なるセットごとに一意の値を計算します
2)カードの順序に依存しないという意味で対称です
これの目的は、ルックアップテーブルで必要な要素の数を減らすことです。

これを行うためのきちんとした方法は、各ランクに素数を割り当てることです(2-> 2、3-> 3、4-> 5、5-> 7、6-> 11、7-> 13、8-> 17 、9-> 19、t-> 23、j-> 29、q-> 31、k-> 37、a-> 41)、そしてプライムの積を計算します。たとえば、カードが39TJQQの場合、ハッシュは36536259です。

ルックアップテーブルを作成するには、ランクの可能なすべての組み合わせを通過し、いくつかの単純なアルゴリズムを使用して、それらが直線的な引き分けを形成するかどうかを判断します。各組み合わせについて、ハッシュ値を計算し、結果をハッシュがハッシュであり、値がストレートドローチェックの結果であるマップに保存します。カードの最大数が少ない場合(4以下)、線形配列でさえ実現可能になる可能性があります。

ルックアップテーブルを使用するには、最初に特定のカードのハッシュを計算し、次にマップから対応する値を読み取ります。

これがC ++の例です。私はそれが正しく機能していることを保証するものではなく、hash_mapの代わりにソートされた配列とバイナリ検索を使用することにより、おそらく大いに最適化される可能性があります。 Hash_Mapは、この目的のためにちょっと遅いです。

#include <iostream>
#include <vector>
#include <hash_map>
#include <numeric>
using namespace std;

const int MAXCARDS = 9;
stdext::hash_map<long long, bool> lookup;

//"Hash function" that is unique for a each set of card ranks, and also
//symmetric so that the order of cards doesn't matter.
long long hash(const vector<int>& cards)
{
    static const int primes[52] = {
        2,3,5,7,11,13,17,19,23,29,31,37,41,
        2,3,5,7,11,13,17,19,23,29,31,37,41,
        2,3,5,7,11,13,17,19,23,29,31,37,41,
        2,3,5,7,11,13,17,19,23,29,31,37,41
    };

    long long res=1;
    for(vector<int>::const_iterator i=cards.begin();i!=cards.end();i++)
        res *= primes[*i];
    return res;
}

//Tests whether there is a straight draw (assuming there is no
//straight). Only used for filling the lookup table.
bool is_draw_slow(const vector<int>& cards)
{
    int ranks[14];
    memset(ranks,0,14*sizeof(int));

    for(vector<int>::const_iterator i=cards.begin();i!=cards.end();i++)
        ranks[ *i % 13 + 1 ] = 1;
    ranks[0]=ranks[13]; //ace counts also as 1

    int count = ranks[0]+ranks[1]+ranks[2]+ranks[3];
    for(int i=0; i<=9; i++) {
        count += ranks[i+4];
        if(count==4)
            return true;
        count -= ranks[i];
    }

    return false;
};

void create_lookup_helper(vector<int>& cards, int idx)
{
    for(;cards[idx]<13;cards[idx]++) {
        if(idx==cards.size()-1)
            lookup[hash(cards)] = is_draw_slow(cards);
        else {
            cards[idx+1] = cards[idx];
            create_lookup_helper(cards,idx+1);
        }
    }
}

void create_lookup()
{
    for(int i=1;i<=MAXCARDS;i++) {
        vector<int> cards(i);
        create_lookup_helper(cards,0);
    }
}

//Test for a draw using the lookup table
bool is_draw(const vector<int>& cards)
{
    return lookup[hash(cards)];
};

int main(int argc, char* argv[])
{
    create_lookup();

    cout<<lookup.size()<<endl; //497419

    int cards1[] = {1,2,3,4};
    int cards2[] = {0,1,2,7,12};
    int cards3[] = {3,16,29,42,4,17,30,43};

    cout << is_draw(vector<int>(cards1,cards1+4)) <<endl; //true
    cout << is_draw(vector<int>(cards2,cards2+5)) <<endl; //true
    cout << is_draw(vector<int>(cards3,cards3+8)) <<endl; //false

}

これは素朴な解決策かもしれませんが、パフォーマンスの問題についてはわかりませんが、私はそれがうまくいくと確信しています。

カードが数値1〜13で表されると想定すると、4枚のカードが3または4の数値範囲(最高のカードランクから最低のランクまで)が含まれていて、複製が含まれていない場合、まっすぐに引き分けます。

3の範囲は、2,3,4,5の範囲が3の範囲で、重複が含まれていない、オープンエンドの引き分けがあることを意味します。

4つの範囲は、あなたがガットショットを持っていることを意味します(あなたが呼んだように)例えば、5,6,8,9の範囲は4つあり、重複は含まれていません。

更新:クリスチャン・マンのコメントによると...それはこれです:

まあ言ってみれば、 A として表されます 1. J 11、 Q 12など

loop through 1 to 13 as i
  if my cards already has this card i, then don't worry about this case, skip to next card
  for this card i, look to the left for number of consecutive cards there is
  same as above, but look to the right
  if count_left_consecutive + count_right_consecutive == 4, then found case

左の連続カードと右連続カードのカウントを探すために機能を定義する必要があります...また、正しい連続しているとき、その後、ケースを処理する必要があります。 K, 、 A 連続しています。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top