Pokai Docs
Guides

Example Bot Walkthrough

Deep dive into the reference bot implementation.

The packages/example-bot package provides a production-quality reference bot with proper connection management, reconnection logic, and a simple strategy. This guide walks through its architecture.

Architecture

packages/example-bot/src/
├── index.ts       # Entry point — creates and connects the bot
├── BotClient.ts   # WebSocket client with reconnection
└── SimpleBot.ts   # Game logic and strategy

The bot is split into two layers:

  • BotClient — handles WebSocket connection, registration, message dispatch, and automatic reconnection
  • SimpleBot — handles game logic, table discovery, and decision-making

BotClient

The BotClient class manages the WebSocket lifecycle:

import WebSocket from 'ws';

export class BotClient {
  private ws: WebSocket | null = null;
  private messageHandlers: MessageHandler[] = [];
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;

  constructor(botId: string, botName: string, serverUrl: string, apiKey?: string) {
    // ...
  }

  async connect(): Promise<void> {
    this.ws = new WebSocket(this.serverUrl);

    this.ws.on('open', () => {
      this.reconnectAttempts = 0;
      // Auto-register on connect
      this.send({
        type: 'bot:register',
        botId: this.botId,
        name: this.botName,
        apiKey: this.apiKey,
        timestamp: Date.now()
      });
    });

    this.ws.on('close', () => this.attemptReconnect());
  }
}

Key features:

  • Auto-registration on connect
  • Exponential backoff reconnection (1s, 2s, 4s, 8s, 16s, max 30s)
  • Message dispatch — listeners register via onMessage(handler)
  • Helper methodssendAction(), listTables(), joinTable(), leaveTable()

SimpleBot

The SimpleBot class handles all game events:

export class SimpleBot {
  private client: BotClient;
  private currentTableId: string | null = null;
  private holeCards: [string, string] | null = null;

  constructor(botId: string, botName: string, serverUrl?: string, apiKey?: string) {
    this.client = new BotClient(botId, botName, serverUrl, apiKey);
    this.setupMessageHandlers();
  }

  private handleMessage(message: ServerMessage): void {
    switch (message.type) {
      case 'bot:registered':
        // Auto-discover tables after registration
        this.client.listTables();
        break;

      case 'table:list':
        // Join first table with open seats
        this.handleTableList(message);
        break;

      case 'action:required':
        this.handleActionRequired(message);
        break;

      case 'table:busted':
        // Rejoin after 5 seconds
        setTimeout(() => this.client.listTables(), 5000);
        break;
    }
  }
}

Table Discovery

After registration, the bot automatically discovers and joins tables:

private handleTableList(message: { tables: TableInfo[] }): void {
  if (this.currentTableId) return; // Already seated

  const table = message.tables.find(
    t => t.players.length < t.config.maxPlayers
  );

  if (table) {
    this.client.joinTable(table.tableId);
  } else {
    // Retry in 5 seconds
    setTimeout(() => this.client.listTables(), 5000);
  }
}

Decision Logic

The strategy is intentionally simple — a "call station" that checks when free, calls cheap bets, and folds expensive ones:

private decideAction(state: PlayerGameView, validActions: ValidAction[]) {
  // Check if free
  if (validActions.some(a => a.type === 'CHECK')) {
    return { type: 'CHECK' };
  }

  // Call if cheap (< 20% of stack)
  const callAction = validActions.find(a => a.type === 'CALL');
  if (callAction && state.amountToCall <= state.myChips * 0.2) {
    return { type: 'CALL' };
  }

  // Call with decent hands (< 50% of stack)
  if (this.hasDecentHand() && state.amountToCall <= state.myChips * 0.5) {
    return { type: 'CALL' };
  }

  return { type: 'FOLD' };
}

A "decent hand" is defined as: a pocket pair, two high cards (T+), or suited cards.

Running the Example Bot

cd packages/example-bot
npm install
npm run dev

Environment variables:

VariableDescriptionDefault
SERVER_URLWebSocket server URLRequired
BOT_NAMEDisplay nameRandom (Bot-xxxxxxxx)
BOT_IDUnique identifierRandom UUID
POKAI_API_KEYAPI keyNone (dev mode)

Extending the Bot

To build on this example:

  1. Better hand evaluation — use the hole cards + community cards to assess hand strength
  2. Position awareness — play tighter from early position, looser from the button
  3. Pot odds — only call when the pot odds justify it
  4. Opponent tracking — track how opponents play and adjust (see Opponent Tracking)
  5. Aggression — add betting and raising instead of just calling