Documentação da API

Guia passo a passo para iniciantes

O que você precisa

  • Uma conta no FlipSight. Depois de entrar, você pode registrar um bot e baixar o conector.
  • Uma foto do bot: um arquivo GIF, JPG ou PNG de até 5 MB, com pelo menos 175 px de largura e de altura.
  • Seu computador para executar o bot As jogadas do seu bot são calculadas no computador onde o conector está rodando. Mesmo com o mesmo motor, a força pode variar conforme o desempenho do computador e as configurações do motor, e se você mudar de computador ou de configuração, o rating pode mudar de acordo.
  • Python 3, um programa gratuito que executa o conector. A versão 3.9 ou mais recente funciona; recomendamos a 3.11 ou mais recente. Você vai verificar isso no passo 5.
  • O motor do seu bot. Um motor de prática para testes está incluído no programa de conexão.

1. Registre o seu bot

Entre na sua conta e abra a página de bots (othelloclub.com/pt/flipsight/bot). No quadro Registrar meu bot, no topo, digite um nome em Nome do bot (2–24 caracteres). Por enquanto, deixe marcado em Jogos compatíveis apenas Padrão. Escolha uma imagem em Enviar foto do bot e pressione o botão Registrar meu bot.

Se tudo deu certo: aparece o quadro Token de conexão (exibido uma vez) com um texto longo que começa com fsbm_.

2. Guarde o token

Esse texto longo é o token, uma senha só do seu bot. O conector usa o token para provar ao servidor que fala em nome deste bot. Copie o texto inteiro e cole em algum lugar temporário, como um editor de texto. Depois que você fechar o quadro, não poderá ver o mesmo token de novo.

Trate o token como uma senha. Não mostre o token a ninguém e não publique capturas de tela dele. Se você o perder ou alguém o vir, abra Gerenciar na lista de bots e pressione Emitir token de conexão. Quando você recebe um novo token, o antigo para de funcionar na hora.

3. Baixe o conector

Pressione Baixar exemplos Python para baixar o arquivo ZIP e extraia o arquivo. Este guia chama a pasta extraída de "pasta do bot". Coloque a pasta em um lugar fácil de encontrar, como a área de trabalho.

Se tudo deu certo: a pasta do bot contém o arquivo README.md e a pasta tools.

4. Abra um terminal na pasta do bot

Um terminal é uma janela onde você digita comandos. Abra o terminal da forma indicada para o seu sistema.

macOS: pressione Command+Space, digite Terminal e abra o app. Digite cd e um espaço, arraste a pasta do bot para a janela e pressione Enter.

Linux: no gerenciador de arquivos, clique com o botão direito na pasta do bot e escolha Abrir no terminal. O nome do menu pode variar entre distribuições. Se esse menu não existir, abra um terminal e use cd como no macOS.

Windows: abra a pasta do bot no Explorador de Arquivos. Digite powershell na barra de endereços, no topo, e pressione Enter. Uma janela do PowerShell se abre nessa pasta.

Verificação: digite o comando abaixo e pressione Enter. Deu certo se README.md e tools aparecerem na lista. Este comando é o mesmo no macOS, no Linux e no PowerShell do Windows.

ls

5. Verifique o Python

Na mesma janela, copie o comando do seu sistema, cole e pressione Enter. Você pode usar o botão de copiar no canto superior direito de cada quadro de código.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Se tudo deu certo: aparece uma versão que começa com 3, como Python 3.11.9. A versão 3.9 ou mais recente funciona. Se o comando não for encontrado, instale o Python como indicado abaixo, feche a janela e recomece pelo passo 4.

macOS: baixe o instalador para macOS (.pkg) em python.org e instale. Depois, execute uma vez o Install Certificates.command que fica na pasta do Python, dentro de Aplicativos. Isso prepara os certificados necessários para conexões seguras (HTTPS).

Linux: instale o python3 com a central de programas ou o gerenciador de pacotes da sua distribuição.

Windows: instale o gerenciador de instalação do Python (Python install manager) a partir de python.org.

python.org

6. Informe o token

Coloque o token em uma variável de ambiente, uma configuração temporária que só existe dentro desta janela do terminal. O nome dela é FS_BOT_TOKEN. Copie abaixo o comando de uma linha do seu sistema, cole e pressione Enter uma vez. Então aparece Bot token, esperando você digitar. Agora cole o token que você copiou no passo 2 e pressione Enter.

macOS / Linux

printf 'Bot token: '; read -r -s FS_BOT_TOKEN; printf '\n'; export FS_BOT_TOKEN

Windows PowerShell

$env:FS_BOT_TOKEN = [Net.NetworkCredential]::new('', (Read-Host 'Bot token' -AsSecureString)).Password

No macOS e no Linux o token colado não aparece na tela, e no Windows você vê * no lugar dos caracteres. As duas coisas são normais. Como o token não é escrito no comando, ele não fica no histórico de comandos. A configuração vale apenas nesta janela. Se você fechou a janela ou abriu uma nova, repita os passos 4 e 6.

Windows: no PowerShell, cole com Ctrl+V ou com o botão direito do mouse. Este comando de uma linha usa uma sintaxe que funciona tanto no Windows PowerShell 5.1, que vem com o Windows, quanto no PowerShell 7, mais novo, mas ainda não foi testado em um computador Windows de verdade.

Se tudo deu certo: o prompt para o próximo comando volta sem nenhum erro.

7. Inicie o conector

Na mesma janela, copie o comando abaixo sem alterações, cole e pressione Enter. Não mude os nomes de arquivo nele. O seu bot se conecta com o motor de treino, um motor muito fraco que joga na casa legal de menor número.

macOS / Linux

python3 tools/bot_match_client.py --engine 'python3 tools/bot_match_example_engine.py'

Windows PowerShell

py tools/bot_match_client.py --engine 'py tools/bot_match_example_engine.py'

É normal que o comando não mostre texto nem volte ao prompt enquanto estiver em execução. Para manter seu bot online, deixe esta janela aberta com o programa de conexão e o motor em execução e impeça o computador de entrar em suspensão. Fechar esta janela ou pressionar Ctrl+C encerra os dois programas e deixa seu bot offline. Execute novamente o comando acima para colocá-lo online. O programa de conexão aceita automaticamente os pedidos de outros bots.

Enquanto o bot roda com o motor de treino, ele não recebe confrontos automáticos, e os confrontos contra outros bots, mesmo os que você pedir, não contam para o rating. Reinicie-o com o seu próprio motor (passo 10) e a partir daí ele é pareado e conta para o rating.

Se tudo deu certo: na lista Bots registrados da página de bots, o ponto à direita do nome do seu bot fica verde. Ao passar o mouse sobre ele, aparece Online. Se estiver cinza (Offline), pressione Atualizar.

8. Teste com um primeiro jogo

Na página de bots, escolha o seu bot em Meu bot jogador. Escolha qualquer bot da lista Bots FlipSight, pressione Desafiar e confirme na janela que se abre. Em todos os tipos de jogo o servidor calcula os lances dos bots FlipSight, então não é preciso manter a sala de jogo aberta. Com o pareamento automático ativado (padrão), o site também marca confrontos de bots enquanto o conector estiver rodando.

Se tudo deu certo: a sala de jogo mostra dois tabuleiros e uma peça é colocada em cada vez do seu bot. O motor de treino é muito fraco, então perder é normal.

9. Manter rodando após fechar a janela (opcional)

Inicie com o comando abaixo em vez do passo 7, e o bot continua rodando depois que você fechar a janela. Execute o comando na mesma janela em que você informou o token (passo 6). Ele para se o computador desligar ou entrar em suspensão. Se você o parar durante um jogo, esse jogo é perdido por tempo ou por desconexão.

macOS / Linux

macOS / Linux: depois de executar o comando de início, você pode fechar a janela. As mensagens são gravadas no arquivo bot.log da pasta do bot. Para parar, abra um novo terminal e execute o comando de parada. Copie só o quadro de que você precisa: se colar os dois quadros de uma vez, o bot para logo depois de iniciar.

Iniciar

nohup python3 tools/bot_match_client.py --engine 'python3 tools/bot_match_example_engine.py' > bot.log 2>&1 &

Parar

pkill -f bot_match_client.py

Windows PowerShell

Windows: uma janela minimizada aparece na barra de tarefas e o bot roda nela. Você pode fechar a janela atual do PowerShell. Para parar, abra a janela minimizada e pressione Ctrl+C, ou feche essa janela.

Iniciar

Start-Process py -ArgumentList 'tools/bot_match_client.py --engine "py tools/bot_match_example_engine.py"' -WorkingDirectory $PWD -WindowStyle Minimized

10. Troque para o seu próprio motor

Após testar a conexão, troque para seu motor: opções dedicadas abaixo para Edax ou Egaroucid, --nboard para outros motores compatíveis, JSON para seu próprio motor. Mantenha o mesmo token.

Marque uma variante (Anti, 10x10, Octagon, Reversi clássico) em Jogos compatíveis somente se o seu motor conhecer as regras dela. Outros bots e pessoas podem desafiar o seu bot em todos os jogos marcados. Para mudar, abra Gerenciar na lista de bots, edite Jogos compatíveis e pressione Salvar. Não é possível mudar enquanto um jogo está em andamento ou um desafio está pendente.

Exemplos de solicitação ao motor por jogo

Conectar um motor compatível com NBoard, como Edax

NBoard é uma aplicação gráfica de Othello que pode ser usada com NTest. O protocolo NBoard é seu formato público de comunicação com motores e funciona com motores que o implementam, como NTest e Edax.

Os exemplos de instalação e execução abaixo usam Edax 4.6 e Egaroucid for Console 7.8.2.

O Edax 4.6 não está incluído no programa de conexão. Baixe o arquivo do seu sistema em Assets na página Edax 4.6, coloque-o na pasta do bot e extraia-o com os comandos de preparação abaixo. Os comandos Windows e Linux são para computadores x86-64.

Windows PowerShell (x86-64): edax-4.6-MS-windows-x86.zip

Preparar (só na primeira vez)

Expand-Archive -LiteralPath edax-4.6-MS-windows-x86.zip -DestinationPath edax-4.6

Iniciar

py tools/bot_match_client.py --edax "edax-4.6\wEdax-x86-64.exe"

Linux (x86-64): edax-4.6-linux-x86.tar.gz

Preparar (só na primeira vez)

mkdir -p edax-4.6
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6
chmod +x edax-4.6/lEdax-x86-64

Iniciar

python3 tools/bot_match_client.py --edax "edax-4.6/lEdax-x86-64"

macOS: edax-reversi-4.6.tar.gz

Não há executável oficial do Edax 4.6 para macOS. Baixe Source code (tar.gz) e edax-4.6-linux-x86.tar.gz da mesma versão. Compile o executável Mac a partir do código-fonte; do arquivo Linux, extraia apenas o arquivo de avaliação comum data/eval.dat. São necessárias as ferramentas de linha de comando do Xcode. Os comandos de preparação compilam o código-fonte.

Preparar (só na primeira vez)

xcode-select --install
tar -xzf edax-reversi-4.6.tar.gz
mv edax-reversi-4.6 edax-4.6
mkdir -p edax-4.6/bin
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6 data/eval.dat
(cd edax-4.6/src && make build ARCH=native OS=osx)

Iniciar

python3 tools/bot_match_client.py --edax "edax-4.6/bin/mEdax-native"

Edax também pode conectar-se indicando apenas o executável com --edax. Não é necessário um adaptador; o conector distribui o tempo de reflexão por lance conforme o relógio restante.

Substitua o caminho no comando pelo caminho completo do executável. Use aspas duplas em caminhos com espaços. Se o arquivo de avaliação não for encontrado automaticamente, indique eval.dat com --edax-eval.

Uma conexão NBoard joga apenas jogos padrão 8×8. Deixe marcado só Padrão em Jogos compatíveis para este bot. Se chegar uma variante, o conector não consulta o motor, nenhum lance é feito e o jogo é anulado ou perdido por tempo.

Egaroucid: --egaroucid

Prepare Egaroucid for Console. Escolha o executável de console e mantenha resources no lugar. No macOS e Linux, use um executável compilado seguindo as instruções oficiais. O conector envia posição e tempo restante com setboard, settime e go, sem adaptador.

Egaroucid for Console

Windows PowerShell
py tools/bot_match_client.py --egaroucid "egaroucid\Egaroucid_for_console.exe"
macOS / Linux
python3 tools/bot_match_client.py --egaroucid "egaroucid/bin/Egaroucid_for_Console.out"

Os dois modos aceitam apenas o jogo padrão 8×8, com um motor por partida simultânea. --max-think-seconds é o limite de segurança por lance (10 segundos por padrão). Se Egaroucid o atingir, a busca é interrompida, um lance legal de emergência é jogado e o motor reinicia no próximo turno.

Motor próprio: conectar por JSON

Leia esta parte só se você quiser conectar um motor que você mesmo escreveu. Se você usa Edax, Egaroucid ou outro motor NBoard, já terminou acima e pode pular esta parte.

Cada vez que chega a vez do seu bot, o conector executa o my_adapter.py uma vez. Ele entrega a posição atual em uma linha (onde estão as peças, de quem é a vez, quais casas são legais, quanto tempo resta), o my_adapter.py responde com uma casa em uma linha, e o conector joga esse lance no servidor. O my_adapter.py é um pequeno arquivo Python que fica no meio e pede o lance ao seu motor. O conector cuida da conexão com o servidor, do token e do envio dos lances, então esse arquivo nunca precisa lidar com isso.

FlipSight→ bot_match_client.py→ my_adapter.py
FlipSight← bot_match_client.py← my_adapter.py

De que tipo é o seu motor?

  • Um motor que escolhe o lance em código Python: use o exemplo A abaixo.
  • Um arquivo de programa que, cada vez que é executado, imprime uma linha de resposta e fecha: use o exemplo B abaixo.
  • Um executável que fica aberto e troca comandos continuamente: se suportar NBoard, conecte-o com --nboard acima. Com outro protocolo, adapte o exemplo B a esse protocolo e faça my_adapter.py manter o motor aberto e trocar uma linha por vez em um laço.

Passo 1: um arquivo que só verifica a conexão (my_adapter.py)

Na pasta do bot (onde estão README.md e tools), crie um arquivo chamado my_adapter.py, cole o código abaixo do jeito que está e salve. Esse arquivo não usa nenhum motor e simplesmente responde com a primeira casa legal. Ele serve para confirmar que a conexão funciona antes de você ligar o seu motor.

import json
import sys

def choose_move(position):
    legal = position["legal_moves"]
    return legal[0] if legal else -1

line = sys.stdin.readline()
position = json.loads(line)
move = choose_move(position)
print(json.dumps({"move": move}), flush=True)

Verifique a entrada e a saída antes de conectar

O comando do quadro 1 passa a posição de exemplo direto para o adaptador. Ele deve imprimir uma linha: {"move": 19}. Quando essa linha aparecer, use o comando do quadro 2 para iniciar o conector com este adaptador, na janela que tem o token do passo 6. Execute os dois quadros separadamente. Se o seu motor já lê este formato JSON, você pode colocar o comando dele logo após --engine.

1. Testar só o adaptador

macOS / Linux

printf '%s\n' '{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | python3 my_adapter.py

Windows PowerShell

'{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | py my_adapter.py

2. Iniciar o conector com o adaptador

macOS / Linux

python3 tools/bot_match_client.py --engine 'python3 my_adapter.py'

Windows PowerShell

py tools/bot_match_client.py --engine 'py my_adapter.py'

Passo 2: trocar por um arquivo que chama o seu motor

Quando o passo 1 funcionar, substitua todo o conteúdo de my_adapter.py pelo exemplo A ou B abaixo. Altere só as linhas marcadas com EDIT e deixe o resto como está. Depois execute de novo o comando de teste 1 acima e veja se aparece uma linha de resposta. Quem escolheu o lance foi o seu motor, então é normal aparecer uma casa diferente de 19.

Exemplo A: um motor que escolhe o lance com uma função Python

Este exemplo pressupõe um arquivo chamado my_engine.py com uma função search que escolhe o lance. Em EDIT 1, mude o nome do arquivo e o nome da função e, em EDIT 2, mude os valores passados, para que correspondam ao seu motor. A função deve retornar o número da casa (0 a 63) ou uma coordenada como d3.

import json
import sys
from pathlib import Path

from my_engine import search  # EDIT 1: your engine file and function name


def think_ms(position):
    # Time for this move: share the remaining clock over my remaining moves.
    my_moves_left = position["board"].count(0) // 2 + 1
    share = position["remaining_ms"] / my_moves_left
    return int(min(share, position["budget_ms"]) * 0.8)


def choose_move(position):
    if not position["legal_moves"]:
        return -1
    # EDIT 2: pass what your function needs
    return search(
        board=position["board"],
        color=position["color"],
        legal_moves=position["legal_moves"],
        time_limit_ms=think_ms(position),
    )


position = json.loads(sys.stdin.readline())
legal = position["legal_moves"]
try:
    move = choose_move(position)
except Exception as error:
    # The engine failed: note why in my_adapter.log and play the first legal square.
    with open(Path(__file__).with_name("my_adapter.log"), "a") as log:
        log.write(repr(error) + "\n")
    move = legal[0] if legal else -1
print(json.dumps({"move": move}), flush=True)
Exemplo B: um motor que é um arquivo de programa

Este exemplo pressupõe que o programa lê uma linha com 64 caracteres do tabuleiro, o lado que joga e o tempo de reflexão em milissegundos, e responde com uma linha de coordenada como D3 (. = vazia, X = pretas, O = brancas). Cada motor tem seu próprio formato, então mude o nome do arquivo de programa em EDIT 1, a linha enviada ao motor em EDIT 2 e a parte que lê a resposta em EDIT 3 para que correspondam ao seu motor.

my_adapter.py → my_engine

...........................OX......XO........................... X 1777

my_engine → my_adapter.py

D3
import json
import subprocess
import sys
from pathlib import Path

# EDIT 1: your engine program file (kept in the same folder as my_adapter.py)
ENGINE = Path(__file__).with_name("my_engine.exe" if sys.platform == "win32" else "my_engine")


def think_ms(position):
    # Time for this move: share the remaining clock over my remaining moves.
    my_moves_left = position["board"].count(0) // 2 + 1
    share = position["remaining_ms"] / my_moves_left
    return int(min(share, position["budget_ms"]) * 0.8)


def choose_move(position):
    if not position["legal_moves"]:
        return -1
    # EDIT 2: build the line your engine reads
    board = "".join(".XO"[cell] for cell in position["board"])
    color = "X" if position["color"] == 1 else "O"
    reply = subprocess.run(
        [str(ENGINE)],
        input=f"{board} {color} {think_ms(position)}\n",
        text=True,
        capture_output=True,
        check=True,
        timeout=position["budget_ms"] / 1000 * 0.9,
    )
    # EDIT 3: take the square out of your engine's answer
    return reply.stdout.strip()


position = json.loads(sys.stdin.readline())
legal = position["legal_moves"]
try:
    move = choose_move(position)
except Exception as error:
    # The engine failed: note why in my_adapter.log and play the first legal square.
    with open(Path(__file__).with_name("my_adapter.log"), "a") as log:
        log.write(repr(error) + "\n")
    move = legal[0] if legal else -1
print(json.dumps({"move": move}), flush=True)

Tempo de reflexão: nas partidas entre bots, cada bot tem 1 minuto por partida, então pensar muito em um único lance faz perder por tempo rapidamente. Nos exemplos, think_ms divide o tempo restante (remaining_ms) pelos lances que ainda faltam para você e assim define o tempo deste lance. budget_ms não é o tempo a gastar neste lance. É o tempo máximo que o conector espera pela resposta (10 segundos por padrão). Se a resposta chegar depois disso, o bot não consegue continuar nessa partida e a perde por tempo, então passe think_ms ao seu motor, não budget_ms.

Quando algo dá errado: se a chamada ao motor falhar, os arquivos de exemplo escrevem o motivo em my_adapter.log e jogam a primeira casa legal no lugar. Se os lances parecerem estranhos, abra esse arquivo primeiro. O conector não mostra na tela as mensagens de erro do adaptador. Use print só para a linha de resposta e escreva qualquer texto de depuração em um arquivo.

Velocidade: o conector mantém my_adapter.py aberto em cada partida e envia uma linha por jogada. Um arquivo que responde uma linha e termina, como os exemplos, é iniciado de novo na jogada seguinte, então seu motor também reinicia a cada vez. Se o motor demora para iniciar ou deve aproveitar o trabalho das jogadas anteriores, transforme my_adapter.py em um laço: ler uma linha, responder uma linha, repetir. Não leia a entrada até o fim e faça flush após cada resposta.

O restante desta parte é material de referência sobre o que o conector e o my_adapter.py trocam entre si. Se você usar os arquivos de exemplo acima do jeito que estão, não precisa ler.

JSON de entrada (conector → adaptador)

Estes são dados de exemplo enviados pelo conector, não código executável. Não os guarde como ficheiro Python. Durante a partida, o conector envia automaticamente os dados atuais em cada turno.

Abaixo está um exemplo da posição inicial padrão 8×8. Em jogos reais, o conector preenche a posição atual. board lista as casas a partir de a1 para a direita e depois a linha seguinte; 0=vazia, 1=preta, 2=branca. color=1 pede um lance das pretas. legal_moves lista as casas em que você pode jogar. remaining_ms é o tempo restante no relógio do jogo e budget_ms é o tempo máximo que o conector espera pela resposta deste lance, ambos em milissegundos (1000 = 1 segundo). Os campos e exemplos das variantes estão na seção de variantes abaixo.

{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

JSON de resposta (adaptador → conector)

Escreva uma das respostas abaixo como uma única linha JSON terminada com quebra de linha. move é um número de casa ou uma coordenada, sem diferença entre maiúsculas e minúsculas. No 8×8, d3 é (3−1)×8+3 = 19. Escolha sempre entre os legal_moves que você recebeu. Em pares de bots sem lance legal, envie -1 ou pass; em jogos contra pessoas, o servidor resolve os passes forçados. Escreva no stdout apenas a linha de resposta, sem nenhuma outra mensagem.

{"move":19}
{"move":"d3"}
{"move":-1}

Exemplos de solicitação ao motor por jogo

Exemplos da primeira solicitação para as pretas que o conector envia a um motor --engine, gerados com o código das regras. Os jogos reais de todos os tipos começam de uma posição aleatória com 12 peças, então board e legal_moves mudam a cada jogo. Para manter estes exemplos fixos, os lances iniciais foram escolhidos perto do centro do tabuleiro. match_id e os valores do relógio também são diferentes em jogos reais.

  • game_mode: Um de std, anti, grand, octagon, reversi. XOT em jogos contra pessoas chega como std.
  • board_size: 8, ou 10 para grand e octagon.
  • board: Array de tamanho board_size². Leia a partir de a1 para a direita e depois a linha seguinte. 0=vazia (incluindo as casas bloqueadas do Octagon), 1=preta, 2=branca.
  • blocked_squares: Casas inutilizáveis do Octagon. Array vazio nos outros jogos.
  • placed: Peças já colocadas por cor, incluindo os lances iniciais aleatórios; as chaves são as strings "1" e "2". Usado para o limite de 32 peças do Reversi clássico.

Campos da solicitação ao motor

Anti (anti)

JSON de entrada (conector → adaptador)

{
    "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "color": 1,
    "legal_moves": [2, 3, 12, 25, 42, 44, 45],
    "game_mode": "anti",
    "board_size": 8,
    "blocked_squares": [],
    "placed": {
        "1": 4,
        "2": 4
    },
    "remaining_ms": 60000,
    "match_id": 123,
    "leg": 0,
    "revision": 0,
    "history": [],
    "round_no": null,
    "room_created_at": null,
    "budget_ms": 10000
}

JSON de resposta (adaptador → conector)

{"move":2}
Grand (10x10) (grand)

JSON de entrada (conector → adaptador)

{
    "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "color": 1,
    "legal_moves": [13, 14, 25, 42, 63, 65, 66],
    "game_mode": "grand",
    "board_size": 10,
    "blocked_squares": [],
    "placed": {
        "1": 4,
        "2": 4
    },
    "remaining_ms": 60000,
    "match_id": 123,
    "leg": 0,
    "revision": 0,
    "history": [],
    "round_no": null,
    "room_created_at": null,
    "budget_ms": 10000
}

JSON de resposta (adaptador → conector)

{"move":13}
Octagon (octagon)

JSON de entrada (conector → adaptador)

{
    "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "color": 1,
    "legal_moves": [13, 14, 25, 42, 63, 65, 66],
    "game_mode": "octagon",
    "board_size": 10,
    "blocked_squares": [0, 1, 10, 8, 9, 19, 80, 90, 91, 89, 98, 99],
    "placed": {
        "1": 4,
        "2": 4
    },
    "remaining_ms": 60000,
    "match_id": 123,
    "leg": 0,
    "revision": 0,
    "history": [],
    "round_no": null,
    "room_created_at": null,
    "budget_ms": 10000
}

JSON de resposta (adaptador → conector)

{"move":13}
Reversi clássico (reversi)

JSON de entrada (conector → adaptador)

{
    "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "color": 1,
    "legal_moves": [18, 25, 33, 41, 42, 50, 51],
    "game_mode": "reversi",
    "board_size": 8,
    "blocked_squares": [],
    "placed": {
        "1": 6,
        "2": 6
    },
    "remaining_ms": 60000,
    "match_id": 123,
    "leg": 0,
    "revision": 0,
    "history": [],
    "round_no": null,
    "room_created_at": null,
    "budget_ms": 10000
}

JSON de resposta (adaptador → conector)

{"move":18}

Problemas comuns

SintomaCausa e solução
O bot mostra um ponto cinza (offline).Verifique se a janela do conector ainda está rodando. Ele para quando você fecha a janela, pressiona Ctrl+C ou o computador entra em suspensão. Repita os passos 6 e 7 e pressione Atualizar.
Aparece que o comando não foi encontrado.
command not found / is not recognized
O Python não está instalado, ou a janela não foi reaberta depois da instalação. Veja o passo 5, abra uma nova janela e recomece pelo passo 4.
Aparece que o arquivo não pode ser aberto.
can't open file
Você executou o comando fora da pasta do bot. Vá para a pasta do bot como no passo 4 e confira com ls se README.md e tools aparecem na lista.
Aparece que o token está faltando e o programa fecha.
FS_BOT_TOKEN is missing or invalid
Esta janela não tem token. Se você abriu uma nova janela, repita o passo 6.
Aparece que o token foi recusado e o programa para.
Bot credential rejected
O token foi colado errado ou é antigo. Depois que você emite um novo token ou desativa o bot, o token antigo deixa de funcionar. Recomece pelo passo 6 com o token mais recente.
A mesma linha de erro continua aparecendo.
Connection/state error
Verifique a conexão com a internet. No macOS, confira se você executou o Install Certificates.command do passo 5. Se o erro só aparece durante os jogos, o servidor pode estar recusando o lance do seu motor como ilegal. O conector envia o mesmo lance de novo, então é preciso corrigir o motor.
O botão de desafio não funciona, ou o desafio não é aceito.Os dois bots precisam estar com o ponto verde. Confira se o seu bot está selecionado em Meu bot jogador. Um bot que já está em outro jogo não aceita um novo desafio. Um desafio não aceito expira após 120 segundos.
O bot não faz nenhum lance e depois perde por tempo, ou o jogo é anulado.
Engine failed for match
O motor não respondeu a tempo ou a resposta veio no formato errado. Confira o comando do motor e a saída JSON de uma linha. Se um bot não fizer o primeiro lance em até 30 segundos após o início, o jogo é anulado.
Não chegam desafios de variantes.A janela de desafio mostra apenas os jogos que os dois bots suportam. Confira se o jogo está marcado em Jogos compatíveis do seu bot e se o outro bot também o suporta. Um bot conectado por NBoard deve ter apenas Padrão marcado.

Glossário

TermoSignificado
APIUma forma combinada de os programas enviarem pedidos e respostas. O conector cuida disso para você.
TokenUma senha só do seu bot. O conector usa o token para provar ao servidor qual bot ele é.
TerminalUma janela onde você digita comandos. No Windows, use o PowerShell.
Variável de ambienteUma configuração temporária passada aos programas iniciados a partir de uma janela do terminal. Ela some quando a janela é fechada.
JSONUm formato para escrever dados como texto. Exemplo: {"move":19}
MotorUm programa que analisa uma posição e escolhe um lance.
AdaptadorUm pequeno programa que converte dados entre o seu motor e o conector.
NBoardNBoard é uma aplicação gráfica de Othello que pode ser usada com NTest. O protocolo NBoard é seu formato público de comunicação com motores e funciona com motores que o implementam, como NTest e Edax.
Lance legalUma casa em que você pode jogar agora. O servidor lista essas casas em legal_moves.

Downloads

Baixar exemplos Python

OpenAPI 3.1 JSON

README.md

Entre para baixar. .zip contém guia, conector e motor de exemplo; .json é a especificação da API e .md é o guia.

Especificação detalhada

O restante desta página é uma referência detalhada para desenvolvedores que trabalham diretamente com um motor ou com a API.

Opções do conector

Opções de tools/bot_match_client.py. É obrigatório usar exatamente uma das opções --engine e --nboard / --edax / --egaroucid. O token nunca é uma opção; ele é lido apenas da variável de ambiente FS_BOT_TOKEN. Códigos de saída: 0 parada normal, 2 token recusado, 3 --max-runtime atingido.

ParâmetrosDescrição
--engine "COMMAND"Inicia este comando uma vez por partida e troca uma linha JSON por jogada sem fechar a entrada. Um motor que responde uma linha e termina é iniciado de novo na jogada seguinte.
--nboard "COMMAND"Mantém um motor NBoard por partida, dois no total. Somente padrão (std).
--edax PATHEdax também pode conectar-se indicando apenas o executável com --edax. Não é necessário um adaptador; o conector distribui o tempo de reflexão por lance conforme o relógio restante.
--edax-eval PATHCaminho de eval.dat do Edax. Se omitido, procura data/eval.dat na pasta do executável ou na pasta superior.
--egaroucid PATHPrepare Egaroucid for Console. Escolha o executável de console e mantenha resources no lugar. No macOS e Linux, use um executável compilado seguindo as instruções oficiais. O conector envia posição e tempo restante com setboard, settime e go, sem adaptador.
--nboard-depth NEnvia set depth N ao motor NBoard. De 0 a 60; 0 mantém o padrão do motor. (--edax: 0 → 60).
--max-think-seconds SMáximo de segundos de espera pelo motor por lance. Padrão 10, máximo 300. Nunca passa do tempo restante no relógio menos 0,75 segundo. É um limite de segurança do conector; o servidor não tem limite de tempo por lance. Com 0, o limite por lance é removido e vale o tempo restante da partida.
--engine-name NAMENome do motor informado nas informações do bot (até 120 caracteres). Não inclua comandos nem caminhos.
--cpu-name NAMENome da CPU a informar (até 120 caracteres). É detectado automaticamente se omitido.
--once-match IDTrata apenas este confronto e sai quando ele termina (finalizado, anulado, expirado, recusado). IDs negativos de jogos contra pessoas também funcionam. Para testes, combine com --max-runtime.
--max-runtime SEncerra os motores e sai após esse número de segundos (código de saída 3).
--no-streamNão usa o fluxo de eventos; apenas consulta periodicamente.
--api URL, --stream-url URLEndereços da API e do fluxo. Normalmente, mantenha os padrões. Só HTTPS é aceito e, como o token é enviado junto, o fluxo precisa estar no host da API.

Implementar a API por conta própria

Receba desafios e posições com agent_poll ou com o fluxo de eventos autenticado. Envie agent_accept para desafios pendentes em que você é bot_b. Depois, calcule apenas as posições em que é sua vez em cada partida de um confronto em andamento. Envie no máximo uma vez por match_id, partida e revision, e descarte o trabalho desatualizado quando o estado mudar. Jogos contra pessoas usam match_id negativo e leg=0, e exigem round_no e room_created_at exatamente como recebidos. Em salas normais, o servidor trata os passes forçados; só os pares de bots enviam passes.

agent_poll / bot-stream → pending (bot_b = my_id)
→ agent_accept → running → legs[0] + legs[1]
→ agent_move(match_id, leg, revision, move) → finished / aborted

Variantes

Um bot recebe apenas os jogos marcados em Jogos compatíveis. O game_mode nas solicitações ao motor e no estado do confronto indica o jogo. O servidor calcula o tamanho do tabuleiro, as casas bloqueadas e as casas legais, então o motor só precisa escolher entre os legal_moves que recebe. Não gere lances de 10×10 ou de Reversi clássico com a geração de lances do padrão 8×8.

Jogogame_modeTabuleiroPeças iniciaisRelógio (por partida, por cor)Primeiros legal_moves das pretas
Padrãostd8×812 peças aleatórias60,000 msDepende da posição
Antianti8×812 peças aleatórias60,000 msDepende da posição
Grand (10x10)grand10×1012 peças aleatórias60,000 msDepende da posição
Octagonoctagon10×10 (88)12 peças aleatórias60,000 msDepende da posição
Reversi clássicoreversi8×812 peças aleatórias60,000 msDepende da posição

Regras comuns: as pretas (1) jogam primeiro. As duas partidas começam da mesma posição com as cores trocadas e são jogadas ao mesmo tempo. Em pares de bots, quando você não tem lance legal, legal_moves chega como um array vazio e o próprio bot precisa enviar move=-1 (passe). O servidor não passa por você, e o seu relógio continua correndo. Passar quando existe um lance legal é ilegal. Uma partida termina quando nenhuma cor pode jogar. Casas vazias não são dadas a ninguém; somam-se apenas as peças reais das duas partidas, e totais iguais dão empate. Todas as partidas entre bots começam numa posição aleatória com 12 peças. Em todos os tipos de jogo, cada bot tem 1 minuto por partida.

Anti: colocar e virar peças funciona como no Othello padrão. Vence o lado com menos peças somando as duas partidas.

10x10: regras padrão em um tabuleiro 10×10 (100 casas). As peças iniciais ficam nas quatro casas centrais; os pares de bots acrescentam 8 lances aleatórios e começam com 12 peças.

Octagon: o mesmo tabuleiro e a mesma disposição inicial do 10x10, mas três casas em cada canto, 12 no total, são inutilizáveis (88 casas jogáveis). As casas bloqueadas chegam como números de casa em blocked_squares e como 0, o mesmo valor de vazio, em board. O array não está em ordem numérica. Não é possível jogar numa casa bloqueada, e as linhas de peças param nela.

Reversi clássico: pelas regras, começa com o tabuleiro vazio. Enquanto qualquer uma das quatro casas centrais (27, 28, 35, 36) estiver vazia, você só pode colocar peças ali e nada é virado. Quando as quatro estiverem preenchidas, valem as regras padrão. Cada cor coloca no máximo 32 peças, incluindo os quatro primeiros lances. placed conta as peças que cada cor já colocou; as chaves são as strings "1" (pretas) e "2" (brancas). Os pares de bots começam depois que o servidor coloca 12 peças aleatórias, incluindo os quatro lances centrais, então na primeira solicitação os dois valores de placed somam 12 (normalmente {"1":6,"2":6}). Uma cor que já colocou as 32 recebe legal_moves vazio e envia -1 em cada uma das suas vezes.

Exemplos de solicitação ao motor por jogo

Exemplos da primeira solicitação para as pretas que o conector envia a um motor --engine, gerados com o código das regras. Os jogos reais de todos os tipos começam de uma posição aleatória com 12 peças, então board e legal_moves mudam a cada jogo. Para manter estes exemplos fixos, os lances iniciais foram escolhidos perto do centro do tabuleiro. match_id e os valores do relógio também são diferentes em jogos reais.

Anti (anti)

{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"anti","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

Grand (10x10) (grand)

{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"grand","board_size":10,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

Octagon (octagon)

{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"octagon","board_size":10,"blocked_squares":[0,1,10,8,9,19,80,90,91,89,98,99],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

Reversi clássico (reversi)

{"board":[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,2,2,1,1,0,0,0,0,2,2,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[18,25,33,41,42,50,51],"game_mode":"reversi","board_size":8,"blocked_squares":[],"placed":{"1":6,"2":6},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

Campos da solicitação ao motor

CampoTipoDescrição
game_modestringUm de std, anti, grand, octagon, reversi. XOT em jogos contra pessoas chega como std.
board_sizeinteger 8 | 108, ou 10 para grand e octagon.
boardinteger[board_size²]Array de tamanho board_size². Leia a partir de a1 para a direita e depois a linha seguinte. 0=vazia (incluindo as casas bloqueadas do Octagon), 1=preta, 2=branca.
blocked_squaresinteger[]Casas inutilizáveis do Octagon. Array vazio nos outros jogos.
placed{"1": integer, "2": integer}Peças já colocadas por cor, incluindo os lances iniciais aleatórios; as chaves são as strings "1" e "2". Usado para o limite de 32 peças do Reversi clássico.
legal_movesinteger[]Casas em que você pode jogar agora. Se estiver vazio, responda move=-1.
colorinteger 1 | 2Cor que joga. 1=preta, 2=branca.
budget_msintegerTempo permitido para este lance em milissegundos: o menor entre --max-think-seconds e o tempo restante no relógio menos 0,75 segundo. O conector para o motor quando ele é excedido.
remaining_msintegerTempo restante no relógio da sua cor nesta partida, em milissegundos, já descontado o tempo usado nesta vez.
match_idinteger ≠ 0ID do confronto. Negativo (-número da sala) em jogos contra pessoas.
leginteger 0 | 1Número da partida, 0 ou 1. Sempre 0 em jogos contra pessoas.
revisionintegerNúmero de estado desta partida. Envie-o sem alteração com agent_move.
historyarrayLances jogados nesta partida. Decida a partir de board.
round_nointeger | nullNúmero da rodada de um jogo contra pessoa. null em pares de bots.
room_created_atstring | nullHora de criação da sala do jogo contra pessoa. null em pares de bots.

Desafiar outro bot para uma variante

Adicione game_mode ao agent_challenge; o padrão é std. Os dois bots precisam ter o jogo em supported_games. Veja os jogos do adversário em bots[].supported_games na resposta de agent_poll. Se algum dos bots não tiver o jogo, a resposta é HTTP 409 com o erro abaixo.

{"action":"agent_challenge","target_id":2,"game_mode":"anti"}
{"action":"agent_challenge","target_name":"Opponent","game_mode":"grand"}
HTTP 409
{"ok":false,"error":"unsupported game mode","server_now_ms":1800000000000}

Alterar supported_games

O formulário de registro começa com Padrão marcado, e bots antigos sem lista salva suportam std. Você só pode alterar a lista em Gerenciar na página web de bots, nunca com o token do bot (API). A alteração é recusada enquanto um confronto está em andamento ou aguardando aceitação. Uma lista vazia não pode ser salva.

NBoard e variantes

Uma conexão --nboard suporta apenas o padrão (std). Quando chega uma variante, o conector não consulta o motor e desiste daquela vez (mensagem Engine failed for match). Nenhum lance é enviado, então o jogo é anulado antes do primeiro lance, ou aquela partida é perdida por tempo mais tarde. Configure um bot NBoard com o padrão como único jogo suportado.

Pessoas contra bots registrados

As pessoas podem desafiar o seu bot em qualquer jogo que ele tenha registrado. Se ele suporta o padrão, podem chegar tanto STD quanto XOT. XOT chega com game_mode std e começa de um tabuleiro em que 8 lances já foram jogados (eles estão incluídos em history). Quem desafia escolhe o relógio, e o jogo não vale para a classificação. Um jogo contra pessoa tem uma partida (leg 0) e um match_id negativo. O servidor trata os passes quando não há lance possível, então não envie -1. Bots registrados online com pareamento automático ativado também participam de partidas rápidas STD/XOT compatíveis. Elas valem para o rating dos membros, mas não dos convidados.

Lances ilegais e tempo

Um lance ilegal ou uma revision desatualizada é recusado com HTTP 409 (illegal_move, stale_revision, not_your_turn) e o tabuleiro não muda. Enquanto isso, o relógio continua correndo. O conector reenvia o mesmo lance com a mesma revision, então, a menos que o motor seja corrigido, aquela partida acaba perdida por tempo. O servidor não tem limite por lance e só verifica o tempo restante de cada cor. Os 10 segundos são o --max-think-seconds padrão do conector.

Autenticação

Use HTTPS, JSON POST e Authorization: Bearer. O token pertence a um bot. Nunca o coloque em URLs, código público ou registros.

Content-Type: application/json
Authorization: Bearer <FS_BOT_TOKEN>

POST https://othelloclub.com/flipsight/bot_match_api.php

Regras do confronto

Todas as partidas entre bots começam numa posição aleatória com 12 peças. Duas partidas são jogadas simultaneamente com cores trocadas. O total de peças decide o vencedor; no Anti vence o menor total.

Todas as partidas entre bots começam numa posição aleatória com 12 peças. Em todos os tipos de jogo, cada bot tem 1 minuto por partida.

Se um bot não fizer o primeiro lance em até 30 segundos após o início, o confronto é anulado e não vale para o ranking. Se durante o confronto a conexão de um bot cair por mais de 30 segundos enquanto o outro está conectado, o bot conectado vence (finish_reason: disconnect) e o resultado vale para o ranking.

O servidor calcula os lances dos bots FlipSight em todos os tipos de jogo. Desafios pela web e pela API (agent_challenge) são aceitos, e o servidor inicia o confronto na hora, então não é preciso manter a sala de jogo aberta. Se o cálculo do servidor parar por mais de 30 segundos, o confronto é anulado (finish_reason: technical) e não conta para o rating. Variantes são caras de calcular, então no máximo 2 confrontos de variante contra bots FlipSight acontecem ao mesmo tempo. Se as vagas estiverem cheias, você recebe HTTP 409 com site bot variant matches are full; tente de novo daqui a pouco.

Confrontos de bots automáticos: o site mantém sempre duas salas de confrontos de bots. Enquanto um bot registrado com o pareamento automático ativado estiver online e sem jogar, ele recebe um desafio de confronto de bots padrão (std) contra um bot com rating parecido (primeiro outro bot registrado; se não houver, um bot FlipSight), como bot_b. O conector aceita automaticamente e o confronto começa. Se o bot não aceitar em 120 segundos ou recusar, fica fora do pareamento automático por 10 minutos. Os confrontos automáticos contam para o rating. Os confrontos automáticos entre bots continuam durante os jogos da Arena. O pareamento automático vem ativado e pode ser desligado em Gerenciar na página de bots. Um bot iniciado com o motor de treino (environment.practice é true) fica fora dos confrontos automáticos, e os confrontos contra outros bots nesse período não contam para o rating.

Um desafio humano direto joga uma partida numa sala normal, com jogo e tempo compatíveis. O servidor sempre torna os desafios diretos não ranqueados. Bots registrados online com pareamento automático ativado também participam de partidas rápidas STD/XOT compatíveis. Elas valem para o rating dos membros, mas não dos convidados. Um bot registrado só aceita tantas partidas quanto a configuração de partidas simultâneas permite (padrão 2). Uma partida contra uma pessoa e um confronto entre bots (dois tabuleiros) contam 1 cada.

pending → running → finished / aborted
pending → rejected / expired
kind=human: id=-room_id, legs[0], round_no, room_created_at
agent_move: move=19 / "d3" / "D3" (8×8)
10×10: "j10" = "J0" = 99
pass: -1 / "pass" / "PA" / "PS" / "--" (paired games only)
index=row*board_size+column (0-based)

Fluxo do confronto

Todas as partidas entre bots começam numa posição aleatória com 12 peças. Duas partidas são jogadas simultaneamente com cores trocadas. O total de peças decide o vencedor; no Anti vence o menor total.

Seu número de participante é 0 se você for bot_a e 1 se for bot_b. O participante 0 joga de pretas (1) em legs[0] e de brancas (2) em legs[1]; o participante 1, o contrário. É sua vez quando leg.status é active e leg.turn é igual à sua cor. Pode ser sua vez nas duas partidas ao mesmo tempo.

Se não houver lance legal, você mesmo deve enviar move -1 (passe); o servidor não passa por você. Em partidas contra uma pessoa, o servidor trata os passes como nas salas de jogo normais.

Na sua vez, tempo restante = clocks_ms[sua cor - 1] - (server_now_ms - turn_started_ms). clocks_ms segue a ordem das cores (preto, branco); totals e winner seguem a ordem dos participantes (winner 0 ou 1, null em caso de empate).

Números das casas

Número da casa = linha × 8 + coluna, então a1=0, h1=7, a2=8 e h8=63. Por exemplo, 19 é d3 e 37 é f5. O array board usa a mesma ordem, com 0=vazia, 1=preta, 2=branca.

    a  b  c  d  e  f  g  h
1   0  1  2  3  4  5  6  7
2   8  9 10 11 12 13 14 15
3  16 17 18 19 20 21 22 23
4  24 25 26 27 28 29 30 31
5  32 33 34 35 36 37 38 39
6  40 41 42 43 44 45 46 47
7  48 49 50 51 52 53 54 55
8  56 57 58 59 60 61 62 63

No 10×10 (grand, octagon), casa = linha × 10 + coluna. As colunas vão de a a j, e a décima linha é escrita como 10 ou 0 nas coordenadas. a1=0, j1=9, a2=10, j10=99 (igual a j0). A mesma coordenada tem um número diferente em cada tamanho de tabuleiro: d3 é 19 no 8×8 e 23 no 10×10.

     a  b  c  d  e  f  g  h  i  j
  1   0  1  2  3  4  5  6  7  8  9
  2  10 11 12 13 14 15 16 17 18 19
  3  20 21 22 23 24 25 26 27 28 29
  4  30 31 32 33 34 35 36 37 38 39
  5  40 41 42 43 44 45 46 47 48 49
  6  50 51 52 53 54 55 56 57 58 59
  7  60 61 62 63 64 65 66 67 68 69
  8  70 71 72 73 74 75 76 77 78 79
  9  80 81 82 83 84 85 86 87 88 89
 10  90 91 92 93 94 95 96 97 98 99

Octagon (octagon)

     a  b  c  d  e  f  g  h  i  j
  1  ## ##  2  3  4  5  6  7 ## ##
  2  ## 11 12 13 14 15 16 17 18 ##
  3  20 21 22 23 24 25 26 27 28 29
  4  30 31 32 33 34 35 36 37 38 39
  5  40 41 42 43 44 45 46 47 48 49
  6  50 51 52 53 54 55 56 57 58 59
  7  60 61 62 63 64 65 66 67 68 69
  8  70 71 72 73 74 75 76 77 78 79
  9  ## 81 82 83 84 85 86 87 88 ##
 10  ## ## 92 93 94 95 96 97 ## ##

Fluxo de eventos

Conecte-se com o mesmo token Bearer a GET https://othelloclub.com/flipsight_api/bot-stream para receber um evento JSON por linha (NDJSON): hello confirma a conexão, match indica que um dos seus confrontos mudou (eventos entre bots incluem o objeto completo do confronto) e ping é um sinal de manutenção a cada 10 segundos. Ao receber match, chame agent_poll uma vez. Se a conexão cair, reconecte com um intervalo crescente de 1 a 30 segundos e continue consultando nesse meio-tempo. Enquanto o fluxo estiver conectado, seu bot aparece online; cada bot pode abrir até 3 fluxos.

Exemplos de eventos

curl -N 'https://othelloclub.com/flipsight_api/bot-stream' \
  -H "Authorization: Bearer $FS_BOT_TOKEN"

{"type":"hello","bot_id":1,"server_now_ms":1800000000000}
{"type":"match","match":{"id":123,"bot_a":1,"bot_b":2,"status":"running","state":{...}},"server_now_ms":1800000000500}
{"type":"match","match":{"id":-45,"kind":"human","room_id":45},"server_now_ms":1800000003000}
{"type":"ping","server_now_ms":1800000010000}

Adaptador do motor

O conector de bots do FlipSight (Python 3.9+, só biblioteca padrão) funciona no Windows, macOS e Linux e conecta um motor de duas formas. --engine inicia o motor uma vez por partida, envia uma linha JSON a cada jogada e lê uma linha {"move": casa}. Um motor que responde uma linha e termina continua funcionando, porque é iniciado de novo na jogada seguinte, mas um motor que lê a entrada até o fim (sys.stdin.read() etc.) nunca responde, então leia uma linha por vez. legal_moves na requisição lista as casas legais; se estiver vazia, responda -1. --nboard mantém um motor NBoard em execução, um por partida; ajuste a profundidade com --nboard-depth.

Edax também pode conectar-se indicando apenas o executável com --edax. Não é necessário um adaptador; o conector distribui o tempo de reflexão por lance conforme o relógio restante.

Prepare Egaroucid for Console. Escolha o executável de console e mantenha resources no lugar. No macOS e Linux, use um executável compilado seguindo as instruções oficiais. O conector envia posição e tempo restante com setboard, settime e go, sem adaptador.

JSON de entrada (conector → adaptador)

Estes são dados de exemplo enviados pelo conector, não código executável. Não os guarde como ficheiro Python. Durante a partida, o conector envia automaticamente os dados atuais em cada turno.

{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":40000,"match_id":123,"leg":1,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}

JSON de resposta (adaptador → conector)

{"move":2}

Sincronização

Com o fluxo conectado, chame agent_poll quando chegar um evento e a cada 10 segundos. Sem o fluxo, consulte a cada segundo durante um confronto e a cada 5 segundos em espera, nunca mais de uma vez por segundo. O tempo de rede é descontado do seu relógio. Se a resposta a um lance se perder, consulte o novo estado e reenvie o mesmo lance só se a revision ainda for a mesma.

Ações da API

Os exemplos de resposta mostram o formato. O botão de copiar copia apenas a requisição e não chama a API.

POST agent_poll

Consultar estado

ParâmetrosTipoObrigatórioDescrição
actionstring "agent_poll"ObrigatórioNome da ação a chamar
environmentobjectOpcionalCPU, SO e nome do motor do PC do conector (até 120 caracteres cada, opcional) e practice (true ou false, opcional). practice true significa que o bot está treinando: fica fora dos confrontos automáticos, e os confrontos entre bots pedidos ou aceitos nesse período não contam para o rating. O conector envia isso sozinho quando é iniciado com o motor de treino. O servidor guarda o último valor, então ao terminar o treino envie sem practice ou com false.

Exemplo de requisição

curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
  -H "Authorization: Bearer $FS_BOT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"action":"agent_poll","environment":{"cpu":"Apple M4","engine":"MyEngine","os":"Darwin"}}'

Exemplo de resposta

{
    "ok": true,
    "bots": [
        {
            "id": 1,
            "name": "Opponent",
            "owner_id": "example",
            "owner_name": "Example",
            "owner": {
                "player_id": "example",
                "name": "Example",
                "photo": "/img/no_profile.gif",
                "country_code": "",
                "country_name": "",
                "country_flag_url": "",
                "donor": false,
                "verified_player_id": ""
            },
            "engine_level": null,
            "rating": {
                "rating": 1200,
                "rd": 300,
                "official": false,
                "official_progress": {
                    "kind": "provisional",
                    "current": 0,
                    "required": 50
                },
                "color": "pz-rating-unofficial",
                "high_rating": 1200,
                "wins": 0,
                "losses": 0,
                "draws": 0,
                "streak": 0,
                "best_streak": 0
            },
            "supported_games": ["std"],
            "auto_match": true,
            "bio": "",
            "registered_at": "2026-11-12 18:00:00",
            "registered_at_ts": 1794474000,
            "environment": {
                "cpu": "Apple M4",
                "engine": "MyEngine",
                "os": "Darwin",
                "practice": false
            },
            "is_builtin": false,
            "online": true,
            "disabled": false,
            "deleted": false,
            "photo": "/flipsight/bot_match_api.php?action=photo&bot_id=1&v=1794474000000"
        },
        {
            "id": 2,
            "name": "MyBot",
            "owner_id": "example",
            "owner_name": "Example",
            "owner": {
                "player_id": "example",
                "name": "Example",
                "photo": "/img/no_profile.gif",
                "country_code": "",
                "country_name": "",
                "country_flag_url": "",
                "donor": false,
                "verified_player_id": ""
            },
            "engine_level": null,
            "rating": {
                "rating": 1200,
                "rd": 300,
                "official": false,
                "official_progress": {
                    "kind": "provisional",
                    "current": 0,
                    "required": 50
                },
                "color": "pz-rating-unofficial",
                "high_rating": 1200,
                "wins": 0,
                "losses": 0,
                "draws": 0,
                "streak": 0,
                "best_streak": 0
            },
            "supported_games": ["std", "anti"],
            "auto_match": true,
            "bio": "",
            "registered_at": "2026-11-12 18:00:00",
            "registered_at_ts": 1794474000,
            "environment": {
                "cpu": "Apple M4",
                "engine": "MyEngine",
                "os": "Darwin",
                "practice": false
            },
            "is_builtin": false,
            "online": true,
            "disabled": false,
            "deleted": false,
            "photo": "/flipsight/bot_match_api.php?action=photo&bot_id=2&v=1794474000000"
        }
    ],
    "record_bots": [],
    "matches": [
        {
            "id": 123,
            "bot_a": 1,
            "bot_b": 2,
            "game_mode": "std",
            "time_limit_sec": 60,
            "status": "running",
            "state": {
                "version": 1,
                "game_mode": "std",
                "time_limit_sec": 60,
                "status": "active",
                "revision": 1,
                "started_ms": 1799999980000,
                "last_now_ms": 1799999984200,
                "finished_ms": null,
                "opening_moves": [19, 20, 29, 34, 26, 11, 43, 37],
                "opening_history": [
                    {
                        "color": 1,
                        "move": 19
                    },
                    {
                        "color": 2,
                        "move": 20
                    },
                    {
                        "color": 1,
                        "move": 29
                    },
                    {
                        "color": 2,
                        "move": 34
                    },
                    {
                        "color": 1,
                        "move": 26
                    },
                    {
                        "color": 2,
                        "move": 11
                    },
                    {
                        "color": 1,
                        "move": 43
                    },
                    {
                        "color": 2,
                        "move": 37
                    }
                ],
                "legs": [
                    {
                        "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 1, 2, 2, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                        "turn": 2,
                        "black_participant": 0,
                        "placed": {
                            "1": 5,
                            "2": 4
                        },
                        "clocks_ms": [55800, 60000],
                        "turn_started_ms": 1799999984200,
                        "revision": 1,
                        "history": [
                            {
                                "color": 1,
                                "participant": 0,
                                "move": 44,
                                "at_ms": 1799999984200
                            }
                        ],
                        "status": "active",
                        "discs": [5, 8],
                        "finished_ms": null,
                        "legal_moves": [18, 21, 22, 25, 30, 33, 38, 42, 50, 51, 52]
                    },
                    {
                        "board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                        "turn": 1,
                        "black_participant": 1,
                        "placed": {
                            "1": 4,
                            "2": 4
                        },
                        "clocks_ms": [60000, 60000],
                        "turn_started_ms": 1799999980000,
                        "revision": 0,
                        "history": [],
                        "status": "active",
                        "discs": [3, 9],
                        "finished_ms": null,
                        "legal_moves": [2, 3, 12, 25, 42, 44, 45]
                    }
                ],
                "totals": [14, 11],
                "winner": null,
                "finish_reason": null,
                "board_size": 8,
                "blocked_squares": []
            },
            "auto": false,
            "expires_ms": 1800000080000,
            "ranked": true,
            "rating_applied": false,
            "rating_before": [null, null],
            "rating_after": [null, null],
            "created_at": "2026-11-12 19:00:00"
        }
    ],
    "me": "example",
    "bot": {
        "id": 2,
        "name": "MyBot",
        "owner_id": "example",
        "owner_name": "Example",
        "owner": {
            "player_id": "example",
            "name": "Example",
            "photo": "/img/no_profile.gif",
            "country_code": "",
            "country_name": "",
            "country_flag_url": "",
            "donor": false,
            "verified_player_id": ""
        },
        "engine_level": null,
        "rating": {
            "rating": 1200,
            "rd": 300,
            "official": false,
            "official_progress": {
                "kind": "provisional",
                "current": 0,
                "required": 50
            },
            "color": "pz-rating-unofficial",
            "high_rating": 1200,
            "wins": 0,
            "losses": 0,
            "draws": 0,
            "streak": 0,
            "best_streak": 0
        },
        "supported_games": ["std", "anti"],
        "auto_match": true,
        "bio": "",
        "registered_at": "2026-11-12 18:00:00",
        "registered_at_ts": 1794474000,
        "environment": {
            "cpu": "Apple M4",
            "engine": "MyEngine",
            "os": "Darwin",
            "practice": false
        },
        "is_builtin": false,
        "online": true,
        "disabled": false,
        "deleted": false,
        "photo": "/flipsight/bot_match_api.php?action=photo&bot_id=2&v=1794474000000"
    },
    "server_now_ms": 1800000000000
}

Objeto de confronto (matches[])

CampoTipoDescrição
idintegerID do confronto (negativo em partidas contra uma pessoa)
kindstringhuman em partidas contra uma pessoa
bot_aintegerID do bot do participante 0 (0 para uma pessoa)
bot_bintegerID do bot do participante 1 (0 para uma pessoa)
game_modestringJogo (std, anti, grand, octagon, reversi)
time_limit_secintegerSegundos dados por partida, por cor
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerPrazo de aceitação (milissegundos)
rankedbooleanSe o confronto é ranqueado
state.statusstringactive, finished ou aborted
state.game_modestringJogo (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Casas por lado, 8 ou 10
state.blocked_squaresinteger[]Casas inutilizáveis (só Octagon, sem ordenação)
state.legsarrayPartidas (2 entre bots, 1 contra uma pessoa)
state.totals[integer, integer]Total de peças das duas partidas por participante
state.winnerinteger | nullParticipante vencedor 0 ou 1, null em caso de empate
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Objeto de partida (state.legs[])

CampoTipoDescrição
boardinteger[64 | 100]64 / 100 casas, 0=vazia 1=preta 2=branca
turnintegerCor da vez, 1 (preto) ou 2 (branco)
legal_movesinteger[]Casas legais para a cor que joga. Vazio em partidas terminadas
placed{"1": integer, "2": integer}Peças colocadas por cor, chaves "1" (pretas) e "2" (brancas)
black_participantintegerParticipante que joga de pretas nesta partida
clocks_ms[number, number]Valores base do tempo restante na ordem das cores (preto, branco)
turn_started_msintegerHora do servidor no início da vez atual
revisionintegerAumenta 1 a cada mudança desta partida
statusstringactive, finished, timeout, stopped
historyarrayLances jogados (color, participant, move, at_ms)
discs[integer, integer]Número de peças na ordem das cores (preto, branco)
result_discs[integer, integer] (optional)Placar arbitrado opcional [preto, branco] para uma partida perdida por tempo; some este valor em vez das peças. Placares do lado que esgotou o tempo e do adversário: std e reversi 0 a 64, grand 0 a 100, octagon 0 a 88, anti 64 a 0. A outra partida continua, e os totais decidem o vencedor depois que as duas partidas terminam. Registros antigos já finalizados não são recalculados.

POST agent_challenge

Desafiar

Indique target_id (número do bot) ou target_name (nome do bot), disponíveis na lista bots da resposta de agent_poll. Os bots FlipSight integrados (is_builtin: true) também podem ser desafiados pela API em todos os tipos de jogo, e o servidor inicia o confronto na hora. Se já houver 2 confrontos de variante contra bots integrados, você recebe HTTP 409 com site bot variant matches are full.

Desafiar pela página de bots

ParâmetrosTipoObrigatórioDescrição
actionstring "agent_challenge"ObrigatórioNome da ação a chamar
target_idinteger ≥ 1OpcionalID do bot a desafiar
target_namestringOpcionalNome do bot a desafiar (em vez de target_id)
game_modestring std | anti | octagon | grand | reversiOpcionalJogo de um par de bots. O padrão é std. Os dois bots precisam suportar o jogo. Padrão / Anti / Octagon / Grand (10x10) / Reversi clássico

Exemplo de requisição

curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
  -H "Authorization: Bearer $FS_BOT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"action":"agent_challenge","target_id":2,"game_mode":"anti"}'

Exemplo de resposta

{
    "ok": true,
    "match_id": 123,
    "server_now_ms": 1800000000000
}

POST agent_accept

Aceitar

ParâmetrosTipoObrigatórioDescrição
actionstring "agent_accept"ObrigatórioNome da ação a chamar
match_idinteger ≥ 1ObrigatórioID do confronto (negativo em partidas contra uma pessoa)

Exemplo de requisição

curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
  -H "Authorization: Bearer $FS_BOT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"action":"agent_accept","match_id":123}'

Exemplo de resposta

{
    "ok": true,
    "match": {
        "id": 123,
        "status": "running",
        "bot_a": 1,
        "bot_b": 2
    },
    "server_now_ms": 1800000000000
}

POST agent_reject

Recusar

ParâmetrosTipoObrigatórioDescrição
actionstring "agent_reject"ObrigatórioNome da ação a chamar
match_idinteger ≥ 1ObrigatórioID do confronto (negativo em partidas contra uma pessoa)

Exemplo de requisição

curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
  -H "Authorization: Bearer $FS_BOT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"action":"agent_reject","match_id":123}'

Exemplo de resposta

{
    "ok": true,
    "match": {
        "id": 123,
        "status": "rejected",
        "bot_a": 1,
        "bot_b": 2
    },
    "server_now_ms": 1800000000000
}

POST agent_move

Enviar lance

ParâmetrosTipoObrigatórioDescrição
actionstring "agent_move"ObrigatórioNome da ação a chamar
match_idinteger ≠ 0ObrigatórioID do confronto (negativo em partidas contra uma pessoa)
round_nointeger ≥ 0OpcionalSó em partidas contra uma pessoa: round_no do objeto de confronto
room_created_atstringOpcionalSó em partidas contra uma pessoa: room_created_at do objeto de confronto
leginteger 0 | 1ObrigatórioPartida 0 ou 1 (0 em partidas contra uma pessoa)
revisioninteger ≥ 0Obrigatóriorevision da partida para a qual o lance foi calculado; 409 stale_revision se a partida mudou
moveinteger -1..99 | stringObrigatórioCasa 0-(board_size²−1), -1 para passar / d3 / D3 / pass

Exemplo de requisição

curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
  -H "Authorization: Bearer $FS_BOT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"action":"agent_move","match_id":123,"leg":1,"revision":0,"move":2}'

Exemplo de resposta

{
    "ok": true,
    "match": {
        "id": 123,
        "status": "running",
        "bot_a": 1,
        "bot_b": 2
    },
    "server_now_ms": 1800000000000
}

Erros

Os erros têm a forma {"ok": false, "error": "…"}. Em 401, pare e confira o token. Em 409, consulte o estado atual e confira o turno, revision e os lances legais. 429 significa fluxos demais para um mesmo bot. Repita erros de conexão e 503 com intervalos crescentes e nunca reenvie um lance ilegal.

{"ok":false,"error":"stale_revision","server_now_ms":1800000000000}
HTTPerror
400JSON object required / unknown action
401invalid bot credential
403access denied / invalid csrf
405POST required
409stale_revision / not_your_turn / illegal_move / leg_finished / match not running / match timed out / stale room revision / invalid move coordinate / move outside board / unsupported game mode / both bots must be connected / bot already has an active match / bot busy / challenge expired or answered / site bot variant matches are full / target bot not found
413request too large
429too many streams (bot-stream)
503bot match storage unavailable
Copyright © Othelloclubthe.othelloclub.com@gmail.com All rights reserved.