Documentación de la API

Guía paso a paso para principiantes

Qué necesitas

  • Una cuenta de FlipSight. Después de iniciar sesión puedes registrar un bot y descargar el conector.
  • Una foto del bot: un archivo GIF, JPG o PNG de 5 MB como máximo, de al menos 175 px de ancho y de alto.
  • Tu propio ordenador para ejecutar el bot Las jugadas de tu bot se calculan en el ordenador donde se ejecuta el conector. Incluso con el mismo motor, la fuerza puede variar según la potencia del ordenador y la configuración del motor, y si cambias de ordenador o de configuración, la puntuación puede moverse en consecuencia.
  • Python 3, un programa gratuito que ejecuta el conector. Funciona con la versión 3.9 o posterior; se recomienda la 3.11 o posterior. Lo compruebas en el paso 5.
  • El motor de tu bot. El conector incluye un motor de práctica para hacer pruebas.

1. Registrar tu bot

Inicia sesión y abre la página de bots (othelloclub.com/es/flipsight/bot). En el recuadro Registrar mi bot de arriba, escribe un nombre en Nombre del bot (2–24 caracteres). En Juegos compatibles, deja marcado solo Estándar por ahora. Elige una imagen en Subir foto del bot y pulsa el botón Registrar mi bot.

Así se ve si funciona: aparece el recuadro Token de conexión (se muestra una sola vez) con un texto largo que empieza por fsbm_.

2. Guardar el token

Este texto largo es el token, una contraseña solo para tu bot. El conector lo usa para demostrar al servidor que habla en nombre de este bot. Cópialo entero y pégalo temporalmente en algún sitio, por ejemplo en un editor de texto. Cuando cierres el recuadro, no podrás volver a ver ese mismo token.

Trata el token como una contraseña. No se lo enseñes a nadie ni publiques capturas de pantalla de él. Si lo pierdes o alguien lo ha visto, abre Administrar en la lista de bots y pulsa Emitir token de conexión. Cuando obtienes un token nuevo, el anterior deja de funcionar al instante.

3. Descargar el conector

Pulsa Descargar ejemplos de Python para descargar el archivo ZIP y descomprímelo. Esta guía llama «carpeta del bot» a la carpeta descomprimida. Ponla en un lugar fácil de encontrar, como el escritorio.

Así se ve si funciona: la carpeta del bot contiene el archivo README.md y la carpeta tools.

4. Abrir una terminal en la carpeta del bot

Una terminal es una ventana donde se escriben comandos. Ábrela así según tu sistema.

macOS: pulsa Command+Space, escribe Terminal y ábrela. Escribe cd y un espacio, arrastra la carpeta del bot a la ventana y pulsa Intro.

Linux: en el gestor de archivos, haz clic derecho en la carpeta del bot y elige «Abrir en una terminal». El nombre del menú puede variar según la distribución. Si no existe ese menú, abre una terminal y usa cd como en macOS.

Windows: abre la carpeta del bot en el Explorador de archivos. Escribe powershell en la barra de direcciones de arriba y pulsa Intro. Se abre una ventana de PowerShell en esa carpeta.

Comprobación: escribe el comando de abajo y pulsa Intro. Funciona si README.md y tools aparecen en la lista. Este comando es igual en macOS, Linux y Windows PowerShell.

ls

5. Comprobar Python

En la misma ventana, copia el comando de tu sistema, pégalo y pulsa Intro. Puedes usar el botón de copiar de la esquina superior derecha de cada recuadro de código.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Así se ve si funciona: una versión que empieza por 3, como Python 3.11.9. Funciona con la versión 3.9 o posterior. Si no se encuentra el comando, instala Python como se indica abajo, cierra la ventana y vuelve a empezar desde el paso 4.

macOS: descarga el instalador para macOS (.pkg) de python.org e instálalo. Después ejecuta una vez Install Certificates.command desde la carpeta de Python en Aplicaciones. Así se preparan los certificados necesarios para las conexiones seguras (HTTPS).

Linux: instala python3 con el gestor de software o el gestor de paquetes de tu distribución.

Windows: instala el Python install manager desde python.org.

python.org

6. Introducir el token

Guarda el token en una variable de entorno, un ajuste temporal que solo existe dentro de esta ventana de terminal. Su nombre es FS_BOT_TOKEN. Copia abajo el comando de una línea para tu sistema, pégalo y pulsa Intro una vez. Entonces aparece Bot token y espera a que escribas. Ahora pega el token que copiaste en el paso 2 y pulsa Intro.

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

En macOS y Linux el token pegado no se muestra, y en Windows ves * en lugar de sus caracteres. Las dos cosas son normales. Como el token no se escribe en el comando, no queda en el historial de comandos. El ajuste solo dura en esta ventana. Si la cerraste o abriste una ventana nueva, repite los pasos 4 y 6.

Windows: en PowerShell, pega con Ctrl+V o con clic derecho. Este comando de una línea usa una sintaxis que funciona tanto en Windows PowerShell 5.1, que viene con Windows, como en el más reciente PowerShell 7, pero todavía no se ha probado en un equipo Windows real.

Así se ve si funciona: vuelve a aparecer el indicador para el siguiente comando sin ningún error.

7. Iniciar el conector

En la misma ventana, copia el comando de abajo tal cual, pégalo y pulsa Intro. No cambies los nombres de archivo que contiene. Tu bot se conecta con el motor de práctica, un motor muy débil que juega en la casilla legal con el número más bajo.

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'

Es normal que el comando no muestre texto ni vuelva al indicador mientras se ejecuta. Para mantener tu bot en línea, deja esta ventana abierta con el conector y el motor en marcha, y evita que el ordenador entre en suspensión. Cerrar esta ventana o pulsar Ctrl+C detiene ambos programas y deja tu bot sin conexión. Ejecuta de nuevo el comando anterior para volver a conectarlo. El conector acepta automáticamente las solicitudes de otros bots.

Mientras el bot funciona con el motor de práctica no recibe encuentros automáticos, y sus encuentros contra otros bots, incluso los que pidas tú, no puntúan. Vuelve a iniciarlo con tu propio motor (paso 10) y desde ese momento se empareja y puntúa.

Así se ve si funciona: en la lista Bots registrados de la página de bots, el punto a la derecha del nombre de tu bot está en verde. Al pasar el ratón por encima se muestra En línea. Si está gris (Desconectado), pulsa Actualizar.

8. Probar con una primera partida

En la página de bots, elige tu bot en Mi bot jugador. Elige cualquier bot de la lista Bots de FlipSight, pulsa Desafiar y confirma en la ventana que se abre. En todos los tipos de juego el servidor calcula las jugadas de los bots de FlipSight, así que no hace falta mantener abierta la sala de juego. Con el emparejamiento automático activado (opción predeterminada), el sitio también organiza encuentros de bots mientras el conector está en marcha.

Así se ve si funciona: la sala de juego muestra dos tableros y se coloca una ficha en cada turno de tu bot. El motor de práctica es muy débil, así que es normal perder.

9. Seguir funcionando al cerrar la ventana (opcional)

Inicia con el comando de abajo en lugar del paso 7, y el bot seguirá funcionando después de cerrar la ventana. Ejecútalo en la misma ventana en la que introdujiste el token (paso 6). Se detiene si el ordenador se apaga o entra en suspensión. Si lo detienes durante una partida, esa partida se pierde por tiempo o por desconexión.

macOS / Linux

macOS / Linux: después de ejecutar el comando de inicio, puedes cerrar la ventana. Los mensajes se escriben en el archivo bot.log de la carpeta del bot. Para detenerlo, abre una terminal nueva y ejecuta el comando de detención. Copia solo el recuadro que necesites: si pegas los dos recuadros a la vez, el bot se detiene justo después de iniciarse.

Iniciar

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

Detener

pkill -f bot_match_client.py

Windows PowerShell

Windows: aparece una ventana minimizada en la barra de tareas y el bot se ejecuta en ella. Puedes cerrar la ventana de PowerShell actual. Para detenerlo, abre la ventana minimizada y pulsa Ctrl+C, o cierra esa ventana.

Iniciar

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

10. Cambiar a tu propio motor

Tras probar la conexión, cambie a su motor: opciones dedicadas de abajo para Edax o Egaroucid, --nboard para otros motores compatibles, JSON para uno propio. Conserve el mismo token.

Marca una variante (Anti, 10x10, Octagon, Reversi clásico) en Juegos compatibles solo si tu motor conoce sus reglas. Otros bots y personas pueden desafiar a tu bot en cada juego marcado. Para cambiarlo, abre Administrar en la lista de bots, edita Juegos compatibles y pulsa Guardar. No puedes cambiarlo mientras haya una partida en curso o un desafío pendiente.

Ejemplos de peticiones al motor por juego

Conectar un motor compatible con NBoard, como Edax

NBoard es una aplicación gráfica de Othello que puede usarse con NTest. El protocolo NBoard es su formato público para comunicarse con motores y funciona con motores que lo implementan, como NTest y Edax.

Los ejemplos de instalación e inicio siguientes usan Edax 4.6 y Egaroucid for Console 7.8.2.

Edax 4.6 no está incluido en el conector. Descarga el archivo de tu sistema desde Assets en Edax 4.6, guárdalo en la carpeta del bot y extráelo con los comandos de preparación siguientes. Los comandos de Windows y Linux son para equipos x86-64.

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

Preparar (solo la primera 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 (solo la primera 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

No hay un ejecutable oficial de Edax 4.6 para macOS. Descarga Source code (tar.gz) y edax-4.6-linux-x86.tar.gz de la misma versión. Compila el ejecutable de Mac desde el código fuente; del archivo Linux solo se extrae el archivo de evaluación común data/eval.dat. Se necesitan las herramientas de línea de comandos de Xcode. Los comandos de preparación compilan el código fuente.

Preparar (solo la primera 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 también se conecta indicando solo la ruta del ejecutable con --edax. No necesita un adaptador; el conector reparte el tiempo para cada jugada según el reloj restante.

Sustituya la ruta del comando por la ruta completa de su ejecutable. Use comillas dobles si contiene espacios. Si no se encuentra el archivo de evaluación, indique eval.dat con --edax-eval.

Una conexión NBoard solo juega partidas estándar de 8×8. Deja marcado solo Estándar en Juegos compatibles para este bot. Si llega una variante, el conector no consulta al motor, no se juega ninguna jugada y la partida se anula o se pierde por tiempo.

Egaroucid: --egaroucid

Prepare Egaroucid for Console. Elija el ejecutable de consola y conserve la carpeta resources en su sitio. En macOS y Linux, use un ejecutable compilado siguiendo las instrucciones oficiales. El conector envía la posición y el tiempo restante con setboard, settime y go, sin 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"

Ambos modos admiten solo el juego estándar 8×8, con un motor por partida simultánea. --max-think-seconds es el límite de seguridad por jugada (10 segundos por defecto). Si Egaroucid lo alcanza, se detiene la búsqueda, se juega una jugada legal de respaldo y se reinicia el motor en el siguiente turno.

Motor propio: conectar mediante JSON

Lee esta parte solo si quieres conectar un motor que has escrito tú. Si usas Edax, Egaroucid u otro motor NBoard, ya has terminado más arriba y puedes saltártela.

Cada vez que le toca jugar a tu bot, el conector ejecuta my_adapter.py una vez. Le entrega la posición actual en una línea (dónde están las fichas, a quién le toca, qué casillas son legales, cuánto tiempo queda), my_adapter.py responde con una casilla en una línea y el conector juega esa jugada en el servidor. my_adapter.py es un pequeño archivo Python que queda en medio y le pide la jugada a tu motor. El conector se encarga de la conexión con el servidor, del token y del envío de las jugadas, así que este archivo nunca tiene que ocuparse de eso.

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

¿De qué tipo es tu motor?

  • Un motor que elige su jugada con código Python: usa el ejemplo A de abajo.
  • Un archivo de programa que, cada vez que se ejecuta, muestra una línea de respuesta y se cierra: usa el ejemplo B de abajo.
  • Un ejecutable que se queda abierto y va intercambiando comandos: si admite NBoard, conéctalo con --nboard arriba. Con otro protocolo, adapta el ejemplo B a ese protocolo y haz que my_adapter.py mantenga el motor abierto e intercambie una línea cada vez en un bucle.

Paso 1: un archivo que solo comprueba la conexión (my_adapter.py)

En la carpeta del bot (donde están README.md y tools), crea un archivo llamado my_adapter.py, pega el código de abajo tal cual y guárdalo. Este archivo no usa ningún motor y simplemente responde con la primera casilla legal. Sirve para confirmar que la conexión funciona antes de añadir tu 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)

Comprobar la entrada y la salida antes de conectar

El comando del recuadro 1 pasa la posición de ejemplo directamente al adaptador. Debería mostrar una línea: {"move": 19}. Cuando veas esa línea, usa el comando del recuadro 2 para iniciar el conector con este adaptador, en la ventana que tiene el token del paso 6. Ejecuta los dos recuadros por separado. Si tu motor lee este formato JSON por sí mismo, puedes poner su comando directamente después de --engine.

1. Probar solo el 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 el conector con el 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'

Paso 2: cambiar a un archivo que llama a tu motor

Cuando el paso 1 funcione, sustituye todo el contenido de my_adapter.py por el ejemplo A o B de abajo. Cambia solo las líneas marcadas con EDIT y deja el resto como está. Después vuelve a ejecutar el comando de prueba 1 de arriba y comprueba que aparece una línea de respuesta. La jugada la ha elegido tu motor, así que es normal que salga una casilla distinta de 19.

Ejemplo A: un motor que elige su jugada con una función Python

Este ejemplo supone que hay un archivo llamado my_engine.py con una función search que elige la jugada. En EDIT 1 cambia el nombre del archivo y el de la función, y en EDIT 2 cambia los valores que se le pasan, para que coincidan con tu motor. La función debe devolver el número de la casilla (0 a 63) o una 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)
Ejemplo B: un motor que es un archivo de programa

Este ejemplo supone que el programa lee una línea con 64 caracteres del tablero, el bando que juega y el tiempo de reflexión en milisegundos, y responde con una línea con una coordenada como D3 (. = vacía, X = negras, O = blancas). Cada motor tiene su propio formato, así que cambia el nombre del archivo de programa en EDIT 1, la línea que se envía al motor en EDIT 2 y la parte que lee la respuesta en EDIT 3 para que coincidan con tu 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)

Tiempo de reflexión: en las partidas entre bots cada bot dispone de 1 minuto por partida, así que pensar mucho en una sola jugada hace perder por tiempo enseguida. En los ejemplos, think_ms divide el tiempo restante (remaining_ms) entre las jugadas que te quedan para decidir el tiempo de esta jugada. budget_ms no es el tiempo que hay que gastar en esta jugada. Es el tiempo máximo que el conector espera la respuesta (10 segundos por defecto). Si la respuesta llega más tarde, el bot no puede seguir en esa partida y la pierde por tiempo, así que pasa think_ms a tu motor, no budget_ms.

Si algo falla: cuando la llamada al motor falla, los archivos de ejemplo escriben el motivo en my_adapter.log y juegan la primera casilla legal en su lugar. Si las jugadas te parecen raras, abre primero ese archivo. El conector no muestra en pantalla los mensajes de error del adaptador. Usa print solo para la línea de respuesta y escribe cualquier texto de depuración en un archivo.

Velocidad: el conector mantiene my_adapter.py abierto en cada partida y envía una línea por jugada. Un archivo que responde una línea y termina, como los ejemplos, se vuelve a iniciar en la jugada siguiente, así que tu motor también se reinicia cada vez. Si el motor tarda en arrancar o debe conservar el trabajo de jugadas anteriores, convierte my_adapter.py en un bucle: leer una línea, responder una línea, repetir. No leas la entrada hasta el final y haz flush tras cada respuesta.

El resto de esta parte es material de consulta sobre lo que intercambian el conector y my_adapter.py. Si usas los archivos de ejemplo de arriba tal cual, no hace falta que lo leas.

JSON de entrada (conector → adaptador)

Estos son datos de ejemplo enviados por el conector, no código ejecutable. No los guarde como un archivo Python. Durante la partida, el conector envía los datos actuales automáticamente en cada turno.

Abajo tienes un ejemplo con la posición inicial estándar de 8×8. En las partidas reales, el conector rellena la posición actual. board enumera las casillas desde a1 hacia la derecha y luego la fila siguiente; 0=vacía, 1=negra, 2=blanca. color=1 pide una jugada de las negras. legal_moves enumera las casillas donde puedes jugar. remaining_ms es el tiempo que queda en el reloj de la partida y budget_ms el tiempo máximo que el conector espera la respuesta de esta jugada, ambos en milisegundos (1000 = 1 segundo). Los campos y ejemplos de las variantes están más abajo, en la sección de variantes.

{"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 respuesta (adaptador → conector)

Escribe una de las respuestas de abajo como una sola línea JSON terminada en un salto de línea. move es un número de casilla o una coordenada, sin distinguir mayúsculas y minúsculas. En 8×8, d3 es (3−1)×8+3 = 19. Elige siempre entre los legal_moves recibidos. En los pares de bots, si no hay jugada legal, envía -1 o pass; en las partidas contra personas, el servidor resuelve los pases forzados. Escribe en stdout solo la línea de respuesta, sin otros mensajes.

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

Ejemplos de peticiones al motor por juego

Ejemplos de la primera petición para las negras que el conector envía a un motor con --engine, generados con el código de reglas. Las partidas reales de todos los tipos empiezan desde una posición aleatoria con 12 fichas, así que board y legal_moves cambian en cada partida. Para que estos ejemplos no cambien, sus jugadas iniciales se eligieron cerca del centro del tablero. match_id y los valores del reloj también son distintos en las partidas reales.

  • game_mode: Uno de std, anti, grand, octagon, reversi. El XOT de las partidas contra personas llega como std.
  • board_size: 8, o 10 en grand y octagon.
  • board: Array de longitud board_size². Se lee desde a1 hacia la derecha y luego la fila siguiente. 0=vacía (incluidas las casillas bloqueadas de Octagon), 1=negra, 2=blanca.
  • blocked_squares: Casillas inutilizables de Octagon. Array vacío en los demás juegos.
  • placed: Fichas colocadas hasta el momento por color, incluidas las jugadas iniciales aleatorias; las claves son las cadenas "1" y "2". Se usa para el límite de 32 fichas del Reversi clásico.

Campos de la petición al 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 respuesta (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 respuesta (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 respuesta (adaptador → conector)

{"move":13}
Reversi clásico (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 respuesta (adaptador → conector)

{"move":18}

Problemas frecuentes

SíntomaCausa y solución
El bot muestra un punto gris (desconectado).Comprueba que la ventana del conector sigue funcionando. Se detiene si cierras la ventana, pulsas Ctrl+C o el ordenador entra en suspensión. Repite los pasos 6 y 7 y luego pulsa Actualizar.
Dice que no se encuentra el comando.
command not found / is not recognized
Falta Python, o no volviste a abrir la ventana después de instalarlo. Consulta el paso 5, abre una ventana nueva y vuelve a empezar desde el paso 4.
Dice que no se puede abrir el archivo.
can't open file
Lo ejecutaste fuera de la carpeta del bot. Ve a la carpeta del bot como en el paso 4 y comprueba con ls que aparecen README.md y tools.
Dice que falta el token y se cierra.
FS_BOT_TOKEN is missing or invalid
Esta ventana no tiene token. Si abriste una ventana nueva, repite el paso 6.
Dice que el token fue rechazado y se detiene.
Bot credential rejected
El token se pegó mal o es antiguo. Después de emitir un token nuevo o desactivar el bot, el token anterior deja de funcionar. Vuelve a empezar desde el paso 6 con el token más reciente.
La misma línea de error aparece una y otra vez.
Connection/state error
Comprueba la conexión a internet. En macOS, comprueba que ejecutaste Install Certificates.command del paso 5. Si solo aparece durante las partidas, puede que el servidor esté rechazando la jugada de tu motor por ilegal. El conector vuelve a enviar la misma jugada, así que hay que corregir el motor.
El botón de desafío no funciona, o el desafío no se acepta.Los dos bots deben tener el punto verde. Comprueba que tu bot está seleccionado en Mi bot jugador. Un bot que ya está en otra partida no acepta un desafío nuevo. Un desafío que no se acepta caduca a los 120 segundos.
El bot no hace ninguna jugada y luego pierde por tiempo o la partida se anula.
Engine failed for match
El motor no respondió a tiempo o su respuesta tenía un formato incorrecto. Comprueba el comando del motor y su salida JSON de una línea. Si un bot no hace su primera jugada en los 30 segundos siguientes al inicio, la partida se anula.
No llegan desafíos de variantes.La ventana de desafío solo muestra los juegos que admiten ambos bots. Comprueba que el juego está marcado en Juegos compatibles de tu bot y que el otro bot también lo admite. Un bot conectado mediante NBoard debe tener marcado solo Estándar.

Glosario

TérminoSignificado
APIUna forma acordada en que los programas envían peticiones y respuestas. El conector se encarga de ello por ti.
TokenUna contraseña solo para tu bot. El conector la usa para demostrar al servidor qué bot es.
TerminalUna ventana donde se escriben comandos. En Windows, usa PowerShell.
Variable de entornoUn ajuste temporal que se pasa a los programas iniciados desde una ventana de terminal. Desaparece al cerrar la ventana.
JSONUn formato para escribir datos como texto. Ejemplo: {"move":19}
MotorUn programa que analiza una posición y elige una jugada.
AdaptadorUn pequeño programa que convierte los datos entre tu motor y el conector.
NBoardNBoard es una aplicación gráfica de Othello que puede usarse con NTest. El protocolo NBoard es su formato público para comunicarse con motores y funciona con motores que lo implementan, como NTest y Edax.
Jugada legalUna casilla donde puedes jugar ahora. El servidor las enumera en legal_moves.

Descargas

Descargar ejemplos de Python

OpenAPI 3.1 JSON

README.md

Inicia sesión para descargar. .zip incluye guía, conector y motor de ejemplo; .json es la especificación de la API y .md es la guía.

Especificación detallada

El resto de esta página es una referencia detallada para desarrolladores que trabajan directamente con un motor o con la API.

Opciones del conector

Opciones de tools/bot_match_client.py. Se requiere exactamente una de --engine y --nboard / --edax / --egaroucid. El token nunca es una opción; solo se lee de la variable de entorno FS_BOT_TOKEN. Códigos de salida: 0 parada normal, 2 token rechazado, 3 se alcanzó --max-runtime.

ParámetrosDescripción
--engine "COMMAND"Inicia este comando una vez por partida e intercambia una línea JSON por jugada sin cerrar su entrada. Un motor que responde una línea y termina se vuelve a iniciar en la jugada siguiente.
--nboard "COMMAND"Mantiene un motor NBoard por partida, dos en total. Solo estándar (std).
--edax PATHEdax también se conecta indicando solo la ruta del ejecutable con --edax. No necesita un adaptador; el conector reparte el tiempo para cada jugada según el reloj restante.
--edax-eval PATHRuta de eval.dat de Edax. Si se omite, busca data/eval.dat en la carpeta del ejecutable o en su carpeta superior.
--egaroucid PATHPrepare Egaroucid for Console. Elija el ejecutable de consola y conserve la carpeta resources en su sitio. En macOS y Linux, use un ejecutable compilado siguiendo las instrucciones oficiales. El conector envía la posición y el tiempo restante con setboard, settime y go, sin adaptador.
--nboard-depth NEnvía set depth N al motor NBoard. De 0 a 60; 0 mantiene el valor predeterminado del motor. (--edax: 0 → 60).
--max-think-seconds SSegundos máximos de espera al motor por jugada. Predeterminado 10, máximo 300. Nunca más que el tiempo restante del reloj menos 0,75 segundos. Es un límite de seguridad del conector; el servidor no tiene límite de tiempo por jugada. Con 0 se elimina el límite por jugada y se usa el tiempo restante de la partida.
--engine-name NAMENombre del motor que se muestra en la información del bot (120 caracteres como máximo). No incluyas comandos ni rutas.
--cpu-name NAMENombre de la CPU que se muestra (120 caracteres como máximo). Se detecta automáticamente si se omite.
--once-match IDGestiona solo este encuentro y se cierra cuando termina (finalizado, anulado, caducado, rechazado). También funcionan los ID negativos de partidas contra personas. Para pruebas, combínalo con --max-runtime.
--max-runtime SCierra los motores y sale después de ese número de segundos (código de salida 3).
--no-streamNo usa el flujo de eventos; solo consulta periódicamente.
--api URL, --stream-url URLDirecciones de la API y del flujo. Normalmente conserva los valores predeterminados. Solo se acepta HTTPS y, como el token se envía con ellas, el flujo debe estar en el host de la API.

Implementar la API por tu cuenta

Recibe desafíos y posiciones con agent_poll o con el flujo de eventos autenticado. Envía agent_accept para los desafíos pendientes en los que eres bot_b, y luego calcula solo las posiciones en las que te toca jugar en cada partida de un encuentro en curso. Envía como máximo una vez por match_id, partida (leg) y revision, y descarta el trabajo obsoleto cuando cambie el estado. Las partidas contra personas usan un match_id negativo y leg=0, y requieren round_no y room_created_at exactamente como se recibieron. En las salas normales el servidor gestiona los pases forzados; solo los pares de bots envían pases.

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

Un bot recibe solo los juegos marcados en Juegos compatibles. El game_mode de las peticiones al motor y del estado del encuentro indica el juego. El servidor calcula el tamaño del tablero, las casillas bloqueadas y las casillas legales, así que el motor solo tiene que elegir entre los legal_moves que recibe. No generes jugadas de 10×10 ni de Reversi clásico con la generación de jugadas del 8×8 estándar.

Juegogame_modeTableroFichas inicialesReloj (por partida, por color)Primeros legal_moves de las negras
Estándarstd8×812 fichas al azar60,000 msDepende de la posición
Antianti8×812 fichas al azar60,000 msDepende de la posición
Grand (10x10)grand10×1012 fichas al azar60,000 msDepende de la posición
Octagonoctagon10×10 (88)12 fichas al azar60,000 msDepende de la posición
Reversi clásicoreversi8×812 fichas al azar60,000 msDepende de la posición

Reglas comunes: las negras (1) mueven primero. Las dos partidas empiezan desde la misma posición con los colores intercambiados y se juegan a la vez. En los pares de bots, cuando no tienes jugada legal, legal_moves llega como un array vacío y el bot debe enviar él mismo move=-1 (pase). El servidor no pasa por ti y tu reloj sigue corriendo. Pasar cuando existe una jugada legal es ilegal. Una partida termina cuando ningún color puede mover. Las casillas vacías no se asignan a nadie; solo se suman las fichas reales de ambas partidas, y los totales iguales son tablas. Todas las partidas entre bots empiezan en una posición aleatoria con 12 fichas. En todos los tipos de juego, cada bot dispone de 1 minuto por partida.

Anti: colocar y voltear funcionan como en el Othello estándar. Gana el bando con menos fichas en el total de ambas partidas.

10x10: reglas estándar en un tablero de 10×10 (100 casillas). Las fichas iniciales ocupan las cuatro casillas centrales; los pares de bots añaden 8 jugadas aleatorias y empiezan con 12 fichas.

Octagon: el mismo tablero y la misma colocación inicial que 10x10, pero tres casillas de cada esquina, 12 en total, no se pueden usar (88 casillas jugables). Las casillas bloqueadas llegan como números de casilla en blocked_squares y como 0, el mismo valor que vacía, en board. El array no está en orden numérico. No se puede jugar en una casilla bloqueada, y las líneas de fichas se detienen allí.

Reversi clásico: según sus reglas, empieza con el tablero vacío. Mientras alguna de las cuatro casillas centrales (27, 28, 35, 36) esté vacía, solo se coloca allí y no se voltea nada. Cuando las cuatro están ocupadas, se aplican las reglas estándar. Cada color coloca como máximo 32 fichas, incluidas las cuatro primeras jugadas. placed cuenta las fichas que cada color ha colocado hasta el momento; sus claves son las cadenas "1" (negras) y "2" (blancas). Los pares de bots empiezan después de que el servidor haya colocado 12 fichas al azar, incluidas las cuatro jugadas centrales, así que en la primera petición los dos valores de placed suman 12 (normalmente {"1":6,"2":6}). Un color que ya ha colocado las 32 recibe legal_moves vacío y envía -1 en cada uno de sus turnos.

Ejemplos de peticiones al motor por juego

Ejemplos de la primera petición para las negras que el conector envía a un motor con --engine, generados con el código de reglas. Las partidas reales de todos los tipos empiezan desde una posición aleatoria con 12 fichas, así que board y legal_moves cambian en cada partida. Para que estos ejemplos no cambien, sus jugadas iniciales se eligieron cerca del centro del tablero. match_id y los valores del reloj también son distintos en las partidas reales.

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ásico (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 de la petición al motor

CampoTipoDescripción
game_modestringUno de std, anti, grand, octagon, reversi. El XOT de las partidas contra personas llega como std.
board_sizeinteger 8 | 108, o 10 en grand y octagon.
boardinteger[board_size²]Array de longitud board_size². Se lee desde a1 hacia la derecha y luego la fila siguiente. 0=vacía (incluidas las casillas bloqueadas de Octagon), 1=negra, 2=blanca.
blocked_squaresinteger[]Casillas inutilizables de Octagon. Array vacío en los demás juegos.
placed{"1": integer, "2": integer}Fichas colocadas hasta el momento por color, incluidas las jugadas iniciales aleatorias; las claves son las cadenas "1" y "2". Se usa para el límite de 32 fichas del Reversi clásico.
legal_movesinteger[]Casillas donde puedes jugar ahora. Si está vacío, responde move=-1.
colorinteger 1 | 2Color al que le toca mover. 1=negras, 2=blancas.
budget_msintegerTiempo permitido para esta jugada en milisegundos: el menor entre --max-think-seconds y el tiempo restante del reloj menos 0,75 segundos. Si se supera, el conector detiene el motor.
remaining_msintegerTiempo restante de tu color en esta partida, en milisegundos, tras restar el tiempo ya usado en este turno.
match_idinteger ≠ 0ID del encuentro. Negativo (-número de sala) en las partidas contra personas.
leginteger 0 | 1Número de partida 0 o 1. Siempre 0 en las partidas contra personas.
revisionintegerNúmero de estado de esta partida. Envíalo sin cambios con agent_move.
historyarrayJugadas realizadas en esta partida. Decide a partir de board.
round_nointeger | nullNúmero de ronda de una partida contra una persona. null en los pares de bots.
room_created_atstring | nullHora de creación de la sala de una partida contra una persona. null en los pares de bots.

Desafiar a otro bot en una variante

Añade game_mode a agent_challenge; el valor predeterminado es std. Ambos bots deben tenerlo en supported_games. Consulta los juegos del rival en bots[].supported_games de la respuesta de agent_poll. Si a alguno de los bots le falta el juego, la respuesta es HTTP 409 con el error de abajo.

{"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}

Cambiar supported_games

El formulario de registro empieza con Estándar marcado, y los bots antiguos sin lista guardada admiten std. Solo puedes cambiar la lista en Administrar en la página web de bots, nunca con el token del bot (API). El cambio se rechaza mientras haya un encuentro en curso o esperando aceptación. No se puede guardar una lista vacía.

NBoard y variantes

Una conexión --nboard solo admite el estándar (std). Cuando llega una variante, el conector no consulta al motor y renuncia a ese turno (mensaje Engine failed for match). No se envía ninguna jugada, así que el encuentro se anula antes de la primera jugada, o esa partida se pierde por tiempo más adelante. Asigna a un bot NBoard el estándar como único juego admitido.

Personas contra bots registrados

Las personas pueden desafiar a tu bot en cualquier juego que tenga registrado. Si admite el estándar, pueden llegar tanto STD como XOT. El XOT llega con game_mode std y empieza desde un tablero con 8 jugadas ya hechas (están incluidas en history). Quien desafía elige el reloj, y la partida no puntúa. Un encuentro contra una persona tiene una sola partida (leg 0) y un match_id negativo. El servidor gestiona los pases cuando no hay jugada posible, así que no envíes -1. Los bots registrados en línea con emparejamiento automático activado también participan en partidas rápidas STD/XOT compatibles. Cuentan para la puntuación de miembros, pero no de invitados.

Jugadas ilegales y tiempo

Una jugada ilegal o una revision obsoleta se rechaza con HTTP 409 (illegal_move, stale_revision, not_your_turn) y el tablero no cambia. Mientras tanto, el reloj sigue corriendo. El conector vuelve a enviar la misma jugada con la misma revision, así que, si no se corrige el motor, esa partida acabará perdiéndose por tiempo. El servidor no tiene límite por jugada y solo comprueba el tiempo restante de cada color. Los 10 segundos son el valor predeterminado de --max-think-seconds del conector.

Autenticación

Usa HTTPS, JSON POST y Authorization: Bearer. Cada token corresponde a un bot. No lo incluyas en URL, código público ni registros.

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

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

Reglas del encuentro

Todas las partidas entre bots empiezan en una posición aleatoria con 12 fichas. Se juegan dos partidas simultáneas con colores invertidos. Gana el mayor total de fichas, o el menor en Anti.

Todas las partidas entre bots empiezan en una posición aleatoria con 12 fichas. En todos los tipos de juego, cada bot dispone de 1 minuto por partida.

Si un bot no ha hecho su primera jugada en los 30 segundos posteriores al inicio, el encuentro se anula y no puntúa. Si durante el encuentro la conexión de un bot se pierde más de 30 segundos mientras el otro sigue conectado, gana el bot conectado (finish_reason: disconnect) y el resultado puntúa.

El servidor calcula las jugadas de los bots de FlipSight en todos los tipos de juego. Se aceptan desafíos web y desafíos por la API (agent_challenge), y el servidor inicia el encuentro enseguida, así que no hace falta mantener abierta la sala de juego. Si el cálculo del servidor se detiene más de 30 segundos, el encuentro se anula (finish_reason: technical) y no puntúa. Las variantes son costosas de calcular, así que como máximo se juegan 2 encuentros de variante contra bots de FlipSight a la vez. Si están llenos, recibirás HTTP 409 con site bot variant matches are full; vuelve a intentarlo en un momento.

Encuentros de bots automáticos: el sitio mantiene siempre dos salas de encuentros de bots. Mientras un bot registrado con el emparejamiento automático activado esté en línea y sin jugar, recibe un desafío de encuentro de bots estándar (std) contra un bot de puntuación similar (primero otro bot registrado y, si no hay, un bot de FlipSight), como bot_b. El conector lo acepta automáticamente y el encuentro empieza. Si el bot no acepta en 120 segundos o lo rechaza, queda fuera del emparejamiento automático durante 10 minutos. Los encuentros automáticos puntúan. Los encuentros automáticos de bots continúan mientras se juegan partidas de Arena. El emparejamiento automático está activado por defecto y se puede desactivar en Administrar en la página de bots. Un bot iniciado con el motor de práctica (environment.practice es true) queda fuera de los encuentros automáticos, y sus encuentros contra otros bots durante ese tiempo no puntúan.

Un desafío humano directo juega una partida en una sala normal con un juego y reloj compatibles. El servidor impone que los desafíos directos no afecten a la puntuación. Los bots registrados en línea con emparejamiento automático activado también participan en partidas rápidas STD/XOT compatibles. Cuentan para la puntuación de miembros, pero no de invitados. Un bot registrado solo acepta tantas partidas como indique su ajuste de partidas simultáneas (2 por defecto). Una partida contra una persona y un enfrentamiento entre bots (dos tableros) cuentan como 1 cada uno.

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)

Flujo del encuentro

Todas las partidas entre bots empiezan en una posición aleatoria con 12 fichas. Se juegan dos partidas simultáneas con colores invertidos. Gana el mayor total de fichas, o el menor en Anti.

Tu número de participante es 0 si eres bot_a y 1 si eres bot_b. El participante 0 juega con negras (1) en legs[0] y con blancas (2) en legs[1]; el participante 1, al revés. Es tu turno cuando leg.status es active y leg.turn coincide con tu color. Puede ser tu turno en ambas partidas a la vez.

Si no tienes ninguna jugada legal, debes enviar tú mismo move -1 (pase); el servidor no pasa por ti. En partidas contra una persona, el servidor gestiona los pases como en las salas de juego normales.

En tu turno, tiempo restante = clocks_ms[tu color - 1] - (server_now_ms - turn_started_ms). clocks_ms va en orden de color (negro, blanco); totals y winner van en orden de participante (winner 0 o 1, null en caso de empate).

Números de casilla

Número de casilla = fila × 8 + columna, así que a1=0, h1=7, a2=8 y h8=63. Por ejemplo, 19 es d3 y 37 es f5. El array board usa el mismo orden, con 0=vacía, 1=negra, 2=blanca.

    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

En 10×10 (grand, octagon), casilla = fila × 10 + columna. Las columnas van de a a j, y la décima fila se escribe como 10 o 0 en las coordenadas. a1=0, j1=9, a2=10, j10=99 (igual que j0). La misma coordenada tiene un número distinto en cada tamaño de tablero: d3 es 19 en 8×8 y 23 en 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 ## ##

Flujo de eventos

Conéctate con el mismo token Bearer a GET https://othelloclub.com/flipsight_api/bot-stream para recibir un evento JSON por línea (NDJSON): hello confirma la conexión, match indica que uno de tus encuentros cambió (los eventos entre bots incluyen el objeto completo del encuentro) y ping es una señal de mantenimiento cada 10 segundos. Al recibir match, llama a agent_poll una vez. Si se cae la conexión, reconéctate con una espera creciente de 1 a 30 segundos y sigue consultando mientras tanto. Mientras el flujo esté conectado, tu bot aparece en línea; cada bot puede abrir hasta 3 flujos.

Ejemplos 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 del motor

El conector de bots de FlipSight (Python 3.9+, solo biblioteca estándar) funciona en Windows, macOS y Linux y conecta un motor de dos formas. --engine inicia el motor una vez por partida, envía una línea JSON en cada jugada y lee una línea {"move": casilla}. Un motor que responde una línea y termina sigue funcionando porque se vuelve a iniciar en la jugada siguiente, pero un motor que lee la entrada hasta el final (sys.stdin.read(), etc.) nunca responde, así que lee una línea cada vez. legal_moves en la solicitud lista las casillas legales; si está vacía, responde -1. --nboard mantiene en marcha un motor NBoard, uno por partida; ajusta su profundidad con --nboard-depth.

Edax también se conecta indicando solo la ruta del ejecutable con --edax. No necesita un adaptador; el conector reparte el tiempo para cada jugada según el reloj restante.

Prepare Egaroucid for Console. Elija el ejecutable de consola y conserve la carpeta resources en su sitio. En macOS y Linux, use un ejecutable compilado siguiendo las instrucciones oficiales. El conector envía la posición y el tiempo restante con setboard, settime y go, sin adaptador.

JSON de entrada (conector → adaptador)

Estos son datos de ejemplo enviados por el conector, no código ejecutable. No los guarde como un archivo Python. Durante la partida, el conector envía los datos actuales automáticamente en 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 respuesta (adaptador → conector)

{"move":2}

Sincronización

Con el flujo conectado, llama a agent_poll cuando llegue un evento y cada 10 segundos. Si consultas sin flujo, hazlo cada segundo durante un encuentro y cada 5 segundos en espera, nunca más de una vez por segundo. El tiempo de red cuenta contra tu reloj. Si se pierde la respuesta a una jugada, consulta el nuevo estado y reenvía la misma jugada solo si la revision sigue siendo la misma.

Acciones API

Los ejemplos de respuesta muestran el formato. El botón de copiar solo copia la solicitud y no llama a la API.

POST agent_poll

Consultar estado

ParámetrosTipoObligatorioDescripción
actionstring "agent_poll"ObligatorioNombre de la acción que se llama
environmentobjectOpcionalCPU, SO y nombre del motor del PC del conector (hasta 120 caracteres cada uno, opcional) y practice (true o false, opcional). practice en true significa que el bot está practicando: queda fuera de los encuentros automáticos y los encuentros entre bots pedidos o aceptados mientras tanto no puntúan. El conector lo envía solo cuando se inicia con el motor de práctica. El servidor recuerda el último valor, así que al terminar la práctica envíalo sin practice o con false.

Ejemplo de solicitud

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"}}'

Ejemplo de respuesta

{
    "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 encuentro (matches[])

CampoTipoDescripción
idintegerID del encuentro (negativo en partidas contra una persona)
kindstringhuman en partidas contra una persona
bot_aintegerID del bot del participante 0 (0 si es una persona)
bot_bintegerID del bot del participante 1 (0 si es una persona)
game_modestringJuego (std, anti, grand, octagon, reversi)
time_limit_secintegerSegundos por partida y por color
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerPlazo de aceptación (milisegundos)
rankedbooleanSi el encuentro es puntuable
state.statusstringactive, finished o aborted
state.game_modestringJuego (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Casillas por lado, 8 o 10
state.blocked_squaresinteger[]Casillas inutilizables (solo Octagon, sin ordenar)
state.legsarrayPartidas (2 entre bots, 1 contra una persona)
state.totals[integer, integer]Total de fichas de ambas partidas por participante
state.winnerinteger | nullParticipante ganador 0 o 1, null en caso de empate
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Objeto de partida (state.legs[])

CampoTipoDescripción
boardinteger[64 | 100]64 / 100 casillas, 0=vacía 1=negra 2=blanca
turnintegerColor al que le toca, 1 (negro) o 2 (blanco)
legal_movesinteger[]Casillas legales del color al que le toca. Vacío en partidas terminadas
placed{"1": integer, "2": integer}Fichas colocadas por color, claves "1" (negras) y "2" (blancas)
black_participantintegerParticipante que juega con negras en esta partida
clocks_ms[number, number]Valores base del tiempo restante en orden de color (negro, blanco)
turn_started_msintegerHora del servidor al empezar el turno actual
revisionintegerAumenta en 1 cada vez que cambia esta partida
statusstringactive, finished, timeout, stopped
historyarrayJugadas realizadas (color, participant, move, at_ms)
discs[integer, integer]Número de fichas en orden de color (negro, blanco)
result_discs[integer, integer] (optional)Puntuación arbitrada opcional [negras, blancas] para una partida perdida por tiempo; se suma en lugar de las fichas. Puntuaciones para el bando que agotó el tiempo y su rival: std y reversi de 0 a 64, grand de 0 a 100, octagon de 0 a 88, anti de 64 a 0. La otra partida continúa, y los totales deciden el ganador cuando terminan ambas partidas. Los registros terminados antiguos no se recalculan.

POST agent_challenge

Desafiar

Indica target_id (número del bot) o target_name (nombre del bot), que aparecen en la lista bots de la respuesta de agent_poll. Los bots integrados de FlipSight (is_builtin: true) también se pueden desafiar por la API en todos los tipos de juego, y el servidor inicia el encuentro enseguida. Si ya hay 2 encuentros de variante contra bots integrados, recibirás HTTP 409 con site bot variant matches are full.

Desafiar desde la página de bots

ParámetrosTipoObligatorioDescripción
actionstring "agent_challenge"ObligatorioNombre de la acción que se llama
target_idinteger ≥ 1OpcionalID del bot al que se desafía
target_namestringOpcionalNombre del bot al que se desafía (en lugar de target_id)
game_modestring std | anti | octagon | grand | reversiOpcionalJuego de un par de bots. Por defecto std. Ambos bots deben admitirlo. Estándar / Anti / Octagon / Grand (10x10) / Reversi clásico

Ejemplo de solicitud

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"}'

Ejemplo de respuesta

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

POST agent_accept

Aceptar

ParámetrosTipoObligatorioDescripción
actionstring "agent_accept"ObligatorioNombre de la acción que se llama
match_idinteger ≥ 1ObligatorioID del encuentro (negativo en partidas contra una persona)

Ejemplo de solicitud

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}'

Ejemplo de respuesta

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

POST agent_reject

Rechazar

ParámetrosTipoObligatorioDescripción
actionstring "agent_reject"ObligatorioNombre de la acción que se llama
match_idinteger ≥ 1ObligatorioID del encuentro (negativo en partidas contra una persona)

Ejemplo de solicitud

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}'

Ejemplo de respuesta

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

POST agent_move

Enviar jugada

ParámetrosTipoObligatorioDescripción
actionstring "agent_move"ObligatorioNombre de la acción que se llama
match_idinteger ≠ 0ObligatorioID del encuentro (negativo en partidas contra una persona)
round_nointeger ≥ 0OpcionalSolo en partidas contra una persona: round_no del objeto de encuentro
room_created_atstringOpcionalSolo en partidas contra una persona: room_created_at del objeto de encuentro
leginteger 0 | 1ObligatorioPartida 0 o 1 (0 en partidas contra una persona)
revisioninteger ≥ 0Obligatoriorevision de la partida para la que se calculó la jugada; 409 stale_revision si la partida cambió
moveinteger -1..99 | stringObligatorioCasilla 0-(board_size²−1), -1 para pasar / d3 / D3 / pass

Ejemplo de solicitud

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}'

Ejemplo de respuesta

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

Errores

Los errores tienen la forma {"ok": false, "error": "…"}. Con 401, detente y comprueba el token. Con 409, consulta el estado actual y comprueba el turno, revision y las jugadas legales. 429 significa demasiados flujos para un mismo bot. Reintenta los errores de conexión y 503 con esperas crecientes, y nunca reenvíes una jugada 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.