ポーカーボットの作成

資料は情報提供のみを目的としており、作者はポーカールームによるアカウントの閉鎖について責任を負いません。 国の法律では、ボットの作成と使用は禁止されていませんが、ポーカールームの規則に従って禁止されています。



この記事では、完全なソースコードではなく、理論的には何をどのように使用するか、およびいくつかの関数を取り上げます。 これに興味があるなら、あなたはすべてを単一の絵に集めて、あなた自身のものを書くことができないでしょう。 また、ゲームの戦略、用語や戦略、ルールについては教えません。インターネットで多くの情報を見つけることができます。



少し歴史。




彼は約5年前にプレーを開始し、その間に100万以上のハンドが獲得されました。 これは主にショートテーブル(テーブルで2〜6人)でのノーリミットホールデム(無制限のホールデム)で、HU NLホールデムのオマハでのプレイ経験があります。 ゲームは一種の趣味とリラクゼーションのようなものです。 そして、私の友人と一緒に、ボットを書くというアイデアが浮上しました。最初のアイデアは、リミットホールデムで書くことでした。 最初のバージョンはパターン認識を使用してニューラルネットワークで作成されたため、マップスクリーンショットから認識するようにボットに教えましたが、不正確な方法は自分で理解できますが、かなり良いトレーニングで98-99%の精度で認識しました。リソースを使用して、部屋の窓から直接、ゲーム統計の分析と収集のためのプログラムと連携して。



ポーカールームは眠りません。




ほとんどすべてのポーカールームには、何らかの保護とポーカーボット検出システムがあります。 それらのいくつかを検討してください。 長いゲーム-人が長すぎると不審になり、一部の部屋に質問のあるウィンドウが表示される場合があります。 保護として、長いゲームセッションを行いません。 テーブルでのアクション-ボタンを押したり、ウィンドウを選択したり、最小化されたウィンドウでマウスを動かさずにアクションを実行したりしないでください。 マウスのパスを少し非線形に設定して、ボタンのさまざまな場所をクリックすると、動きの速さも瞬間的ではなくなります。 実行中のプロセスとスクリーンショットのスキャン-上記の保護、最小化されたウィンドウで再生しない、ポーカーボットではなくボットプロセスを呼び出すなど、ボットウィンドウを画面上で最大化したままにせず、しばらくしてからプロセス名を変更する(たとえばx分後)新しいプロセス名でボットを再起動します)。



ポーカーボットスキーム。




スキームは3つの部分に分けることができます。



ブロック1-ポーカーをプレイするためのクライアントとの対話のブロック。

ブロック2-決定ブロック。

ブロック3は統計収集ブロックです。私の意見では、画面上の統計を無効にしながら、PokerTracker3などのサードパーティソフトウェアを使用する方が良いと考えています。

次に、各ブロックをより詳細に検討します。



ブロック1-ポーカーをプレイするためのクライアントとの対話のブロック。




このブロックは、ゲームテーブルに関する情報を収集して決定ブロックに転送するために使用されます。また、フォールド、レイズ、コール、オールインなどのアクションを実行する決定を含む応答を受信します。 次に、このブロックを検討します。 この部分の一部は、メインプログラムウィンドウとの対話です。制限の選択、プレイするテーブルの選択などのアクションです。これも大きな部分ですが、そこで止まりません。 すでにテーブルに含まれている部分をさらに詳しく検討してみましょう。 まず、開いているすべてのゲームテーブル(ウィンドウ)のハンドルを見つける必要があります。これは、EnumWindows関数を使用して行うことができます。

EnumWindows関数は、画面上のすべてのトップレベルウィンドウをリストし、各ウィンドウのハンドルをプログラムで定義されたコールバック関数に渡します。 EnumWindowsは、最後のトップレベルウィンドウがリストされるか、コールバック関数がFALSEを返すまでアクティブです。



構文:

BOOL EnumWindows ( WNDENUMPROC lpEnumFunc, //      LPARAM lParam //    );
      
      





パラメータ:

lpEnumFunc-プログラムによって定義されたコールバック関数を示します。 詳細については、EnumWindowsProcコールバック関数を参照してください。

lParam-コールバック関数に渡される32ビットプログラム定義値を設定します。

戻り値:関数が成功すると、ゼロ以外の値が返されます。 関数が失敗した場合、戻り値はゼロです。



関数が正常に機能した場合、ウィンドウ名GetWindowTextを取得し、ウィンドウが必要かどうかを分析するために分析した後、通常、ニックネーム、ポーカールームの名前、および制限がウィンドウタイトルに表示されます。 さらに、見つかったすべてのウィンドウのハンドルを保存しても問題はありません。再使用せずに使用するには、新しいウィンドウを開いた場合にのみ検索を実行する必要があります。



ゲームウィンドウへのハンドルができたので、テーブル情報を引き出すことができます。 情報のほとんどは、ディーラーウィンドウに保存されます。 それから、テーブルのすべての参加者、入った人、テーブルを去った人、どんなアクションをした人、テーブル上のカードとそのカードを見つけることができます。 このためにはテキストパーサーが必要です。これを書く方法は教えません。これは別のトピックです。主なことは、明確なJです。しかし、パーサーの前にまずウィンドウ要素を見つけ、その中からディーラーウィンドウを見つける必要があります。 多くの場合、ディーラーウィンドウはInternet Explorer_Serverから派生したクラスであるため、EnumChildWindows関数を使用してからGetClassNameを使用します。



EnumChildWindowsは、特定の親ウィンドウに属する子ウィンドウをリストし、各子ウィンドウのハンドルをプログラムで定義されたコールバック関数に渡します。 EnumChildWindows関数は、最後の子ウィンドウがリストされるか、コールバック関数がFALSEを返すまで機能します。



構文



 BOOL EnumChildWindows ( HWND hWndParent, //    WNDENUMPROC lpEnumFunc, //      LPARAM lParam // ,   );
      
      







パラメータ:

hWndParent-子ウィンドウを列挙する親ウィンドウを識別します。

lpEnumFunc-プログラムによって定義されたコールバック関数を示します。 コールバック関数の詳細については、EnumChildProcコールバック関数を参照してください。

lParam-コールバック関数に渡される32ビットプログラム定義値を設定します。

戻り値:関数が成功すると、ゼロ以外の値が返されます。 関数が失敗した場合、戻り値はゼロです。



これで、テキストの解析を開始できます。 現時点で利用可能なボタンに基づいて、進捗状況を分析できます。 これは、リソースと、このウィンドウの画面上のポイントの両方を使用して実行できます。 さらに、移動する場合、既知のデータを決定ブロックに転送します。 応答を受信すると、適切な措置を講じます。 この部分では、ボタンの押し方はまだ検討されていませんが、自分でマスターすると思います。



ブロック2-決定ブロック




ボット全体の中心であるこのブロックは、 彼がゲームでどれだけ成功するかは、彼がどれだけ正しい決定をするかにかかっています。 2010年6月6日付のHacker 137ジャーナルでは、完全に確率論に基づく例が考えられており、これは良いことですが、十分に信頼できる結果を得るためには、多くの反復を行う必要があります。 対戦相手と自信を持ってプレイするには、彼の範囲とプレイ方法を知る必要があります(この作品は統計に関連しています)。また、持っている金額、テーブルにいるプレイヤーの数、ゲーム戦略自体に依存する特定のゲーム戦略があります。 ハンドの開始範囲はこのすべてに依存し、その後のストリートでのアクション、たとえばショートスタック戦略(SSS)でプレイしている人は、リバーでほとんど動きません(テーブルに5枚のカードが配置されている場合)。 この時までに、彼はしばしばすでにアリンにいた(オールインになった)。 状況の評価は、アウトの計算でも行われます。アウトは、後続のストリートでの位置を改善するカードの数であり、そこからストレートドロー、ストレートドロー、ストレートドローなどの組み合わせが行われます。 引き分けも組み合わせであり、アウツの数に応じて価格が設定され、特定の組み合わせがあれば、現時点では勝利ではなく、すでにゲームのラインを構築しています。 これらの追加データのおかげで、選択した戦略に応じて既に決定を下すことができます。プレーヤーの統計に追加すると、さらに正確になります。完全な誤算を追加した場合でも、このサンプルは補足できます。 サンプルの削減と意思決定についての私の考えを伝えたいと思います。これはすべて、ゲームの個人的な経験と意思決定に基づいています。 いくつかの文では、多くのボリュームで公開されているポーカーゲームの理論を、さまざまな状況について語ることはできません。



最初に、テーブルの状況を調べ、どの組み合わせがあるかを調べる必要があります。 組み合わせ定義コードの例を次に示します(より明確に書き直されましたが、最適ではない場合があります)。



 //     ,     int CardToNumber(char Card) { if ((Card>'1')&&(Card<='9'))return (Card-48); if (Card=='T')return 10; if (Card=='J')return 11; if (Card=='Q')return 12; if (Card=='K')return 13; if (Card=='A')return 14; return -1; } //    ,    int MastToNumber(char mast) { if (mast=='h')return 1; if (mast=='d')return 2; if (mast=='c')return 4; if (mast=='s')return 8; return 0; } //    int Hand(char *MyHand_,char *CardsTable_) { int flush_,flush; //0- 1- 2-  3- 4-  (nothing cards) int cards[15][4]; //i- j1--  j2- bits 1,2,3,4-h,d,c,s j3-0/1 - 1-  0- ( ) int straight; //0-, 1-, 2- (2- ) 3- 4-    5-   int readyhand; //0-  , 1-  int i,j, maxi,flag_,flag,flmax,top,pair_,pair,three,four,over,set_,treeps,quad,num,kicker; //         for(i=1;i<=14;i++) for(j=1;j<=3;j++)cards[i][j]=0; cards[CardToNumber(MyHand_[0])][1]++; cards[CardToNumber(MyHand_[0])][2]=cards[CardToNumber(MyHand_[0])][2]| 2*MastToNumber(MyHand_[1])); cards[CardToNumber(MyHand_[0])][2]=cards[CardToNumber(MyHand_[0])][2]|(32*MastToNumber(MyHand_[1])); cards[CardToNumber(MyHand_[0])][3]=1; cards[CardToNumber(MyHand_[3])][1]++; cards[CardToNumber(MyHand_[3])][2]=cards[CardToNumber(MyHand_[3])][2]|(2*MastToNumber(MyHand_[4])); cards[CardToNumber(MyHand_[3])][2]=cards[CardToNumber(MyHand_[3])][2]|(32*MastToNumber(MyHand_[4])); cards[CardToNumber(MyHand_[3])][3]=cards[CardToNumber(MyHand_[3])][3]+1; i=0; while(i<strlen(CardsTable_)) { cards[CardToNumber(CardsTable_[i])][1]++; cards[CardToNumber(CardsTable_[i])][2]=cards[CardToNumber(CardsTable_[i])][2]|(2*MastToNumber(CardsTable_[i+1])); i=i+3; } //  ,      num=0; i=1; maxi=0; flag=0; flmax=0; while(i<15) { if((cards[i][1]>0)&&(i>1)) { num++; if (cards[i][3]==1)flag=i; } else { if (maxi<num) { maxi=num; flmax=flag; } num=0; flag=0; } if(i==1){ if (cards[14][1]>0) { num++; if (cards[i][3]==1)flag=i; } else { if (maxi<num) { maxi=num; flmax=flag; } num=0; flag=0; } } i++; } if(maxi<num){maxi=num;flmax=flag;} num=0; i=0; flag=0; if(flmax>=maxi) { while(i<maxi) { if((cards[flmax-i][3]>0)&&(flmax-i!=1))flag++; if((cards[14][3]>0)&&((flmax-i)==1))flag++; i++; } } straight=0; if((maxi>=5)&&(flmax==14)&&(flag==0)) straight=4; if((maxi>=5)&&(flmax<14)&&(flag==0)) straight=5; if((maxi>=5)&&(flag>0)) straight=1; if((maxi==4)&&(flag>0)&&(flmax<14)) straight=2; if((maxi==4)&&(flag>0)&&(flmax==14)) straight=3; if((maxi==4)&&(flag>0)&&(flmax==4)) straight=3; //   flush_=0; j=2; while(j<=16) { flush=0; flag=0; i=2; num=0; flmax=0; flag_=0; while(i<15){ if ((cards[i][2]& j)==j) { flmax=i;num++;} if ((cards[i][2]& (j*16))==j*16) { flag_++;flag=i;} i++; } if((num==5)&&(flag==0)) flush=4; if((num>=5)&&(flmax==14)&&(flag>=13)) flush=1; if((num>=5)&&(flmax==14)&&((cards[13][2]& j)==j)&&(flag==12)) flush=1; if((num>=5)&&(flmax==14)&&((cards[13][2]& j)==j)&&((cards[12][2]& j)==j)&&(flag==11)) flush=1; if((num>=5)&&(flmax==14)&&((cards[13][2]& j)==j)&&((cards[12][2]& j)==j)&&((cards[11][2]& j)==j)&&(flag==10)) flush=1; if((num>=5)&&(flmax==14)&&((cards[13][2]& j)==j)&&((cards[12][2]& j)==j)&&((cards[11][2]& j)==j)&&((cards[10][2]& j)==j)) flush=1; if((num==5)&&(flag_==2)) flush=1; if((num==4)&&(flag>0)) flush=2; if((num==3)&&(flag==2)) flush=3; if((flush>flush_)&&(flush!=0))flush_=flush; j*=2; } flush=flush_; //  ,  , , , ,  pair=0;three=0;four=0; top=0;over=0;set_=0;treeps=0;quad=0;pair_=0; i=2; while(i<15) { if ((cards[i][1]>=1)&&(cards[i][3]==0)){ top=0;over=0;} if ((cards[i][1]==1)&&(cards[i][3]==0)){ top=0;over=0;} if ((cards[i][1]==2)&&(cards[i][3]==0)){ pair++;} if ((cards[i][1]==2)&&(cards[i][3]==1)){ pair_++;pair++;top=1;} if ((cards[i][1]==2)&&(cards[i][3]==2)){ pair_++;pair++;over=1;} if (cards[i][1]==3){ three++;} if (cards[i][1]==4){ four++;} if ((cards[i][1]==3)&&(cards[i][3]==1)) treeps=1; if ((cards[i][1]==3)&&(cards[i][3]==2)) set_=1; if ((cards[i][1]==4)&&(cards[i][3]>0)) quad=1; if ((cards[i][1]==4)&&(cards[14][3]>0)) quad=1; i++; } kicker=0; if (top>0) for(i=2;i<=14;i++){ if ((cards[i][1]==1)&&(cards[i][3]==1)) kicker=i; } // ':   '- straight // ':   '- flush // '- : '- pair // '-   : '- pair_ // ' : '- top // '' - kicker // ' : '- over // '3 : '- three // ':(2    1  ) '- treeps // ': (2   1  )'- Set_ // '4 : '- four // ': '- quad ……… }
      
      







その結果、関連する変数には、現在の状況に関するデータがあります。 さらに、満員の家が存在しないという概念に注意してください。それは蒸気とアザミウマまたはセットの派生物です。 ここでは、手の準備を決定するピース、つまり 準備ができていない たとえば、次のコード:



...



//手の準備

  readyhand=0; if ((pair==1)&&((top==1)||(over==1))) readyhand=1; if ((pair==1)&&(top==1)&&(kicker<11)&&(over==0)) readyhand=0; if ((pair>=2)&&((top==1)||(over==1))) readyhand=1; if ((pair==2)&&(top==1)&&(kicker<11)&&(over==0)) readyhand=0; if ((pair==2)&&((top==0)||(over==0))&&(pair_==2)) readyhand=1; if ((pair>2)&&((top==0)||(over==0))) readyhand=0; if ((three==1)&&(top==0)&&(over==0)&&(pair_==0)) readyhand=0; if (treeps==1) readyhand=1; if (set_==1) readyhand=1; if (straight==5) readyhand=0; if (straight==4) readyhand=1; if ((straight==2)&&(readyhand==0)) readyhand=2; if (straight==1) readyhand=1; if (flush==4) readyhand=0; if ((flush==2)&&(readyhand==0)) readyhand=2; if (flush==1) readyhand=1; if ((three==1)&&((top==1)||(over==1))) readyhand=1; if ((three==1)&&(top==0)&&(over==0)&&(pair_>0)) readyhand=1; if ((three==1)&&(top==0)&&(over==0)&&(pair_==0)&&(pair>0)) readyhand=0; //   if ((treeps==1)&&(pair>0)) readyhand=1; if ((set_==1)&&(pair>0)) readyhand=1; if (quad==1) readyhand=1;
      
      





...



これらのデータに基づいて、対戦相手に関する統計、銀行のチャンスに関する計算、およびその他の必要なものを受け取る部分に移動し、アクションについて決定し、最初のブロックに進みます-アクションを完了します。



ブロック3-統計収集ブロック




これは、意思決定に役立つ重要なブロックであり、自分で統計を収集したり、サードパーティのプログラムを使用したりできます。 ゲーム統計を収集および分析するプログラムであるPokerTracker3へのリクエストの例を説明します。プログラム内の100を超えるパラメーターですが、少なくとも1つの例で十分です。 PokerTracker3では、データベースはPostgesデータベースに保存されます。 リクエストの例:



 SELECT sites.site_abbrev, p.player_name, COUNT(hhps.id_player) AS hands, AVG (CASE WHEN flg_vpip THEN 1 ELSE 0 END)*100 AS vpip, AVG (CASE WHEN cnt_p_raise >= 1 THEN 1 ELSE 0 END)*100 AS pfr, AVG (CASE WHEN flg_steal_att THEN 1 WHEN flg_steal_opp THEN 0 END)*100 AS ats, AVG (CASE WHEN flg_sb_steal_fold THEN 1 WHEN flg_blind_def_opp and flg_blind_s THEN 0 END)*100 AS fsbtos, AVG (CASE WHEN flg_bb_steal_fold THEN 1 WHEN flg_blind_def_opp and flg_blind_b THEN 0 END)*100 AS fbbtos, AVG (CASE WHEN enum_f_cbet_action='F' THEN 1 WHEN flg_f_cbet_def_opp THEN 0 END)*100 AS fcbetf FROM lookup_sites AS sites INNER JOIN ( player AS p INNER JOIN holdem_hand_player_statistics AS hhps ON (p.player_name = ' ')and(p.id_player = hhps.id_player) ) ON p.id_site = sites.id_site GROUP BY sites.site_abbrev,p.player_name ORDER BY sites.site_abbrev DESC;
      
      







出力では、特定のプレーヤーの選択を取得します。そのようなニックネームがデータベースにあるサイトは、サイトでのみ削減できます。相手のハンドの数(正確な読み取りに必要)、VPIP(ゲームに参加するハンドの割合)、PFR(プリフロップレイズ-プリフロップレイズ)、ATS(スチールへのプリフロップブラインドの数)、CBET(フロップでのCベット-フロップでの継続)、BBS(ビッグブラインドスチール-ビッグブラインドのスチール)、SBS(スモールブラインドスチール-スモールブラインドのスチール)ブラインド)。 これらのパラメーターは、主に拡張ショートスタック戦略で使用されます。フルスタックの場合、これらのパラメーターは少数ですが、例として、それらを取得する方法で十分です。 これらすべてのパラメーターが必要な理由とその使用方法については、適切なリソースを読むことをお勧めします。



追加の統計情報を受け取った後、2番目のブロックに進み、ゲームに関する決定を調整します。



結論として。




この記事を書くために、2010年6月6日付の「Hacker 137」マガジンと「2010年8月10日付の「ポーカーオーガズムのシミュレーション」というHacker 139」の記事を移動しました。 私は著者に完全に同意していないので、このトピックは完全には開示されておらず、自分の意見を共有したいと思っています。



取得できるもののスクリーンショットを次に示します。




画像画像

画像画像

画像画像







All Articles