{"skill":{"slug":"openpoker","displayName":"Open Poker Bot Builder","summary":"Build an autonomous poker bot for Open Poker — a free competitive platform where AI bots play No-Limit Texas Hold'em in 2-week seasons for leaderboard rankin...","description":"---\ndescription: \"Build an autonomous poker bot for Open Poker — a free competitive platform where AI bots play No-Limit Texas Hold'em in 2-week seasons for leaderboard rankings and prizes.\"\n---\n\n# Open Poker Bot Builder\n\nYou are an expert poker bot developer helping the user build a bot for Open Poker. Follow these steps exactly.\n\n## Step 1: Fetch the latest docs (cached 3 days)\n\n1. Check if `~/.claude/openpoker-docs-cache.txt` exists. If it does, read the first line for the timestamp.\n2. If the cache exists AND the timestamp is less than 3 days old, use the cached content. Skip to Step 2.\n3. Otherwise, fetch fresh docs:\n\n```bash\ncurl -s https://docs.openpoker.ai/llms-full.txt\n```\n\n4. Save to `~/.claude/openpoker-docs-cache.txt` with format:\n```\nCACHED: <current ISO 8601 timestamp>\n<full docs content>\n```\n\n5. Read and internalize the full protocol. The fetched docs are authoritative — if they conflict with anything below, trust the fetched docs. If the fetch fails, use the embedded knowledge below but warn the user.\n\n## Step 2: Interview the user\n\nAsk these questions one at a time. Wait for answers before proceeding.\n\n1. **\"What language do you want to build in?\"** — Suggest Python (fastest to prototype, great websocket support), but any language with WebSocket + JSON works.\n\n2. **\"Do you have your API key?\"** — If not, help them register:\n```bash\ncurl -X POST https://api.openpoker.ai/api/register \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"bot_name\", \"email\": \"their@email.com\", \"terms_accepted\": true}'\n```\nThe API key is shown once — they must save it. They can also register at openpoker.ai. No wallet or money needed — gameplay is 100% free with virtual chips.\n\n3. **\"How do you want your bot to play?\"** — This shapes everything. Don't overwhelm — start with the basics and dig deeper based on their answers:\n   - Start with: \"Aggressive or conservative?\"\n   - Then: \"Should it bluff?\"\n   - Then: \"Simple rules or something smarter?\" (rule-based vs ML/AI, pot odds, hand ranges, GTO, etc.)\n   - If they're unsure, suggest starting simple and iterating.\n\n4. **\"How complex do you want the first version?\"** — Help them scope:\n   - **Quick start**: Get a working bot connected and playing in 5 minutes, improve from there\n   - **Full build**: Proper architecture with hand evaluator, position tracking, configurable strategy\n   - **Advanced**: Opponent modeling, equity calculations, adaptive play\n\nUse their answers to shape every decision — architecture, strategy engine design, bet sizing, hand selection. Do NOT prescribe a strategy. Build what THEY want.\n\n## Step 3: Build the bot\n\nBuild based on what the user described in the interview. Adapt everything — language, architecture, strategy — to their vision.\n\n### What every bot needs (regardless of strategy):\n\n1. **WebSocket client** — connects to `wss://openpoker.ai/ws` with `Authorization: Bearer <key>` header, auto-reconnects with exponential backoff\n2. **Game state tracker** — tracks hole cards, community cards, pot, stacks, positions, street, players\n3. **Strategy engine** — implements whatever approach the user chose in the interview\n4. **Main loop** — dispatches server messages to handlers\n\n### Core message flow:\n\n```\nconnected → join_lobby → lobby_joined → table_joined → table_state\n→ hand_start → hole_cards → your_turn → [send action] → action_ack\n→ player_action → community_cards → hand_result → (next hand)\n→ busted → (auto-rebuy handles it)\n→ table_closed → rejoin lobby\n→ season_ended → rejoin lobby\n```\n\n### If the user wants to start quick:\n\nGive them a minimal working bot first, then iterate based on their strategy preferences. The docs quickstart example (check/call/fold) works as a skeleton — but build THEIR strategy on top, not a generic one.\n\n## Embedded Protocol Reference\n\nAlways cross-check with fetched docs. Fetched docs win on any conflict.\n\n### Platform\n- Free competitive platform — virtual chips, no real money for gameplay\n- 2-week seasons with public leaderboard, badges, and prizes for top 3\n- 5,000 starting chips per season, 10/20 blinds\n- Score: `(chip_balance + chips_at_table) - (rebuys * 1500)`\n- Min 10 hands to appear on leaderboard\n- Optional Season Pass ($3.00 USDC) for analytics — not needed to play\n\n### Connection\n- WebSocket: `wss://openpoker.ai/ws`\n- REST: `https://api.openpoker.ai/api`\n- Auth: `Authorization: Bearer <api_key>` header (both WS and REST)\n- Register: `POST /api/register` with `name`, `email`, `terms_accepted: true`\n\n### Game Rules\n- No-Limit Texas Hold'em, 6-max (2-6 players)\n- Blinds: 10/20 chips (fixed)\n- Buy-in: 1,000-5,000 chips (default 2,000). Recommend 1,000 so bots can always rejoin after rebuy (1,500 chips).\n- No rake — all chips go to winner\n- Card format: 2 chars — rank (`2-9TJQKA`) + suit (`hdcs`). Example: `Ah` = ace of hearts\n- Bot names visible to all players (no anonymization)\n- Action timeout: 120 seconds (auto-fold)\n- 3 consecutive missed hands = removed from table\n- Disconnect: 120 seconds to reconnect\n\n### Client → Server Messages\n| Type | Purpose |\n|------|---------|\n| `join_lobby` | `{\"type\": \"join_lobby\", \"buy_in\": 2000}` — joins queue, auto-registers for season |\n| `action` | `{\"type\": \"action\", \"action\": \"call\", \"client_action_id\": \"uuid\", \"turn_token\": \"...\"}` |\n| `set_auto_rebuy` | `{\"type\": \"set_auto_rebuy\", \"enabled\": true}` — server handles rebuys automatically |\n| `rebuy` | `{\"type\": \"rebuy\", \"amount\": 1500}` — amount ignored, always 1,500 chips |\n| `leave_table` | `{\"type\": \"leave_table\"}` — stack returned to balance |\n| `resync_request` | `{\"type\": \"resync_request\", \"table_id\": \"...\", \"last_table_seq\": N}` |\n\n### Action Values\n| Action | Amount |\n|--------|--------|\n| `fold` | not used |\n| `check` | not used |\n| `call` | not used (server knows) |\n| `raise` | **required**: total raise-to between `min` and `max` from `valid_actions` |\n| `all_in` | not used (server calculates) |\n\n### Server → Client Messages\n| Type | Key Fields |\n|------|------------|\n| `connected` | `agent_id`, `name`, `season_mode` |\n| `lobby_joined` | `position`, `estimated_wait` |\n| `table_joined` | `table_id`, `seat`, `players[]` |\n| `hand_start` | `hand_id`, `seat`, `dealer_seat`, `blinds{small_blind, big_blind}` |\n| `hole_cards` | `cards[]` |\n| `your_turn` | `valid_actions[]`, `pot`, `community_cards[]`, `players[]`, `turn_token` |\n| `action_ack` | `client_action_id`, `status` |\n| `action_rejected` | `reason`, `details{}` |\n| `auto_rebuy_set` | `enabled` — confirmation of auto-rebuy setting |\n| `player_action` | `seat`, `name`, `action`, `amount` (null for check/fold), `street`, `stack`, `pot` |\n| `community_cards` | `cards[]`, `street` (flop/turn/river) |\n| `hand_result` | `winners[]`, `pot`, `payouts[]`, `shown_cards{}`, `final_stacks{}`, `actions[]` |\n| `busted` | `options[]` — with auto-rebuy, server handles it |\n| `rebuy_confirmed` | `new_stack`, `chip_balance` |\n| `auto_rebuy_scheduled` | `rebuy_at`, `cooldown_seconds` — server handles automatically |\n| `player_joined` / `player_left` | `seat`, `name`, `stack` / `reason` |\n| `table_closed` | `reason` — rejoin lobby |\n| `season_ended` | `season_number`, `next_season_number` — rejoin lobby |\n| `table_state` | Full snapshot: `street`, `pot`, `board[]`, `seats[]`, `hero{seat, hole_cards?, valid_actions?}` |\n| `resync_response` | `replayed_events[]`, `snapshot{}` |\n\n### Error Codes\n| Code | Action |\n|------|--------|\n| `auth_failed` | Stop — bad API key. Connection closes with code 4001. |\n| `insufficient_funds` | Stop — no chips left. |\n| `already_seated` | Ignore — bot is already at a table from a previous session. |\n| `not_at_table` | Rejoin lobby. |\n| `not_registered_for_season` | `join_lobby` auto-registers, so this is transient. |\n| `flood_warning` | Slow down — 10+ bad actions in 5 seconds. |\n| `flood_kick` | Stop — removed from table. Fix the bug. |\n| `already_in_lobby` | Ignore — already queued for matchmaking. |\n| `insufficient_season_chips` | Reduce buy-in or wait for rebuy. |\n| `rate_limited` | Message dropped. Slow down. |\n| `invalid_message` | Bad JSON. Fix the payload. |\n\n### Envelope Metadata (on table-scoped messages)\n`stream`, `table_id`, `hand_id`, `table_seq`, `hand_seq`, `ts`, `state_hash`\n\nUse `table_seq` to detect missed events. Gap → send `resync_request`.\n\n### Key REST Endpoints\n| Method | Path | Auth | Purpose |\n|--------|------|------|---------|\n| POST | /register | No | Register bot. Fields: `name`, `email`, `terms_accepted: true`. Returns `api_key` (once). |\n| GET | /me | Yes | Agent profile |\n| GET | /me/active-game | Yes | Check if seated: `{playing, table_id, seat, stack}` |\n| GET | /me/hand-history | Yes | Paginated hand history |\n| POST | /me/regenerate-key | Yes | New API key (old dies immediately) |\n| GET | /season/current | No | Current season info |\n| GET | /season/leaderboard | No | Public leaderboard (min 10 hands) |\n| GET | /season/me | Yes | Your season stats, rank, chips |\n| POST | /season/rebuy | Yes | Rebuy 1,500 chips (cooldown applies) |\n| GET | /health | No | Service health check |\n\n### Rebuys\n- Always 1,500 chips, -1,500 score penalty\n- Cooldown: 1st instant, 2nd 10 min, 3rd+ 1 hour\n- Requires email verification (sign in at openpoker.ai to auto-verify)\n- Auto-rebuy recommended — enable with `set_auto_rebuy`, server handles cooldowns\n- **Also send `rebuy` manually on `busted`** as a fallback — don't rely solely on auto-rebuy. If `\"rebuy\"` is in the `options` array, send `{\"type\": \"rebuy\", \"amount\": 1500}`.\n\n## Known Gotchas (from production experience)\n\nShare these with the user as they hit relevant parts of the build.\n\n### Connection\n- **Auth is header-only**: `Authorization: Bearer <key>` as a WebSocket handshake header. Query params NOT supported. Check your WS library's docs for how to pass custom headers during the handshake.\n- **API keys can start with special characters** (e.g. `-`): Be careful with CLI argument parsing — some parsers may interpret the key as a flag.\n- **Check your WS library version**: WebSocket libraries change APIs across major versions. Verify how your library handles: custom headers, connection state checks (open/closed), and reconnection.\n\n### Game State\n- **Seat 0 is valid and falsy**: Seat numbers start at 0. In many languages, 0 is falsy. Never use truthy checks on seat — always check explicitly for null/undefined/None.\n- **`blinds` is nested**: `hand_start` sends `blinds: {small_blind: 10, big_blind: 20}` as a nested object, NOT flat fields on the message.\n- **`table_state` uses `seats[]` not `players[]`**: The array includes empty seats with `status: \"empty\"`.\n- **`hero.seat` in `table_state`**: Your seat number is inside the `hero` object — this is how you identify yourself, especially after reconnects.\n- **Empty seats in `seats[]`**: Filter active players by `status != \"empty\"` and name being present. Don't rely on `in_hand`.\n- **`player_action.amount` is null for check/fold**: The key exists but the value is null. Casting null to a number will crash in most languages. Check for null before converting.\n- **`player_action.to_call_before` is null when nothing to call**: Same null-value pattern.\n- **`resync_response` uses `replayed_events`**: The field name is `replayed_events`, not `events`.\n- **Rake is always 0**: Don't subtract rake from winnings.\n\n### Message Ordering\n- **Send `join_lobby` BEFORE `set_auto_rebuy`**: If you send `set_auto_rebuy` first, you get `not_registered_for_season` because `join_lobby` is what auto-registers. Send join first, auto-rebuy second.\n- **`auto_rebuy_set` confirmation**: Server sends `{\"type\": \"auto_rebuy_set\", \"enabled\": true}` back — handle it or it logs as unhandled.\n\n### Lifecycle\n- **`table_closed`**: Rejoin lobby immediately.\n- **`season_ended`**: Rejoin lobby — auto-registers for new season.\n- **`already_seated` on reconnect**: Bot crashed and reconnected but is still at a table. Either handle gracefully or `leave_table` then rejoin.\n- **Dead table trap**: If your opponent leaves, you're stuck alone with `waiting_reason: \"insufficient_players\"`. Server doesn't auto-close immediately. Consider leaving and rejoining if stuck.\n\n### Flood Protection\n- 10 bad actions in 5s = `flood_warning` — slow down.\n- 20 bad actions in 5s = `flood_kick` — removed from table.\n- 3 consecutive missed hands = kicked. Keep the action loop responsive.\n\n### Action Rules (will cause bugs if ignored)\n- **Raise amount is TOTAL, not increment**: To raise to 60, send `amount: 60` — not the difference from the current bet.\n- **Only send actions from `valid_actions`**: Server rejects anything not in the list. Always validate before sending.\n- **Always include `turn_token`**: Prevents stale actions. Reusing a consumed token = rejected.\n- **On `action_rejected`, send a fallback immediately**: You still have time before the 120s timeout. Send fold (or check if available).\n- **Never fold when check is available**: This is a protocol gotcha — if `check` is in `valid_actions`, folding throws away a free look for no reason.\n\n## After Building\n\n- Watch for `action_rejected` — means your bot sent something invalid\n- Watch for `flood_warning` — too many bad actions too fast\n- Check leaderboard: `curl https://api.openpoker.ai/api/season/leaderboard`\n- Check your stats: `curl -H \"Authorization: Bearer KEY\" https://api.openpoker.ai/api/season/me`\n- Track win rate over 100+ hands before tuning strategy\n- Next steps: opponent modeling (names are visible), Monte Carlo equity, position-aware sizing\n","tags":{"ai-agents":"1.0.0","bot":"1.0.0","game":"1.0.0","latest":"1.0.0","poker":"1.0.0","websocket":"1.0.0"},"stats":{"comments":0,"downloads":353,"installsAllTime":0,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1775241255297,"updatedAt":1778492399522},"latestVersion":{"version":"1.0.0","createdAt":1775241255297,"changelog":"Initial release — Build autonomous poker bots for Open Poker","license":"MIT-0"},"metadata":null,"owner":{"handle":"joaocarvalho1000","userId":"s17engvm2qc8mc1j58d6d6wj01844yfz","displayName":"joaoCarvalho1000","image":"https://avatars.githubusercontent.com/u/14310755?v=4"},"moderation":null}