Pular para o conteúdo
[OPEN_POKER]

Depure seu bot de pôquer: 7 erros comuns de WebSocket resolvidos

JJoão Carvalho||Atualizado |10 min de leitura

Aula 3 de 13: Tornar o bot confiável

Identifique erros e interrompa a execução quando for necessária uma intervenção. Conclua a aula 2, use Python 3.11 ou superior e instale as dependências do requirements.txt incluído no download.

Quem desenvolve bots costuma encontrar os mesmos erros de WebSocket. Já acompanhei centenas de conexões ao Open Poker, e os problemas se repetem: falhas de autenticação, tempo limite excedido, JSON malformado, desconexões silenciosas e condições de corrida que só aparecem sob carga. A seguir, veja sete problemas frequentes e como investigar cada um. Para entender as decisões lentas, leia Por que seu bot de pôquer fica sem tempo. Se você está começando, siga o tutorial inicial em Python.

1. Por que meu bot recebe auth_failed imediatamente?

O erro auth_failed indica que o servidor rejeitou sua chave de API antes de concluir a negociação inicial da conexão WebSocket. A conexão é encerrada com o código 4001, e você verá esta resposta:

{
  "type": "error",
  "code": "auth_failed",
  "message": "Invalid or missing API key"
}

Há três causas comuns. A primeira é a ausência do cabeçalho Authorization. A biblioteca websockets do Python só envia cabeçalhos personalizados quando você os informa explicitamente.

import websockets
 
# Wrong: no auth header
ws = await websockets.connect("wss://openpoker.ai/ws")
 
# Right: pass the header explicitly
headers = {"Authorization": f"Bearer {API_KEY}"}
ws = await websockets.connect("wss://openpoker.ai/ws", additional_headers=headers)

A segunda causa são espaços ou quebras de linha na chave de API. Eles podem aparecer ao copiar a chave do painel ou de um e-mail. Remova os espaços nas extremidades com API_KEY = os.environ["POKER_API_KEY"].strip().

A terceira causa é gerar uma nova chave com POST /api/me/regenerate-key e esquecer de atualizar a configuração do bot. A chave anterior deixa de valer imediatamente; não há período de transição.

Confira a documentação do protocolo WebSocket para conhecer o fluxo de autenticação e a alternativa por parâmetro de URL usada por clientes de navegador.

2. Por que meu bot desiste automaticamente em todas as mãos?

Seu bot está ultrapassando o tempo disponível. Nas partidas públicas, o Open Poker permite 45 segundos por ação. Se nenhuma ação válida chegar nesse intervalo, o servidor faz o fold automaticamente. Uma desconexão não pausa nem reinicia esse prazo. Mesmo uma janela de 45 segundos pode ser insuficiente quando o código bloqueia a execução.

Bloqueio do loop de eventos. Se o código que trata your_turn executa trabalho síncrono, como chamadas HTTP, acesso a arquivos ou cálculos pesados, o loop async for fica bloqueado. As mensagens se acumulam enquanto esse trabalho não termina.

# Bad: blocks the event loop
def decide(msg):
    time.sleep(2)  # simulating slow computation
    return "call"
 
# Good: keep it async
async def decide(msg):
    await asyncio.sleep(0)  # yield control briefly if needed
    return "call"

Ausência de tratamento para your_turn. Se o roteador não reconhecer esse tipo de mensagem, ela será descartada sem que o bot responda. Registre os tipos de mensagem que ficaram sem tratamento:

async for raw in ws:
    msg = json.loads(raw)
    t = msg.get("type")
 
    if t == "your_turn":
        await handle_turn(ws, msg)
    elif t in ("hand_start", "hand_result", "community_cards"):
        pass  # informational
    else:
        print(f"Unhandled message type: {t}")

A documentação de ciclo de vida do bot mostra cada mensagem que seu bot precisa tratar, com os formatos JSON exatos.

3. O que causa erros action_rejected?

O servidor verificou sua ação e encontrou um problema. Você ainda precisa enviar uma ação válida antes do fim do prazo para evitar o fold automático. A resposta tem este formato:

{
  "type": "action_rejected",
  "reason": "Invalid raise amount"
}

As três causas mais comuns:

turn_token desatualizado. Cada mensagem your_turn traz um token novo, que deve ser devolvido na ação. Se você reutilizar o token de uma rodada de decisão anterior ou não informar nenhum token, o servidor rejeitará a ação.

# Always use the token from the CURRENT your_turn
await ws.send(json.dumps({
    "type": "action",
    "hand_id": msg["hand_id"],
    "action": "raise",
    "amount": 60.0,
    "turn_token": msg["turn_token"],  # from the your_turn you're responding to
}))

Valor do aumento fora dos limites. A lista valid_actions informa os valores min e max permitidos para um aumento. Qualquer valor fora desse intervalo será rejeitado. Calcule o tamanho da aposta a partir desses limites, em vez de usar um valor fixo.

actions = {a["action"]: a for a in msg["valid_actions"]}
if "raise" in actions:
    min_raise = actions["raise"]["min"]
    max_raise = actions["raise"]["max"]
    # Your desired amount, clamped to valid range
    amount = max(min_raise, min(your_amount, max_raise))

Ação não permitida. Se valid_actions contiver apenas fold e call, enviar check causará uma rejeição. Leia sempre a lista enviada pelo servidor; não suponha quais ações estão disponíveis.

4. Como tratar campos null sem interromper o bot?

O protocolo do Open Poker mantém alguns campos presentes mesmo quando seu valor é null. Isso torna o formato das mensagens consistente, mas exige cuidado antes de converter os valores para outros tipos.

Um exemplo comum de falha:

# This raises TypeError when amount is null
amount = float(msg["amount"])  # float(None) -> TypeError

A mensagem player_action define amount como null em ações de fold e check. Trate esse caso antes de usar o valor:

# Safe: handles null
amount = msg.get("amount") or 0.0

Aplique o mesmo cuidado a to_call_before, que pode ser null quando não há aposta a pagar:

to_call = msg.get("to_call_before") or 0.0

Gastamos cerca de quatro horas investigando uma variação desse problema. Nosso bot somava player_action.amount para acompanhar as apostas dos adversários. Quando alguém dava check, o valor null afetava o total acumulado. Só percebemos o erro quando o cálculo de pot odds produziu inf. Ao reconstruir o estado da mesa, valide cada campo recebido.

Consulte a documentação de tratamento de mensagens para saber quais campos aceitam null e como acessá-los com segurança.

5. Por que meu bot perde o assento após uma desconexão?

O prazo de 45 segundos para a ação continua correndo após a desconexão. Reconectar não o pausa nem o reinicia. Existe um período separado para recuperar o assento e restaurar o estado da mesa, mas isso não torna válida uma ação calculada antes da queda. Confira o estado atual antes de agir.

Causas comuns de desconexão:

Ausência de resposta a ping/pong. A biblioteca websockets normalmente responde aos pings automaticamente. Se você usar um cliente de nível mais baixo ou desativar as respostas automáticas, o servidor poderá encerrar conexões consideradas inativas.

Quedas de rede sem novas tentativas. Encapsule a conexão em uma rotina que tente restabelecê-la com intervalos crescentes entre as tentativas:

import asyncio
import json
import websockets
 
async def connect_with_retry(api_key, max_retries=10):
    headers = {"Authorization": f"Bearer {api_key}"}
    retries = 0
    while retries < max_retries:
        try:
            async with websockets.connect(
                "wss://openpoker.ai/ws",
                additional_headers=headers
            ) as ws:
                retries = 0  # reset on successful connection
                await play_loop(ws)
        except (websockets.ConnectionClosed, ConnectionError) as e:
            retries += 1
            wait = min(2 ** retries, 60)  # exponential backoff, cap at 60s
            print(f"Disconnected: {e}. Retrying in {wait}s ({retries}/{max_retries})")
            await asyncio.sleep(wait)
    print("Max retries reached. Exiting.")

Substituição da sessão. Ao abrir outra conexão WebSocket com a mesma chave de API, a conexão anterior é substituída imediatamente. Isso pode acontecer quando você reinicia o bot sem encerrar o processo antigo. Mantenha uma conexão por agente e encerre o processo anterior antes de iniciar outro.

Após reconectar, envie um resync_request para recuperar eventos perdidos:

await ws.send(json.dumps({
    "type": "resync_request",
    "table_id": stored_table_id,
    "last_table_seq": last_seq_number
}))

6. O que significa rate_limited e como evitar?

O Open Poker limita cada conexão WebSocket a 20 mensagens por segundo e cada endereço IP a 10 tentativas de conexão por minuto. Ao exceder um desses limites, você recebe:

{
  "type": "error",
  "code": "rate_limited",
  "message": "Too many messages per second"
}

O limite de mensagens raramente afeta uma partida normal. O bot envia uma ação por vez e, eventualmente, um join_lobby entre mesas. Porém, reenviar ao servidor cada mensagem recebida pode esgotar esse limite rapidamente. Mantenha os registros de diagnóstico no próprio cliente.

O limite de conexões exige mais atenção. Sem espera entre as tentativas, uma oscilação de rede pode consumir as dez tentativas em poucos segundos e bloquear novas conexões por um minuto. O aumento gradual dos intervalos, chamado de backoff exponencial, reduz esse risco.

Para um diagnóstico rápido, conte as mensagens enviadas. Mais de cinco por segundo durante uma partida normal merece investigação. Verifique se o bot está respondendo a toda mensagem recebida, em vez de agir apenas quando recebe your_turn.

7. Por que meu bot entra no lobby, mas nunca chega a uma mesa?

Esse caso costuma estar ligado à formação das mesas, não à conexão WebSocket. Mesmo assim, é uma das dúvidas mais frequentes de quem está começando.

A formação de uma mesa exige pelo menos dois jogadores na fila. Se não houver outro bot disponível, o seu continuará esperando. Confira a classificação para procurar outros bots ativos.

Outras causas:

SintomaCódigo de erroComo resolver
Já está em uma mesaalready_seatedRestaure o estado da mesa antes de decidir se deve sair ou continuar
Já está na filaalready_in_lobbyNão envie join_lobby novamente
Sem temporada ativano_active_seasonAguarde o início da próxima temporada
Fichas insuficientesinsufficient_season_chipsConfira seu saldo de fichas

Nos testes locais, execute dois bots com chaves de API diferentes para atingir o mínimo de dois jogadores necessário à formação da mesa.

Se os sete casos acima não explicarem o problema, registre os tipos de mensagem e os códigos de erro, compare-os com a referência do protocolo, confira o turn_token atual e teste com o bot básico em Python como referência. Procure chamadas de time.sleep() ou outras operações síncronas que possam bloquear o tratamento assíncrono das mensagens. Evite registrar mensagens completas que contenham credenciais ou cartas privadas.

FAQ

Meu bot funciona localmente, mas falha em produção. O que muda? Três aspectos mudam: a URL passa de ws://localhost:8000/ws para wss://openpoker.ai/ws, a conexão exige um certificado TLS válido e a latência aumenta. Note o prefixo wss. Se você usa um certificado autoassinado nos testes locais, confira se a configuração de produção valida a cadeia de certificados do servidor. A biblioteca websockets normalmente cuida disso, mas uma configuração SSL personalizada pode interferir.

Como sei se minha ação foi realmente aceita? O servidor envia action_ack com o client_action_id da tentativa. Inclua esse identificador em cada ação para relacionar a confirmação à tentativa correta. Sem ele, não há esse campo de correlação na resposta.

Posso reconectar no meio da mão e ainda agir? Pode ser possível recuperar o assento, mas o prazo original de 45 segundos para agir continua valendo. Envie resync_request com table_id e last_table_seq. Depois, use o hand_id e o turn_token do estado atual restaurado, ou aguarde uma nova mensagem your_turn. Não reutilize uma ação antiga apenas porque a conexão voltou.

Por que recebo erros invalid_message? O JSON pode estar malformado ou sem campos obrigatórios. Verifique se você usou aspas duplas, incluiu type e serializou o dicionário antes do envio. Use json.dumps; montar JSON manualmente com f-strings facilita erros.


O problema não apareceu aqui? Consulte a referência completa do protocolo ou registre seu bot para testar a conexão. Acompanhe os tipos de mensagem e os códigos de erro para entender o fluxo, sem expor credenciais ou cartas privadas nos registros.

Confira o resultado da aula 3

O que muda
Registre os tipos de decisão e os códigos de erro do protocolo.
Saída esperada
error_fixture: recorded
Confira se funcionou
O erro simulado deve incrementar errors uma vez. Na sessão online, erros desconhecidos interrompem o bot e fornecem um código para investigação.

Extraia o arquivo da aula, abra a pasta em um terminal e execute:

python -m pip install -r requirements.txt
python bot.py --lesson 3 --self-test
python bot.py --lesson 3 --hands 3 --report run.json

O teste usa dados simulados, sem conexão, e exibe checkpoint: passed. Para jogar online, você precisa de OPEN_POKER_API_KEY no ambiente de execução e de adversários disponíveis. Consulte o README incluído para configurar o bot e entender os limites de recuperação.

Todas as aulas do curso
  1. 1. Crie um bot de pôquer em Python
  2. 2. Arquitetura básica de um bot de pôquer
  3. 3. Como depurar erros de WebSocket no bot
  4. 4. Por que o bot fica sem tempo para agir
  5. 5. Matemática do pôquer para bots
  6. 6. Ranges por posição para bots de pôquer
  7. 7. Estratégia de apostas para bots de pôquer
  8. 8. Tutorial de PokerKit
  9. 9. Calculadora de equidade por Monte Carlo
  10. 10. Modelagem de adversários
  11. 11. Arquitetura avançada de um bot de pôquer
  12. 12. Como funciona a pontuação da classificação
  13. 13. Arquitetura profissional de um bot de pôquer

Continue Lendo