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.
ls5. 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 --versionWindows PowerShell
py --versionAsí 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.
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_TOKENWindows PowerShell
$env:FS_BOT_TOKEN = [Net.NetworkCredential]::new('', (Read-Host 'Bot token' -AsSecureString)).PasswordEn 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.pyWindows 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 Minimized10. 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.6Iniciar
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-64Iniciar
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 --installtar -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.
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.
bot_match_client.py→ my_adapter.pybot_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.pyWindows 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.py2. 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 1777my_engine → my_adapter.py
D3import 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íntoma | Causa 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érmino | Significado |
|---|---|
| API | Una forma acordada en que los programas envían peticiones y respuestas. El conector se encarga de ello por ti. |
| Token | Una contraseña solo para tu bot. El conector la usa para demostrar al servidor qué bot es. |
| Terminal | Una ventana donde se escriben comandos. En Windows, usa PowerShell. |
| Variable de entorno | Un ajuste temporal que se pasa a los programas iniciados desde una ventana de terminal. Desaparece al cerrar la ventana. |
| JSON | Un formato para escribir datos como texto. Ejemplo: {"move":19} |
| Motor | Un programa que analiza una posición y elige una jugada. |
| Adaptador | Un pequeño programa que convierte los datos entre tu motor y el conector. |
| NBoard | 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. |
| Jugada legal | Una casilla donde puedes jugar ahora. El servidor las enumera en legal_moves. |
Descargas
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ámetros | Descripció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 PATH | 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. |
--edax-eval PATH | Ruta de eval.dat de Edax. Si se omite, busca data/eval.dat en la carpeta del ejecutable o en su carpeta superior. |
--egaroucid PATH | 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. |
--nboard-depth N | Envía set depth N al motor NBoard. De 0 a 60; 0 mantiene el valor predeterminado del motor. (--edax: 0 → 60). |
--max-think-seconds S | Segundos 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 NAME | Nombre del motor que se muestra en la información del bot (120 caracteres como máximo). No incluyas comandos ni rutas. |
--cpu-name NAME | Nombre de la CPU que se muestra (120 caracteres como máximo). Se detecta automáticamente si se omite. |
--once-match ID | Gestiona 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 S | Cierra los motores y sale después de ese número de segundos (código de salida 3). |
--no-stream | No usa el flujo de eventos; solo consulta periódicamente. |
--api URL, --stream-url URL | Direcciones 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 / abortedVariantes
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.
| Juego | game_mode | Tablero | Fichas iniciales | Reloj (por partida, por color) | Primeros legal_moves de las negras |
|---|---|---|---|---|---|
| Estándar | std | 8×8 | 12 fichas al azar | 60,000 ms | Depende de la posición |
| Anti | anti | 8×8 | 12 fichas al azar | 60,000 ms | Depende de la posición |
| Grand (10x10) | grand | 10×10 | 12 fichas al azar | 60,000 ms | Depende de la posición |
| Octagon | octagon | 10×10 (88) | 12 fichas al azar | 60,000 ms | Depende de la posición |
| Reversi clásico | reversi | 8×8 | 12 fichas al azar | 60,000 ms | Depende 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
| Campo | Tipo | Descripción |
|---|---|---|
game_mode | string | Uno de std, anti, grand, octagon, reversi. El XOT de las partidas contra personas llega como std. |
board_size | integer 8 | 10 | 8, o 10 en grand y octagon. |
board | integer[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_squares | integer[] | 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_moves | integer[] | Casillas donde puedes jugar ahora. Si está vacío, responde move=-1. |
color | integer 1 | 2 | Color al que le toca mover. 1=negras, 2=blancas. |
budget_ms | integer | Tiempo 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_ms | integer | Tiempo restante de tu color en esta partida, en milisegundos, tras restar el tiempo ya usado en este turno. |
match_id | integer ≠ 0 | ID del encuentro. Negativo (-número de sala) en las partidas contra personas. |
leg | integer 0 | 1 | Número de partida 0 o 1. Siempre 0 en las partidas contra personas. |
revision | integer | Número de estado de esta partida. Envíalo sin cambios con agent_move. |
history | array | Jugadas realizadas en esta partida. Decide a partir de board. |
round_no | integer | null | Número de ronda de una partida contra una persona. null en los pares de bots. |
room_created_at | string | null | Hora 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 63En 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 99Octagon (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ámetros | Tipo | Obligatorio | Descripción |
|---|---|---|---|
action | string "agent_poll" | Obligatorio | Nombre de la acción que se llama |
environment | object | Opcional | CPU, 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[])
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID del encuentro (negativo en partidas contra una persona) |
kind | string | human en partidas contra una persona |
bot_a | integer | ID del bot del participante 0 (0 si es una persona) |
bot_b | integer | ID del bot del participante 1 (0 si es una persona) |
game_mode | string | Juego (std, anti, grand, octagon, reversi) |
time_limit_sec | integer | Segundos por partida y por color |
status | string | pending, running, finished, aborted, rejected, expired |
expires_ms | integer | Plazo de aceptación (milisegundos) |
ranked | boolean | Si el encuentro es puntuable |
state.status | string | active, finished o aborted |
state.game_mode | string | Juego (std, anti, grand, octagon, reversi) |
state.board_size | integer 8 | 10 | Casillas por lado, 8 o 10 |
state.blocked_squares | integer[] | Casillas inutilizables (solo Octagon, sin ordenar) |
state.legs | array | Partidas (2 entre bots, 1 contra una persona) |
state.totals | [integer, integer] | Total de fichas de ambas partidas por participante |
state.winner | integer | null | Participante ganador 0 o 1, null en caso de empate |
state.finish_reason | string | null | discs, timeout, double_timeout, disconnect, no_first_move |
Objeto de partida (state.legs[])
| Campo | Tipo | Descripción |
|---|---|---|
board | integer[64 | 100] | 64 / 100 casillas, 0=vacía 1=negra 2=blanca |
turn | integer | Color al que le toca, 1 (negro) o 2 (blanco) |
legal_moves | integer[] | 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_participant | integer | Participante que juega con negras en esta partida |
clocks_ms | [number, number] | Valores base del tiempo restante en orden de color (negro, blanco) |
turn_started_ms | integer | Hora del servidor al empezar el turno actual |
revision | integer | Aumenta en 1 cada vez que cambia esta partida |
status | string | active, finished, timeout, stopped |
history | array | Jugadas 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ámetros | Tipo | Obligatorio | Descripción |
|---|---|---|---|
action | string "agent_challenge" | Obligatorio | Nombre de la acción que se llama |
target_id | integer ≥ 1 | Opcional | ID del bot al que se desafía |
target_name | string | Opcional | Nombre del bot al que se desafía (en lugar de target_id) |
game_mode | string std | anti | octagon | grand | reversi | Opcional | Juego 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ámetros | Tipo | Obligatorio | Descripción |
|---|---|---|---|
action | string "agent_accept" | Obligatorio | Nombre de la acción que se llama |
match_id | integer ≥ 1 | Obligatorio | ID 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ámetros | Tipo | Obligatorio | Descripción |
|---|---|---|---|
action | string "agent_reject" | Obligatorio | Nombre de la acción que se llama |
match_id | integer ≥ 1 | Obligatorio | ID 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ámetros | Tipo | Obligatorio | Descripción |
|---|---|---|---|
action | string "agent_move" | Obligatorio | Nombre de la acción que se llama |
match_id | integer ≠ 0 | Obligatorio | ID del encuentro (negativo en partidas contra una persona) |
round_no | integer ≥ 0 | Opcional | Solo en partidas contra una persona: round_no del objeto de encuentro |
room_created_at | string | Opcional | Solo en partidas contra una persona: room_created_at del objeto de encuentro |
leg | integer 0 | 1 | Obligatorio | Partida 0 o 1 (0 en partidas contra una persona) |
revision | integer ≥ 0 | Obligatorio | revision de la partida para la que se calculó la jugada; 409 stale_revision si la partida cambió |
move | integer -1..99 | string | Obligatorio | Casilla 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}| HTTP | error |
|---|---|
| 400 | JSON object required / unknown action |
| 401 | invalid bot credential |
| 403 | access denied / invalid csrf |
| 405 | POST required |
| 409 | stale_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 |
| 413 | request too large |
| 429 | too many streams (bot-stream) |
| 503 | bot match storage unavailable |