Documentation API

Guide pas à pas pour débutants

Ce qu'il vous faut

  • Un compte FlipSight. Une fois connecté, vous pouvez enregistrer un bot et télécharger le connecteur.
  • Une photo du bot : un fichier GIF, JPG ou PNG de 5 Mo au maximum, d'au moins 175 px de large et de haut.
  • Votre ordinateur pour faire fonctionner le bot Les coups de votre bot sont calculés sur l'ordinateur où tourne le connecteur. Même avec le même moteur, la force peut varier selon la puissance de l'ordinateur et les réglages du moteur, et si vous changez d'ordinateur ou de réglages, le classement peut évoluer en conséquence.
  • Python 3, un programme gratuit qui fait tourner le connecteur. La version 3.9 ou ultérieure fonctionne ; la 3.11 ou ultérieure est recommandée. Vous le vérifiez à l'étape 5.
  • Le moteur de votre bot. Un moteur d’entraînement pour les tests est fourni avec le programme de connexion.

1. Enregistrer votre bot

Connectez-vous et ouvrez la page des bots (othelloclub.com/fr/flipsight/bot). Dans le bloc Enregistrer mon bot en haut, saisissez un nom dans Nom du bot (2 à 24 caractères). Dans Jeux pris en charge, laissez pour l'instant seulement Standard coché. Choisissez une image dans Envoyer une photo du bot, puis appuyez sur le bouton Enregistrer mon bot.

Résultat attendu : le bloc Jeton de connexion (affiché une seule fois) apparaît avec un long texte commençant par fsbm_.

2. Conserver le jeton

Ce long texte est le jeton, un mot de passe propre à votre bot. Le connecteur s'en sert pour prouver au serveur qu'il agit pour ce bot. Copiez-le en entier et collez-le temporairement quelque part, par exemple dans un éditeur de texte. Une fois le bloc fermé, vous ne pourrez plus revoir ce même jeton.

Traitez le jeton comme un mot de passe. Ne le montrez à personne et n'en publiez pas de capture d'écran. Si vous le perdez ou si quelqu'un l'a vu, ouvrez Gérer dans la liste des bots et appuyez sur Créer un jeton de connexion. Dès que vous obtenez un nouveau jeton, l'ancien cesse immédiatement de fonctionner.

3. Télécharger le connecteur

Appuyez sur Télécharger les exemples Python pour télécharger le fichier ZIP, puis décompressez-le. Ce guide appelle le dossier décompressé « dossier du bot ». Placez-le dans un endroit facile à retrouver, comme le bureau.

Résultat attendu : le dossier du bot contient le fichier README.md et le dossier tools.

4. Ouvrir un terminal dans le dossier du bot

Un terminal est une fenêtre où l'on tape des commandes. Ouvrez-le comme suit selon votre système.

macOS : appuyez sur Command+Space, tapez Terminal et ouvrez-le. Tapez cd suivi d'une espace, faites glisser le dossier du bot dans la fenêtre, puis appuyez sur Entrée.

Linux : dans le gestionnaire de fichiers, faites un clic droit sur le dossier du bot et choisissez « Ouvrir dans un terminal ». Le nom du menu peut varier selon la distribution. S'il n'existe pas, ouvrez un terminal et utilisez cd comme sous macOS.

Windows : ouvrez le dossier du bot dans l'Explorateur de fichiers. Tapez powershell dans la barre d'adresse en haut et appuyez sur Entrée. Une fenêtre PowerShell s'ouvre dans ce dossier.

Vérification : tapez la commande ci-dessous et appuyez sur Entrée. C'est réussi si README.md et tools apparaissent dans la liste. Cette commande est la même sous macOS, Linux et Windows PowerShell.

ls

5. Vérifier Python

Dans la même fenêtre, copiez la commande correspondant à votre système, collez-la et appuyez sur Entrée. Vous pouvez utiliser le bouton de copie en haut à droite de chaque bloc de code.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Résultat attendu : une version commençant par 3, par exemple Python 3.11.9. La version 3.9 ou ultérieure fonctionne. Si la commande est introuvable, installez Python comme indiqué ci-dessous, fermez la fenêtre et recommencez à l'étape 4.

macOS : téléchargez l'installateur macOS (.pkg) sur python.org et installez-le. Exécutez ensuite une fois Install Certificates.command depuis le dossier Python dans Applications. Cela prépare les certificats nécessaires aux connexions sécurisées (HTTPS).

Linux : installez python3 avec la logithèque ou le gestionnaire de paquets de votre distribution.

Windows : installez le Python install manager depuis python.org.

python.org

6. Saisir le jeton

Placez le jeton dans une variable d'environnement, un réglage temporaire qui n'existe que dans cette fenêtre de terminal. Son nom est FS_BOT_TOKEN. Copiez ci-dessous la commande d'une ligne pour votre système, collez-la et appuyez une fois sur Entrée. Bot token s'affiche alors et attend votre saisie. Collez maintenant le jeton copié à l'étape 2 et appuyez sur Entrée.

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

Sous macOS et Linux, le jeton collé ne s'affiche pas, et sous Windows vous voyez des * à la place de ses caractères. Les deux cas sont normaux. Comme le jeton n'est pas écrit dans la commande, il ne reste pas dans l'historique des commandes. Le réglage ne vaut que pour cette fenêtre. Si vous l'avez fermée ou avez ouvert une nouvelle fenêtre, refaites les étapes 4 et 6.

Windows : dans PowerShell, collez avec Ctrl+V ou un clic droit. Cette commande d'une ligne utilise une syntaxe qui fonctionne à la fois dans Windows PowerShell 5.1, fourni avec Windows, et dans le plus récent PowerShell 7, mais elle n'a pas encore été testée sur une vraie machine Windows.

Résultat attendu : l'invite de la commande suivante revient sans erreur.

7. Lancer le connecteur

Dans la même fenêtre, copiez la commande ci-dessous telle quelle, collez-la et appuyez sur Entrée. Ne modifiez pas les noms de fichiers qu'elle contient. Votre bot se connecte avec le moteur d'entraînement, un moteur très faible qui joue la case légale de plus petit numéro.

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'

Il est normal que la commande n’affiche aucun texte et ne rende pas la main pendant son exécution. Pour garder votre bot en ligne, laissez cette fenêtre ouverte avec le programme de connexion et le moteur en marche, et empêchez votre ordinateur de se mettre en veille. Fermer cette fenêtre ou appuyer sur Ctrl+C arrête les deux programmes et met votre bot hors ligne. Relancez la commande ci-dessus pour le remettre en ligne. Le programme de connexion accepte automatiquement les demandes des autres bots.

Tant que le bot tourne avec le moteur d'entraînement, il n'obtient aucun match automatique, et ses matchs contre d'autres bots, même ceux que vous demandez, ne sont pas classés. Relancez-le avec votre propre moteur (étape 10) et il sera apparié et classé à partir de ce moment.

Résultat attendu : dans la liste Bots enregistrés de la page des bots, le point à droite du nom de votre bot est vert. En le survolant, vous voyez En ligne. S'il est gris (Hors ligne), appuyez sur Actualiser.

8. Tester avec une première partie

Sur la page des bots, choisissez votre bot dans Mon bot joueur. Choisissez n'importe quel bot dans la liste Bots FlipSight, appuyez sur Défier et confirmez dans la fenêtre qui s'ouvre. Dans tous les types de jeu, le serveur calcule les coups des bots FlipSight : inutile de garder la salle de jeu ouverte. Avec l'appariement automatique activé (par défaut), le site organise aussi des matchs de bots pendant que le connecteur tourne.

Résultat attendu : la salle de jeu affiche deux plateaux et un pion est posé à chaque tour de votre bot. Le moteur d'entraînement est très faible, il est donc normal de perdre.

9. Continuer après la fermeture de la fenêtre (facultatif)

Lancez la commande ci-dessous à la place de l'étape 7, et le bot continue de tourner après la fermeture de la fenêtre. Exécutez-la dans la fenêtre où vous avez saisi le jeton (étape 6). Il s'arrête si l'ordinateur s'éteint ou se met en veille. L'arrêter pendant une partie fait perdre cette partie au temps ou par déconnexion.

macOS / Linux

macOS / Linux : après avoir exécuté la commande de lancement, vous pouvez fermer la fenêtre. Les messages sont écrits dans le fichier bot.log du dossier du bot. Pour l'arrêter, ouvrez un nouveau terminal et exécutez la commande d'arrêt. Copiez seulement le bloc dont vous avez besoin : si vous collez les deux blocs à la fois, le bot s'arrête juste après avoir démarré.

Lancer

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

Arrêter

pkill -f bot_match_client.py

Windows PowerShell

Windows : une fenêtre réduite apparaît dans la barre des tâches et le bot y tourne. Vous pouvez fermer la fenêtre PowerShell actuelle. Pour l'arrêter, ouvrez la fenêtre réduite et appuyez sur Ctrl+C, ou fermez cette fenêtre.

Lancer

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

10. Passer à votre propre moteur

Après le test avec le moteur d’entraînement, passez à votre moteur : options dédiées ci-dessous pour Edax ou Egaroucid, --nboard pour les autres moteurs compatibles, JSON pour votre moteur personnel. Gardez le même jeton.

Ne cochez une variante (Anti, 10x10, Octagon, Reversi classique) dans Jeux pris en charge que si votre moteur en connaît les règles. Les autres bots et les joueurs peuvent défier votre bot dans chaque jeu coché. Pour modifier ce choix, ouvrez Gérer dans la liste des bots, modifiez Jeux pris en charge et appuyez sur Enregistrer. Ce n'est pas possible pendant une partie en cours ou quand un défi est en attente.

Exemples de requêtes au moteur par jeu

Connecter un moteur compatible NBoard, comme Edax

NBoard est une interface graphique d’Othello utilisable avec NTest. Le protocole NBoard est son format public de communication avec les moteurs. Il fonctionne avec les moteurs qui le prennent en charge, comme NTest et Edax.

Les exemples d’installation et de lancement ci-dessous utilisent Edax 4.6 et Egaroucid for Console 7.8.2.

Edax 4.6 n’est pas inclus dans le programme de connexion. Téléchargez le fichier adapté à votre système dans Assets sur Edax 4.6, placez-le dans le dossier du bot et extrayez-le avec les commandes de préparation ci-dessous. Les commandes Windows et Linux visent les ordinateurs x86-64.

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

Préparer (la première fois seulement)

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

Lancer

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

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

Préparer (la première fois seulement)

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

Lancer

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

macOS: edax-reversi-4.6.tar.gz

Il n’existe pas d’exécutable macOS officiel pour Edax 4.6. Téléchargez Source code (tar.gz) et edax-4.6-linux-x86.tar.gz depuis cette version. Compilez le programme Mac à partir des sources ; seule la donnée commune data/eval.dat est extraite de l’archive Linux. Les outils de ligne de commande Xcode sont nécessaires. Les commandes de préparation compilent les sources.

Préparer (la première fois seulement)

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)

Lancer

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

Edax peut aussi se connecter en indiquant uniquement son exécutable avec --edax ci-dessous. Aucun adaptateur séparé n’est nécessaire ; le connecteur répartit le temps de réflexion selon le temps de partie restant.

Remplacez le chemin de la commande par le chemin complet de votre exécutable. Encadrez les chemins contenant des espaces de guillemets doubles. Si le fichier d’évaluation est introuvable, indiquez eval.dat avec --edax-eval.

Une connexion NBoard ne joue que des parties standard en 8×8. Pour ce bot, ne laissez que Standard coché dans Jeux pris en charge. Si une variante arrive, le connecteur n'interroge pas le moteur, aucun coup n'est joué, et la partie est annulée ou perdue au temps.

Egaroucid: --egaroucid

Préparez Egaroucid for Console. Choisissez l’exécutable console et laissez le dossier resources à sa place. Sur macOS et Linux, utilisez un exécutable compilé selon les instructions officielles. Les commandes natives setboard, settime et go transmettent la position et le temps restant, sans adaptateur.

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"

Ces deux modes prennent uniquement en charge le jeu standard 8×8, avec un moteur par partie simultanée. --max-think-seconds est la limite de sécurité par coup (10 secondes par défaut). Si Egaroucid l’atteint, le connecteur arrête la recherche, joue un coup légal de secours et relance le moteur au tour suivant.

Moteur fait maison : connexion par JSON

Ne lisez cette partie que si vous voulez connecter un moteur que vous avez écrit vous-même. Si vous utilisez Edax, Egaroucid ou un autre moteur NBoard, vous avez déjà terminé plus haut et vous pouvez la passer.

Chaque fois que c'est au tour de votre bot, le connecteur lance my_adapter.py une fois. Il lui transmet la position actuelle sur une ligne (où sont les pions, à qui c'est le tour, quelles cases sont légales, combien de temps il reste), my_adapter.py répond par une case sur une ligne, et le connecteur joue ce coup sur le serveur. my_adapter.py est un petit fichier Python placé entre les deux, qui demande le coup à votre moteur. Le connecteur s'occupe de la connexion au serveur, du jeton et de l'envoi des coups. Ce fichier n'a donc jamais à s'en charger.

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

De quel type est votre moteur ?

  • Un moteur qui choisit son coup dans du code Python : utilisez l'exemple A ci-dessous.
  • Un fichier programme qui affiche une ligne de réponse puis se ferme à chaque lancement : utilisez l'exemple B ci-dessous.
  • Un exécutable qui reste ouvert et échange des commandes en continu : s'il prend en charge NBoard, connectez-le avec --nboard ci-dessus. Pour un autre protocole, adaptez l'exemple B à ce protocole et faites en sorte que my_adapter.py garde le moteur ouvert et échange une ligne à la fois en boucle.

Étape 1 : un fichier qui vérifie seulement la connexion (my_adapter.py)

Dans le dossier du bot (là où se trouvent README.md et tools), créez un fichier nommé my_adapter.py, collez-y le code ci-dessous tel quel et enregistrez-le. Ce fichier n'utilise aucun moteur et répond simplement par la première case légale. Il sert à vérifier que la connexion fonctionne avant de brancher votre moteur.

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)

Vérifier l'entrée et la sortie avant de connecter

La commande du bloc 1 envoie directement la position d'exemple à l'adaptateur. Elle doit afficher une ligne : {"move": 19}. Une fois cette ligne affichée, lancez avec la commande du bloc 2 le connecteur avec cet adaptateur, dans la fenêtre qui contient le jeton de l'étape 6. Exécutez les deux blocs séparément. Si votre moteur lit lui-même ce format JSON, vous pouvez mettre sa commande directement après --engine.

1. Tester l'adaptateur seul

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. Lancer le connecteur avec l'adaptateur

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'

Étape 2 : passer à un fichier qui appelle votre moteur

Une fois que l'étape 1 fonctionne, remplacez tout le contenu de my_adapter.py par l'exemple A ou B ci-dessous. Modifiez seulement les lignes marquées EDIT et laissez le reste tel quel. Relancez ensuite la commande de test 1 ci-dessus et vérifiez qu'une ligne de réponse s'affiche. C'est votre moteur qui a choisi le coup, il est donc normal d'obtenir une autre case que 19.

Exemple A : un moteur qui choisit son coup avec une fonction Python

Cet exemple suppose un fichier nommé my_engine.py contenant une fonction search qui choisit le coup. À EDIT 1, changez le nom du fichier et celui de la fonction, et à EDIT 2, changez les valeurs transmises, pour qu'ils correspondent à votre moteur. La fonction doit renvoyer le numéro de la case (0 à 63) ou une coordonnée comme 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)
Exemple B : un moteur sous forme de fichier programme

Cet exemple suppose que le programme lit une ligne contenant 64 caractères du plateau, le camp qui doit jouer et le temps de réflexion en millisecondes, puis répond par une ligne de coordonnée comme D3 (. = vide, X = noir, O = blanc). Chaque moteur a son propre format. Changez donc le nom du fichier programme à EDIT 1, la ligne envoyée au moteur à EDIT 2 et la partie qui lit la réponse à EDIT 3 pour les adapter à votre moteur.

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)

Temps de réflexion : dans les parties entre bots, chaque bot dispose de 1 minute par partie, donc réfléchir longtemps sur un seul coup fait vite perdre au temps. Dans les exemples, think_ms divise le temps restant (remaining_ms) par le nombre de coups qu'il vous reste à jouer pour fixer le temps de ce coup. budget_ms n'est pas le temps à consacrer à ce coup. C'est le temps maximal pendant lequel le connecteur attend la réponse (10 secondes par défaut). Si la réponse arrive plus tard, le bot ne peut pas continuer cette partie et la perd au temps. Transmettez donc think_ms à votre moteur, et non budget_ms.

En cas de problème : si l'appel du moteur échoue, les fichiers d'exemple écrivent la raison dans my_adapter.log et jouent la première case légale à la place. Si les coups vous semblent bizarres, ouvrez d'abord ce fichier. Le connecteur n'affiche pas à l'écran les messages d'erreur de l'adaptateur. N'utilisez print que pour la ligne de réponse et écrivez tout texte de débogage dans un fichier.

Vitesse : le connecteur garde my_adapter.py ouvert pour chaque partie et envoie une ligne par coup. Un fichier qui répond une ligne puis se termine, comme les exemples, est relancé au coup suivant ; votre moteur redémarre donc à chaque fois. Si le moteur démarre lentement ou doit garder le travail des coups précédents, faites de my_adapter.py une boucle : lire une ligne, répondre une ligne, recommencer. Ne lisez pas l'entrée jusqu'au bout et faites un flush après chaque réponse.

Le reste de cette partie est une documentation de référence sur ce que le connecteur et my_adapter.py échangent. Si vous utilisez les fichiers d'exemple ci-dessus tels quels, vous n'avez pas besoin de la lire.

JSON reçu (connecteur → adaptateur)

Il s’agit de données envoyées par le connecteur, pas de code exécutable. Ne les enregistrez pas dans un fichier Python. Pendant une partie, le connecteur transmet automatiquement les données à chaque tour.

Voici un exemple avec la position de départ standard en 8×8. Dans les vraies parties, le connecteur remplit la position actuelle. board liste les cases à partir de a1 vers la droite, puis la rangée suivante ; 0=vide, 1=noir, 2=blanc. color=1 demande un coup des noirs. legal_moves liste les cases où vous pouvez jouer. remaining_ms est le temps restant à la pendule pour la partie et budget_ms le temps maximal pendant lequel le connecteur attend la réponse pour ce coup, tous deux en millisecondes (1000 = 1 seconde). Les champs et exemples des variantes se trouvent plus bas, dans la section sur les 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 réponse (adaptateur → connecteur)

Écrivez l'une des réponses ci-dessous sur une seule ligne JSON terminée par un saut de ligne. move est un numéro de case ou une coordonnée, sans distinction de majuscules. En 8×8, d3 vaut (3−1)×8+3 = 19. Choisissez toujours parmi les legal_moves reçus. Dans les paires de bots, s'il n'y a aucun coup légal, envoyez -1 ou pass ; dans les parties contre un humain, le serveur gère les passes forcées. N'écrivez que la ligne de réponse sur stdout, sans aucun autre message.

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

Exemples de requêtes au moteur par jeu

Exemples de la première requête pour les noirs que le connecteur envoie à un moteur lancé avec --engine, générés avec le code des règles. Les vraies parties de tous les types partent d'une position aléatoire à 12 pions, donc board et legal_moves diffèrent à chaque partie. Pour que ces exemples restent fixes, leurs premiers coups ont été choisis près du centre du plateau. match_id et les valeurs de pendule diffèrent aussi dans les vraies parties.

  • game_mode: L'une des valeurs std, anti, grand, octagon, reversi. Le XOT des parties contre un humain arrive comme std.
  • board_size: 8, ou 10 pour grand et octagon.
  • board: Tableau de longueur board_size². Se lit à partir de a1 vers la droite, puis la rangée suivante. 0=vide (y compris les cases bloquées de l'Octagon), 1=noir, 2=blanc.
  • blocked_squares: Cases inutilisables de l'Octagon. Tableau vide dans les autres jeux.
  • placed: Pions posés jusqu'ici par couleur, y compris les coups d'ouverture aléatoires ; les clés sont les chaînes "1" et "2". Sert à la limite de 32 pions du Reversi classique.

Champs de la requête au moteur

Anti (anti)

JSON reçu (connecteur → adaptateur)

{
    "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 réponse (adaptateur → connecteur)

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

JSON reçu (connecteur → adaptateur)

{
    "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 réponse (adaptateur → connecteur)

{"move":13}
Octagon (octagon)

JSON reçu (connecteur → adaptateur)

{
    "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 réponse (adaptateur → connecteur)

{"move":13}
Reversi classique (reversi)

JSON reçu (connecteur → adaptateur)

{
    "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 réponse (adaptateur → connecteur)

{"move":18}

Problèmes fréquents

SymptômeCause et solution
Le bot affiche un point gris (hors ligne).Vérifiez que la fenêtre du connecteur tourne toujours. Il s'arrête si vous fermez la fenêtre, appuyez sur Ctrl+C ou si l'ordinateur se met en veille. Refaites les étapes 6 et 7, puis appuyez sur Actualiser.
Un message indique que la commande est introuvable.
command not found / is not recognized
Python n'est pas installé, ou la fenêtre n'a pas été rouverte après l'installation. Consultez l'étape 5, ouvrez une nouvelle fenêtre et recommencez à l'étape 4.
Un message indique que le fichier ne peut pas être ouvert.
can't open file
Vous avez lancé la commande hors du dossier du bot. Placez-vous dans le dossier du bot comme à l'étape 4 et vérifiez avec ls que README.md et tools sont listés.
Un message indique que le jeton est absent, puis le programme s'arrête.
FS_BOT_TOKEN is missing or invalid
Cette fenêtre n'a pas de jeton. Si vous avez ouvert une nouvelle fenêtre, refaites l'étape 6.
Un message indique que le jeton a été refusé, puis le programme s'arrête.
Bot credential rejected
Le jeton a été mal collé ou il est ancien. Après la création d'un nouveau jeton ou la désactivation du bot, l'ancien jeton ne fonctionne plus. Recommencez à l'étape 6 avec le jeton le plus récent.
La même ligne d'erreur revient sans cesse.
Connection/state error
Vérifiez la connexion Internet. Sous macOS, vérifiez que vous avez exécuté Install Certificates.command à l'étape 5. Si elle n'apparaît que pendant les parties, le serveur refuse peut-être le coup de votre moteur parce qu'il est illégal. Le connecteur renvoie le même coup, il faut donc corriger le moteur.
Le bouton de défi ne fonctionne pas, ou le défi n'est pas accepté.Les deux bots doivent avoir un point vert. Vérifiez que votre bot est sélectionné dans Mon bot joueur. Un bot déjà en partie n'accepte pas de nouveau défi. Un défi non accepté expire après 120 secondes.
Le bot ne joue aucun coup, puis perd au temps ou la partie est annulée.
Engine failed for match
Le moteur n'a pas répondu à temps ou sa réponse n'avait pas le bon format. Vérifiez la commande du moteur et sa sortie JSON sur une ligne. Si un bot ne joue pas de premier coup dans les 30 secondes après le début, la partie est annulée.
Aucun défi en variante n'arrive.La fenêtre de défi ne liste que les jeux pris en charge par les deux bots. Vérifiez que le jeu est coché dans Jeux pris en charge de votre bot et que l'autre bot le prend aussi en charge. Un bot connecté par NBoard ne doit avoir que Standard coché.

Glossaire

TermeSignification
APIUne manière convenue pour les programmes d'envoyer des demandes et des réponses. Le connecteur s'en charge pour vous.
JetonUn mot de passe propre à votre bot. Le connecteur s'en sert pour prouver au serveur de quel bot il s'agit.
TerminalUne fenêtre où l'on tape des commandes. Sous Windows, utilisez PowerShell.
Variable d'environnementUn réglage temporaire transmis aux programmes lancés depuis une fenêtre de terminal. Il disparaît quand la fenêtre se ferme.
JSONUn format pour écrire des données sous forme de texte. Exemple : {"move":19}
MoteurUn programme qui examine une position et choisit un coup.
AdaptateurUn petit programme qui convertit les données entre votre moteur et le connecteur.
NBoardNBoard est une interface graphique d’Othello utilisable avec NTest. Le protocole NBoard est son format public de communication avec les moteurs. Il fonctionne avec les moteurs qui le prennent en charge, comme NTest et Edax.
Coup légalUne case où vous pouvez jouer maintenant. Le serveur les liste dans legal_moves.

Téléchargements

Télécharger les exemples Python

OpenAPI 3.1 JSON

README.md

Connectez-vous pour télécharger. Le .zip contient le guide, le connecteur et le moteur exemple ; le .json est la spécification API et le .md le guide.

Spécification détaillée

La suite de cette page est une référence détaillée pour les développeurs qui travaillent directement avec un moteur ou l'API.

Options du connecteur

Options de tools/bot_match_client.py. Il faut exactement une option parmi --engine et --nboard / --edax / --egaroucid. Le jeton n'est jamais une option ; il est lu uniquement dans la variable d'environnement FS_BOT_TOKEN. Codes de sortie : 0 arrêt normal, 2 jeton refusé, 3 --max-runtime atteint.

ParamètresDescription
--engine "COMMAND"Lance cette commande une fois par partie et échange une ligne JSON par coup sans fermer son entrée. Un moteur qui répond une ligne puis se termine est relancé au coup suivant.
--nboard "COMMAND"Garde un moteur NBoard par partie, deux au total. Standard (std) uniquement.
--edax PATHEdax peut aussi se connecter en indiquant uniquement son exécutable avec --edax ci-dessous. Aucun adaptateur séparé n’est nécessaire ; le connecteur répartit le temps de réflexion selon le temps de partie restant.
--edax-eval PATHChemin du fichier Edax eval.dat. Par défaut, recherche data/eval.dat dans le dossier de l’exécutable ou son parent.
--egaroucid PATHPréparez Egaroucid for Console. Choisissez l’exécutable console et laissez le dossier resources à sa place. Sur macOS et Linux, utilisez un exécutable compilé selon les instructions officielles. Les commandes natives setboard, settime et go transmettent la position et le temps restant, sans adaptateur.
--nboard-depth NEnvoie set depth N au moteur NBoard. 0 à 60 ; 0 conserve la valeur par défaut du moteur. (--edax: 0 → 60).
--max-think-seconds SNombre maximal de secondes d'attente du moteur par coup. Par défaut 10, maximum 300. Jamais plus que le temps restant à la pendule moins 0,75 seconde. C'est une limite de sécurité du connecteur ; le serveur n'impose pas de limite de temps par coup. Avec 0, la limite par coup est supprimée ; le temps de partie restant s’applique.
--engine-name NAMENom du moteur indiqué dans les informations du bot (120 caractères au maximum). N'y mettez ni commande ni chemin.
--cpu-name NAMENom du processeur à indiquer (120 caractères au maximum). Détecté automatiquement s'il est omis.
--once-match IDNe traite que ce match et s'arrête à sa fin (terminé, annulé, expiré, refusé). Les identifiants négatifs des parties contre un humain fonctionnent aussi. Pour les tests, combinez-la avec --max-runtime.
--max-runtime SArrête proprement les moteurs et quitte après ce nombre de secondes (code de sortie 3).
--no-streamN'utilise pas le flux d'événements ; interroge uniquement par sondage.
--api URL, --stream-url URLAdresses de l'API et du flux. Gardez normalement les valeurs par défaut. Seul HTTPS est accepté et, comme le jeton est envoyé avec, le flux doit être sur l'hôte de l'API.

Implémenter l'API vous-même

Recevez les défis et les positions avec agent_poll ou le flux d'événements authentifié. Envoyez agent_accept pour les défis en attente où vous êtes bot_b, puis ne calculez que les positions où c'est votre tour dans chaque partie d'un match en cours. Envoyez au plus une fois par match_id, partie (leg) et revision, et abandonnez les calculs périmés quand l'état change. Les parties contre un humain ont un match_id négatif et leg=0, et exigent round_no et room_created_at exactement tels que reçus. Dans les salles normales, le serveur gère les passes forcées ; seules les paires de bots envoient elles-mêmes les passes.

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

Variantes

Un bot ne reçoit que les jeux cochés dans Jeux pris en charge. Le game_mode des requêtes au moteur et de l'état du match indique le jeu. Le serveur calcule la taille du plateau, les cases bloquées et les cases légales, le moteur n'a donc qu'à choisir parmi les legal_moves reçus. Ne générez pas les coups du 10×10 ou du Reversi classique avec la génération de coups du 8×8 standard.

Jeugame_modePlateauPions de départPendule (par partie, par couleur)Premiers legal_moves des noirs
Standardstd8×812 pions au hasard60,000 msDépend de la position
Antianti8×812 pions au hasard60,000 msDépend de la position
Grand (10x10)grand10×1012 pions au hasard60,000 msDépend de la position
Octagonoctagon10×10 (88)12 pions au hasard60,000 msDépend de la position
Reversi classiquereversi8×812 pions au hasard60,000 msDépend de la position

Règles communes : les noirs (1) jouent en premier. Les deux parties partent de la même position avec les couleurs inversées et se jouent en même temps. Dans les paires de bots, quand vous n'avez aucun coup légal, legal_moves arrive sous forme de tableau vide et le bot doit envoyer lui-même move=-1 (passe). Le serveur ne passe pas à votre place, et votre pendule continue de tourner. Passer alors qu'un coup légal existe est illégal. Une partie se termine quand aucune couleur ne peut jouer. Les cases vides ne sont attribuées à personne ; seuls les pions réellement posés sont additionnés sur les deux parties, et des totaux égaux donnent une partie nulle. Toutes les parties entre bots commencent dans une position aléatoire avec 12 pions. Pour chaque variante, chaque bot dispose de 1 minute par partie.

Anti : la pose et le retournement des pions fonctionnent comme à l'Othello standard. Le camp qui a le moins de pions sur les deux parties gagne.

10x10 : règles standard sur un plateau 10×10 (100 cases). Les pions de départ occupent les quatre cases centrales ; les paires de bots ajoutent 8 coups aléatoires et commencent à 12 pions.

Octagon : même plateau et même position de départ que le 10x10, mais trois cases dans chaque coin, 12 au total, sont inutilisables (88 cases jouables). Les cases bloquées arrivent sous forme de numéros de case dans blocked_squares et sous forme de 0, la même valeur que vide, dans board. Le tableau n'est pas trié par ordre numérique. On ne peut pas jouer sur une case bloquée, et les lignes de pions s'y arrêtent.

Reversi classique : selon ses règles, la partie commence sur un plateau vide. Tant que l'une des quatre cases centrales (27, 28, 35, 36) est vide, on ne pose que là et rien n'est retourné. Une fois les quatre remplies, les règles standard s'appliquent. Chaque couleur pose au plus 32 pions, y compris les quatre premiers coups. placed compte les pions posés jusqu'ici par chaque couleur ; ses clés sont les chaînes "1" (noirs) et "2" (blancs). Les paires de bots commencent après que le serveur a posé 12 pions au hasard, dont les quatre coups au centre ; dans la première requête, les deux valeurs de placed totalisent donc 12 (en général {"1":6,"2":6}). Une couleur qui a posé ses 32 pions reçoit un legal_moves vide et envoie -1 à chacun de ses tours.

Exemples de requêtes au moteur par jeu

Exemples de la première requête pour les noirs que le connecteur envoie à un moteur lancé avec --engine, générés avec le code des règles. Les vraies parties de tous les types partent d'une position aléatoire à 12 pions, donc board et legal_moves diffèrent à chaque partie. Pour que ces exemples restent fixes, leurs premiers coups ont été choisis près du centre du plateau. match_id et les valeurs de pendule diffèrent aussi dans les vraies parties.

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 classique (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}

Champs de la requête au moteur

ChampTypeDescription
game_modestringL'une des valeurs std, anti, grand, octagon, reversi. Le XOT des parties contre un humain arrive comme std.
board_sizeinteger 8 | 108, ou 10 pour grand et octagon.
boardinteger[board_size²]Tableau de longueur board_size². Se lit à partir de a1 vers la droite, puis la rangée suivante. 0=vide (y compris les cases bloquées de l'Octagon), 1=noir, 2=blanc.
blocked_squaresinteger[]Cases inutilisables de l'Octagon. Tableau vide dans les autres jeux.
placed{"1": integer, "2": integer}Pions posés jusqu'ici par couleur, y compris les coups d'ouverture aléatoires ; les clés sont les chaînes "1" et "2". Sert à la limite de 32 pions du Reversi classique.
legal_movesinteger[]Cases où vous pouvez jouer maintenant. Si la liste est vide, répondez move=-1.
colorinteger 1 | 2Couleur au trait. 1=noir, 2=blanc.
budget_msintegerTemps accordé pour ce coup en millisecondes : la plus petite valeur entre --max-think-seconds et le temps restant à la pendule moins 0,75 seconde. Le connecteur arrête le moteur en cas de dépassement.
remaining_msintegerTemps restant de votre couleur dans cette partie, en millisecondes, après déduction du temps déjà utilisé pendant ce tour.
match_idinteger ≠ 0Identifiant du match. Négatif (-numéro de salle) pour les parties contre un humain.
leginteger 0 | 1Numéro de partie 0 ou 1. Toujours 0 pour les parties contre un humain.
revisionintegerNuméro d'état de cette partie. Renvoyez-le tel quel avec agent_move.
historyarrayCoups joués dans cette partie. Décidez à partir de board.
round_nointeger | nullNuméro de ronde d'une partie contre un humain. null pour les paires de bots.
room_created_atstring | nullHeure de création de la salle d'une partie contre un humain. null pour les paires de bots.

Défier un autre bot dans une variante

Ajoutez game_mode à agent_challenge ; la valeur par défaut est std. Les deux bots doivent l'avoir dans supported_games. Lisez les jeux de l'adversaire dans bots[].supported_games de la réponse agent_poll. Si l'un des bots ne propose pas ce jeu, la réponse est HTTP 409 avec l'erreur ci-dessous.

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

Modifier supported_games

Le formulaire d'enregistrement commence avec Standard coché, et les anciens bots sans liste enregistrée prennent en charge std. La liste ne peut être modifiée que dans Gérer sur la page web des bots, jamais avec le jeton du bot (API). La modification est refusée pendant un match en cours ou en attente d'acceptation. Une liste vide ne peut pas être enregistrée.

NBoard et variantes

Une connexion --nboard ne prend en charge que le standard (std). Quand une variante arrive, le connecteur n'interroge pas le moteur et abandonne ce tour (message Engine failed for match). Aucun coup n'est envoyé, donc le match est annulé avant le premier coup, ou cette partie est perdue au temps plus tard. Donnez à un bot NBoard le standard comme seul jeu pris en charge.

Humains contre bots enregistrés

Les joueurs peuvent défier votre bot dans n'importe quel jeu enregistré pour lui. S'il prend en charge le standard, des parties STD comme XOT peuvent arriver. Le XOT arrive avec game_mode std et part d'un plateau où 8 coups ont déjà été joués (ils figurent dans history). La personne qui défie choisit la pendule, et la partie n'est pas classée. Un match contre un humain comporte une seule partie (leg 0) et un match_id négatif. Le serveur gère les passes quand aucun coup n'est possible, n'envoyez donc pas -1. Les bots inscrits en ligne dont l’appariement automatique est activé participent aussi aux parties rapides STD/XOT compatibles. Elles sont classées pour les membres et non classées pour les invités.

Coups illégaux et temps

Un coup illégal ou une revision périmée est refusé avec HTTP 409 (illegal_move, stale_revision, not_your_turn) et le plateau ne change pas. La pendule continue de tourner pendant ce temps. Le connecteur renvoie le même coup avec la même revision, donc si le moteur n'est pas corrigé, cette partie finit par être perdue au temps. Le serveur n'a pas de limite par coup et ne vérifie que le temps restant de chaque couleur. Les 10 secondes sont la valeur par défaut de --max-think-seconds du connecteur.

Authentification

Utilisez HTTPS, JSON POST et Authorization: Bearer. Le jeton appartient à un seul bot. Ne le placez jamais dans une URL, du code public ou des journaux.

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

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

Règles du match

Toutes les parties entre bots commencent dans une position aléatoire avec 12 pions. Deux parties se jouent simultanément en inversant les couleurs. Le total des pions détermine le vainqueur ; le plus petit total gagne en Anti.

Toutes les parties entre bots commencent dans une position aléatoire avec 12 pions. Pour chaque variante, chaque bot dispose de 1 minute par partie.

Si un bot n’a pas joué son premier coup dans les 30 secondes suivant le début, le match est annulé et non classé. Si, pendant le match, la connexion d’un bot est perdue plus de 30 secondes alors que l’autre est connecté, le bot connecté gagne (finish_reason: disconnect) et le résultat est classé.

Le serveur calcule les coups des bots FlipSight dans tous les types de jeu. Les défis web et les défis par l'API (agent_challenge) sont acceptés, et le serveur lance le match aussitôt : inutile de garder la salle de jeu ouverte. Si le calcul du serveur s'arrête plus de 30 secondes, le match est annulé (finish_reason: technical) et n'est pas classé. Les variantes coûtent cher à calculer : au plus 2 matchs de variante contre des bots FlipSight se jouent à la fois. Quand tout est pris, vous recevez HTTP 409 avec site bot variant matches are full ; réessayez un peu plus tard.

Matchs de bots automatiques : le site garde toujours deux salles de matchs de bots ouvertes. Tant qu'un bot enregistré dont l'appariement automatique est activé est en ligne et ne joue pas, il reçoit un défi de match de bots standard (std) contre un bot de classement proche (d'abord un autre bot enregistré, sinon un bot FlipSight), en tant que bot_b. Le connecteur l'accepte automatiquement et le match commence. Si le bot n'accepte pas sous 120 secondes ou refuse, il est exclu de l'appariement automatique pendant 10 minutes. Les matchs automatiques sont classés. Les matchs automatiques de bots continuent pendant les parties d’Arena. L'appariement automatique est activé par défaut et se désactive dans Gérer sur la page des bots. Un bot lancé avec le moteur d'entraînement (environment.practice vaut true) est exclu des matchs automatiques, et ses matchs contre d'autres bots pendant ce temps ne sont pas classés.

Un défi direct humain joue une partie dans une salle normale avec un jeu et un temps compatibles. Le serveur impose toujours le mode non classé aux défis directs. Les bots inscrits en ligne dont l’appariement automatique est activé participent aussi aux parties rapides STD/XOT compatibles. Elles sont classées pour les membres et non classées pour les invités. Un bot enregistré n'accepte que le nombre de parties fixé par son réglage de parties simultanées (2 par défaut). Une partie contre un humain et un match entre bots (deux plateaux) comptent chacun pour 1.

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)

Déroulement d’un match

Toutes les parties entre bots commencent dans une position aléatoire avec 12 pions. Deux parties se jouent simultanément en inversant les couleurs. Le total des pions détermine le vainqueur ; le plus petit total gagne en Anti.

Votre numéro de participant est 0 si vous êtes bot_a et 1 si vous êtes bot_b. Le participant 0 a les noirs (1) dans legs[0] et les blancs (2) dans legs[1] ; c’est l’inverse pour le participant 1. C’est votre tour quand leg.status vaut active et que leg.turn correspond à votre couleur. Ce peut être votre tour dans les deux parties à la fois.

Si vous n’avez aucun coup légal, vous devez envoyer vous-même move -1 (passe) ; le serveur ne passe pas à votre place. Dans les parties contre un humain, le serveur gère les passes comme dans les salles de jeu normales.

À votre tour, temps restant = clocks_ms[votre couleur - 1] - (server_now_ms - turn_started_ms). clocks_ms suit l’ordre des couleurs (noir, blanc) ; totals et winner suivent l’ordre des participants (winner 0 ou 1, null en cas de nulle).

Numéros de case

Numéro de case = ligne × 8 + colonne, donc a1=0, h1=7, a2=8 et h8=63. Par exemple, 19 est d3 et 37 est f5. Le tableau board suit le même ordre, avec 0=vide, 1=noir, 2=blanc.

    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), case = rangée × 10 + colonne. Les colonnes vont de a à j, et la dixième rangée s'écrit 10 ou 0 dans les coordonnées. a1=0, j1=9, a2=10, j10=99 (identique à j0). Une même coordonnée a un numéro différent selon la taille du plateau : d3 vaut 19 en 8×8 et 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 ## ##

Flux d’événements

Connectez-vous avec le même jeton Bearer à GET https://othelloclub.com/flipsight_api/bot-stream pour recevoir un événement JSON par ligne (NDJSON) : hello confirme la connexion, match signale qu’un de vos matchs a changé (les événements entre bots incluent l’objet match complet) et ping est un maintien de connexion toutes les 10 secondes. À la réception de match, appelez agent_poll une fois. Si la connexion tombe, reconnectez-vous avec un délai croissant de 1 à 30 secondes et continuez à interroger entre-temps. Tant que le flux est connecté, votre bot apparaît en ligne ; chaque bot peut ouvrir jusqu’à 3 flux.

Exemples d’événements

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}

Adaptateur moteur

Le connecteur de bot FlipSight (Python 3.9+, bibliothèque standard uniquement) fonctionne sous Windows, macOS et Linux et connecte un moteur de deux façons. --engine lance le moteur une fois par partie, envoie une ligne JSON à chaque coup et lit une ligne {"move": case}. Un moteur qui répond une ligne puis se termine fonctionne toujours, car il est relancé au coup suivant, mais un moteur qui lit l'entrée jusqu'au bout (sys.stdin.read(), etc.) ne répond jamais : lisez une ligne à la fois. legal_moves dans la requête liste les cases légales ; si elle est vide, répondez -1. --nboard garde un moteur NBoard en marche, un par partie ; réglez sa profondeur avec --nboard-depth.

Edax peut aussi se connecter en indiquant uniquement son exécutable avec --edax ci-dessous. Aucun adaptateur séparé n’est nécessaire ; le connecteur répartit le temps de réflexion selon le temps de partie restant.

Préparez Egaroucid for Console. Choisissez l’exécutable console et laissez le dossier resources à sa place. Sur macOS et Linux, utilisez un exécutable compilé selon les instructions officielles. Les commandes natives setboard, settime et go transmettent la position et le temps restant, sans adaptateur.

JSON reçu (connecteur → adaptateur)

Il s’agit de données envoyées par le connecteur, pas de code exécutable. Ne les enregistrez pas dans un fichier Python. Pendant une partie, le connecteur transmet automatiquement les données à chaque tour.

{"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 réponse (adaptateur → connecteur)

{"move":2}

Synchronisation

Avec le flux connecté, appelez agent_poll à chaque événement et toutes les 10 secondes. Sans flux, interrogez chaque seconde pendant un match et toutes les 5 secondes au repos, jamais plus d’une fois par seconde. Le temps réseau est décompté de votre pendule. Si la réponse à un coup est perdue, relisez le nouvel état et ne renvoyez le même coup que si la revision est inchangée.

Actions API

Les exemples de réponse montrent le format. Le bouton de copie copie seulement la requête et n’appelle pas l’API.

POST agent_poll

Lire l’état

ParamètresTypeObligatoireDescription
actionstring "agent_poll"ObligatoireNom de l’action à appeler
environmentobjectFacultatifCPU, OS et nom du moteur du PC du connecteur (120 caractères max. chacun, facultatif) et practice (true ou false, facultatif). practice à true signifie que le bot s'entraîne : il est exclu des matchs automatiques, et les matchs entre bots demandés ou acceptés pendant ce temps ne sont pas classés. Le connecteur l'envoie tout seul quand il est lancé avec le moteur d'entraînement. Le serveur garde la dernière valeur reçue : à la fin de l'entraînement, envoyez-la sans practice ou avec false.

Exemple de requête

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

Exemple de réponse

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

Objet match (matches[])

ChampTypeDescription
idintegerID du match (négatif pour les parties contre un humain)
kindstringhuman pour les parties contre un humain
bot_aintegerID du bot du participant 0 (0 pour un humain)
bot_bintegerID du bot du participant 1 (0 pour un humain)
game_modestringJeu (std, anti, grand, octagon, reversi)
time_limit_secintegerSecondes accordées par partie et par couleur
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerDate limite d’acceptation (millisecondes)
rankedbooleanIndique si le match est classé
state.statusstringactive, finished ou aborted
state.game_modestringJeu (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Cases par côté, 8 ou 10
state.blocked_squaresinteger[]Cases inutilisables (Octagon uniquement, non triées)
state.legsarrayParties (2 entre bots, 1 contre un humain)
state.totals[integer, integer]Total des pions des deux parties par participant
state.winnerinteger | nullParticipant gagnant 0 ou 1, null en cas de nulle
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Objet partie (state.legs[])

ChampTypeDescription
boardinteger[64 | 100]64 / 100 cases, 0=vide 1=noir 2=blanc
turnintegerCouleur au trait, 1 (noir) ou 2 (blanc)
legal_movesinteger[]Cases légales pour la couleur au trait. Vide pour les parties terminées
placed{"1": integer, "2": integer}Pions posés par couleur, clés "1" (noirs) et "2" (blancs)
black_participantintegerParticipant qui a les noirs dans cette partie
clocks_ms[number, number]Bases de temps restant dans l’ordre des couleurs (noir, blanc)
turn_started_msintegerHeure du serveur au début du tour en cours
revisionintegerAugmente de 1 à chaque changement de cette partie
statusstringactive, finished, timeout, stopped
historyarrayCoups joués (color, participant, move, at_ms)
discs[integer, integer]Nombre de pions dans l’ordre des couleurs (noir, blanc)
result_discs[integer, integer] (optional)Score arbitré facultatif [noirs, blancs] pour une partie perdue au temps ; il est totalisé à la place des pions. Scores pour le camp qui a dépassé le temps et son adversaire : std et reversi 0 à 64, grand 0 à 100, octagon 0 à 88, anti 64 à 0. L'autre partie continue, et les totaux désignent le vainqueur une fois les deux parties terminées. Les anciens résultats terminés ne sont pas recalculés.

POST agent_challenge

Défier

Indiquez target_id (numéro du bot) ou target_name (nom du bot), disponibles dans la liste bots de la réponse d'agent_poll. Les bots FlipSight intégrés (is_builtin: true) peuvent être défiés par l'API dans tous les types de jeu, et le serveur lance le match aussitôt. Si 2 matchs de variante contre des bots intégrés sont déjà ouverts, vous recevez HTTP 409 avec site bot variant matches are full.

Défier depuis la page des bots

ParamètresTypeObligatoireDescription
actionstring "agent_challenge"ObligatoireNom de l’action à appeler
target_idinteger ≥ 1FacultatifID du bot à défier
target_namestringFacultatifNom du bot à défier (au lieu de target_id)
game_modestring std | anti | octagon | grand | reversiFacultatifJeu pour une paire de bots. std par défaut. Les deux bots doivent le prendre en charge. Standard / Anti / Octagon / Grand (10x10) / Reversi classique

Exemple de requête

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

Exemple de réponse

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

POST agent_accept

Accepter

ParamètresTypeObligatoireDescription
actionstring "agent_accept"ObligatoireNom de l’action à appeler
match_idinteger ≥ 1ObligatoireID du match (négatif pour les parties contre un humain)

Exemple de requête

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

Exemple de réponse

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

POST agent_reject

Refuser

ParamètresTypeObligatoireDescription
actionstring "agent_reject"ObligatoireNom de l’action à appeler
match_idinteger ≥ 1ObligatoireID du match (négatif pour les parties contre un humain)

Exemple de requête

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

Exemple de réponse

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

POST agent_move

Envoyer un coup

ParamètresTypeObligatoireDescription
actionstring "agent_move"ObligatoireNom de l’action à appeler
match_idinteger ≠ 0ObligatoireID du match (négatif pour les parties contre un humain)
round_nointeger ≥ 0FacultatifParties contre un humain uniquement : round_no de l’objet match
room_created_atstringFacultatifParties contre un humain uniquement : room_created_at de l’objet match
leginteger 0 | 1ObligatoirePartie 0 ou 1 (0 pour les parties contre un humain)
revisioninteger ≥ 0Obligatoirerevision de la partie pour laquelle le coup a été calculé ; 409 stale_revision si la partie a changé
moveinteger -1..99 | stringObligatoireCase 0-(board_size²−1), -1 pour passer / d3 / D3 / pass

Exemple de requête

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

Exemple de réponse

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

Erreurs

Les erreurs ont la forme {"ok": false, "error": "…"}. Pour 401, arrêtez et vérifiez le jeton. Pour 409, relisez l’état actuel et vérifiez le tour, revision et les coups légaux. 429 signifie trop de flux pour un même bot. Réessayez les erreurs de connexion et 503 avec des délais croissants, et ne renvoyez jamais un coup illégal.

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