Skip to content
[OPEN_POKER]

Poker Botがタイムアウトする理由:原因と非同期の修正方法

JJoão Carvalho||4 min read

Poker botのタイムアウトは保持しているハンドを自動フォールドする。ポケットエース、ナッツフラッシュ、関係ない。サーバーがアクションを没収し、スタックが後退する。120秒のウィンドウはセッションのハンド#47で実際のボットがオーバーするのを見るまで寛大に聞こえる。原因と各バリアントの修正方法を解説する。

タイムアウトすると何が起こるか?

Open Pokerはyour_turnから有効なactionメッセージ送信まで120秒の厳格なウィンドウを強制する。期限を過ぎるとフォールド(またはチェック)が強制される。ボットはテーブルに残るが、ハンドは失われる。繰り返しのタイムアウトは切断につながる。タイムアウトリファレンスを参照。

タイムアウトは最も強いハンドに偏る。ボットは重要な判断により長く考える傾向がある。

なぜ120秒は実際には寛大でないのか?

1. 判断ループ内の同期ネットワーク呼び出し。 原因#1。同期呼び出しはボット全体をブロックする。

2. 判断中の再接続。

3. 長時間稼働ボットのガベージコレクションポーズ。

遅い判断の見つけ方

import time
 
async def handle_your_turn(msg, ws):
    start = time.monotonic()
    try:
        action = await decide(msg)
        await ws.send(json.dumps({
            "type": "action",
            "action": action["type"],
            "amount": action.get("amount", 0),
            "client_action_id": f"a-{msg['turn_token'][:8]}",
            "turn_token": msg["turn_token"],
        }))
    finally:
        elapsed_ms = (time.monotonic() - start) * 1000
        if elapsed_ms > 1000:
            print(f"[SLOW] decision took {elapsed_ms:.0f}ms on hand {msg.get('hand_number')}")

同期ネットワーク呼び出しの修正方法

すべてをasyncに変換。requestsの代わりにhttpxを使用:

import httpx
http = httpx.AsyncClient(timeout=3.0)
 
async def decide(msg):
    response = await http.get("https://api.example.com/equity")
    equity = response.json()["equity"]
    return ("call" if equity > 0.4 else "fold")

タイムアウト付きで判断をラップする方法

async def decide_with_fallback(msg):
    try:
        return await asyncio.wait_for(decide(msg), timeout=10.0)
    except asyncio.TimeoutError:
        return fallback_decision(msg)
 
def fallback_decision(msg):
    actions = {a["action"]: a for a in msg["valid_actions"]}
    if "check" in actions:
        return ("check", 0)
    if "call" in actions:
        call_amt = actions["call"]["amount"]
        if call_amt < msg.get("pot", 0) * 0.2:
            return ("call", call_amt)
    return ("fold", 0)

再接続の処理方法

while True:
    try:
        async with websockets.connect(WS_URL, additional_headers=headers) as ws:
            await ws.send(json.dumps({"type": "set_auto_rebuy", "enabled": True}))
            await ws.send(json.dumps({"type": "join_lobby", "buy_in": 2000}))
            async for raw in ws:
                msg = json.loads(raw)
                await handle_message(msg, ws)
    except websockets.ConnectionClosed:
        print("Connection lost, reconnecting in 2s...")
        await asyncio.sleep(2)

FAQ

Open Pokerのアクションタイムアウトは? 120秒。時間切れで自動フォールド。

なぜ難しい判断でのみタイムアウトするのか? 複雑な判断はより多くのコードパスを実行するため。

タイムアウトを延長できるか? できない。120秒は全ボット固定。

合理的な目標レイテンシーは? 200ms未満が優秀。5秒以上は調査が必要。


タイムアウトはボット勝率を破壊する見えないバグだ。修正は防御的:すべての判断レイテンシーをログし、メインロジックをasyncio.wait_for()でラップし、あらゆる場所でasyncクライアントを使い、安全なフォールバックを用意する。最初のボットを構築する際にこれらのパターンを最初から組み込もう。

続きを読む