Documentazione API

Guida passo passo per principianti

Cosa ti serve

  • Un account FlipSight. Dopo l'accesso puoi registrare un bot e scaricare il connettore.
  • Una foto del bot: un file GIF, JPG o PNG di massimo 5 MB, largo e alto almeno 175 px.
  • Il tuo computer su cui eseguire il bot Le mosse del tuo bot vengono calcolate sul computer su cui gira il connettore. Anche con lo stesso motore la forza può cambiare in base alla potenza del computer e alle impostazioni del motore, e se cambi computer o impostazioni il punteggio può muoversi di conseguenza.
  • Python 3, un programma gratuito che esegue il connettore. Funziona dalla versione 3.9 in poi; si consiglia la 3.11 o successiva. Lo controlli al passo 5.
  • Il motore del tuo bot. Il connettore include un motore di pratica per i test.

1. Registra il tuo bot

Accedi e apri la pagina dei bot (othelloclub.com/it/flipsight/bot). Nel riquadro Registra il mio bot in alto, inserisci un nome in Nome del bot (2–24 caratteri). In Giochi supportati lascia per ora selezionato solo Standard. Scegli un'immagine in Carica foto del bot, poi premi il pulsante Registra il mio bot.

Risultato atteso: compare il riquadro Token di connessione (visibile una sola volta) con un lungo testo che inizia con fsbm_.

2. Conserva il token

Questo lungo testo è il token, una password solo per il tuo bot. Il connettore lo usa per dimostrare al server che agisce per questo bot. Copialo tutto e incollalo temporaneamente da qualche parte, per esempio in un editor di testo. Una volta chiuso il riquadro, non potrai più vedere lo stesso token.

Tratta il token come una password. Non mostrarlo a nessuno e non pubblicarne screenshot. Se lo perdi o qualcuno l'ha visto, apri Gestisci nell'elenco dei bot e premi Genera token di connessione. Quando ottieni un nuovo token, quello vecchio smette subito di funzionare.

3. Scarica il connettore

Premi Scarica esempi Python per scaricare il file ZIP ed estrailo. Questa guida chiama la cartella estratta «cartella del bot». Mettila in un posto facile da trovare, come la scrivania.

Risultato atteso: la cartella del bot contiene il file README.md e la cartella tools.

4. Apri un terminale nella cartella del bot

Un terminale è una finestra in cui si scrivono comandi. Aprilo come segue in base al tuo sistema.

macOS: premi Command+Space, digita Terminale e aprilo. Digita cd e uno spazio, trascina la cartella del bot nella finestra, poi premi Invio.

Linux: nel gestore di file, fai clic destro sulla cartella del bot e scegli «Apri nel terminale». Il nome del menu può cambiare tra le distribuzioni. Se il menu non c'è, apri un terminale e usa cd come su macOS.

Windows: apri la cartella del bot in Esplora file. Digita powershell nella barra degli indirizzi in alto e premi Invio. Si apre una finestra di PowerShell in quella cartella.

Verifica: digita il comando qui sotto e premi Invio. Funziona se README.md e tools compaiono nell'elenco. Questo comando è lo stesso su macOS, Linux e Windows PowerShell.

ls

5. Controlla Python

Nella stessa finestra, copia il comando per il tuo sistema, incollalo e premi Invio. Puoi usare il pulsante di copia in alto a destra di ogni riquadro di codice.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Risultato atteso: una versione che inizia con 3, come Python 3.11.9. Funziona dalla versione 3.9 in poi. Se il comando non viene trovato, installa Python come indicato sotto, chiudi la finestra e ricomincia dal passo 4.

macOS: scarica il programma di installazione per macOS (.pkg) da python.org e installalo. Poi esegui una volta Install Certificates.command dalla cartella di Python in Applicazioni. Così si preparano i certificati necessari per le connessioni sicure (HTTPS).

Linux: installa python3 con il gestore software o il gestore di pacchetti della tua distribuzione.

Windows: installa il Python install manager da python.org.

python.org

6. Inserisci il token

Metti il token in una variabile d'ambiente, un'impostazione temporanea che esiste solo in questa finestra del terminale. Il suo nome è FS_BOT_TOKEN. Copia qui sotto il comando di una riga per il tuo sistema, incollalo e premi Invio una volta. Compare quindi Bot token, in attesa del tuo inserimento. Ora incolla il token copiato al passo 2 e premi Invio.

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

Su macOS e Linux il token incollato non viene mostrato, e su Windows vedi * al posto dei suoi caratteri. In entrambi i casi è normale. Poiché il token non è scritto nel comando, non resta nella cronologia dei comandi. L'impostazione vale solo in questa finestra. Se l'hai chiusa o hai aperto una nuova finestra, ripeti i passi 4 e 6.

Windows: in PowerShell, incolla con Ctrl+V o con un clic destro. Questo comando di una riga usa una sintassi che funziona sia in Windows PowerShell 5.1, incluso in Windows, sia nel più recente PowerShell 7, ma non è ancora stato provato su un vero computer Windows.

Risultato atteso: il prompt per il comando successivo ricompare senza errori.

7. Avvia il connettore

Nella stessa finestra, copia il comando qui sotto così com'è, incollalo e premi Invio. Non cambiare i nomi dei file che contiene. Il tuo bot si collega con il motore di prova, un motore molto debole che gioca la casella legale con il numero più basso.

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'

È normale che il comando non mostri testo e non restituisca il prompt durante l’esecuzione. Per mantenere il bot online, lascia questa finestra aperta con il connettore e il motore in esecuzione e impedisci al computer di andare in sospensione. Chiudendo questa finestra o premendo Ctrl+C, entrambi i programmi si arrestano e il bot va offline. Esegui di nuovo il comando sopra per riportarlo online. Il connettore accetta automaticamente le richieste degli altri bot.

Finché il bot gira con il motore di prova non riceve incontri automatici, e i suoi incontri contro altri bot, anche quelli che chiedi tu, non sono classificati. Riavvialo con il tuo motore (passo 10) e da quel momento viene abbinato e classificato.

Risultato atteso: nell'elenco Bot registrati della pagina dei bot, il pallino a destra del nome del tuo bot è verde. Passandoci sopra con il mouse compare Online. Se è grigio (Offline), premi Aggiorna.

8. Prova con una prima partita

Nella pagina dei bot, scegli il tuo bot in Il mio bot giocatore. Scegli un bot qualsiasi nell'elenco Bot FlipSight, premi Sfida e conferma nella finestra che si apre. In tutti i tipi di gioco il server calcola le mosse dei bot FlipSight, quindi non serve tenere aperta la stanza di gioco. Con l'abbinamento automatico attivo (predefinito), il sito organizza anche incontri tra bot mentre il connettore è in esecuzione.

Risultato atteso: la stanza di gioco mostra due scacchiere e a ogni turno del tuo bot viene posata una pedina. Il motore di prova è molto debole, quindi perdere è normale.

9. Lascialo attivo dopo aver chiuso la finestra (facoltativo)

Avvia con il comando qui sotto invece del passo 7, e il bot continua a funzionare dopo la chiusura della finestra. Eseguilo nella stessa finestra in cui hai inserito il token (passo 6). Si ferma se il computer si spegne o va in stop. Se lo fermi durante una partita, perdi quella partita per tempo o per disconnessione.

macOS / Linux

macOS / Linux: dopo aver eseguito il comando di avvio puoi chiudere la finestra. I messaggi vengono scritti nel file bot.log nella cartella del bot. Per fermarlo, apri un nuovo terminale ed esegui il comando di arresto. Copia solo il riquadro che ti serve: se incolli entrambi i riquadri insieme, il bot si ferma subito dopo l'avvio.

Avvia

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

Ferma

pkill -f bot_match_client.py

Windows PowerShell

Windows: nella barra delle applicazioni compare una finestra ridotta a icona in cui gira il bot. Puoi chiudere la finestra di PowerShell attuale. Per fermarlo, apri la finestra ridotta a icona e premi Ctrl+C, oppure chiudi quella finestra.

Avvia

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

10. Passa al tuo motore

Dopo la prova di connessione, passate al vostro motore: opzioni dedicate sotto per Edax o Egaroucid, --nboard per altri motori compatibili, JSON per un motore proprio. Il token resta lo stesso.

Seleziona una variante (Anti, 10x10, Octagon, Reversi classico) in Giochi supportati solo se il tuo motore ne conosce le regole. Altri bot e persone possono sfidare il tuo bot in ogni gioco selezionato. Per cambiarlo, apri Gestisci nell'elenco dei bot, modifica Giochi supportati e premi Salva. Non puoi cambiarlo mentre una partita è in corso o una sfida è in attesa.

Esempi di richieste al motore per gioco

Collega un motore compatibile NBoard, come Edax

NBoard è un’applicazione grafica di Othello utilizzabile con NTest. Il protocollo NBoard è il suo formato pubblico di comunicazione con i motori e funziona con motori che lo supportano, come NTest ed Edax.

Gli esempi di installazione e avvio qui sotto usano Edax 4.6 ed Egaroucid for Console 7.8.2.

Edax 4.6 non è incluso nel connettore. Scarica il file per il tuo sistema da Assets su Edax 4.6, mettilo nella cartella del bot ed estrailo con i comandi di preparazione sotto. I comandi Windows e Linux sono per computer x86-64.

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

Prepara (solo la prima volta)

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

Avvia

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

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

Prepara (solo la prima volta)

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

Avvia

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

macOS: edax-reversi-4.6.tar.gz

Non esiste un eseguibile macOS ufficiale di Edax 4.6. Scarica Source code (tar.gz) ed edax-4.6-linux-x86.tar.gz dalla stessa versione. Compila l’eseguibile Mac dal sorgente; dall’archivio Linux viene estratto solo il file di valutazione condiviso data/eval.dat. Servono gli strumenti da riga di comando Xcode. I comandi di preparazione compilano il sorgente.

Prepara (solo la prima volta)

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)

Avvia

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

Edax può collegarsi anche indicando solo il percorso dell’eseguibile con --edax. Non serve un adattatore; il connettore distribuisce il tempo per ogni mossa in base al tempo di partita rimasto.

Sostituite il percorso nel comando con quello completo del vostro eseguibile. Racchiudete tra virgolette doppie i percorsi con spazi. Se il file di valutazione non viene trovato, indicate eval.dat con --edax-eval.

Una connessione NBoard gioca solo partite standard 8×8. Per questo bot lascia selezionato solo Standard in Giochi supportati. Se arriva una variante, il connettore non interpella il motore, nessuna mossa viene giocata e la partita viene annullata o persa per tempo.

Egaroucid: --egaroucid

Preparate Egaroucid for Console. Scegliete l’eseguibile console e mantenete resources al suo posto. Su macOS e Linux, usate un eseguibile compilato seguendo le istruzioni ufficiali. Il connettore invia posizione e tempo rimasto con setboard, settime e go, senza adattatore.

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"

Entrambe le modalità supportano solo il gioco standard 8×8, con un motore per ogni partita simultanea. --max-think-seconds è il limite di sicurezza per mossa (10 secondi predefiniti). Se Egaroucid lo raggiunge, la ricerca viene fermata, viene giocata una mossa legale di riserva e il motore riparte al turno seguente.

Motore fatto da te: collegarlo tramite JSON

Leggi questa parte solo se vuoi collegare un motore scritto da te. Se usi Edax, Egaroucid o un altro motore NBoard, hai già finito più sopra e puoi saltarla.

Ogni volta che tocca al tuo bot, il connettore esegue my_adapter.py una volta. Gli passa la posizione attuale in una riga (dove sono le pedine, a chi tocca, quali caselle sono legali, quanto tempo resta), my_adapter.py risponde con una casella in una riga e il connettore gioca quella mossa sul server. my_adapter.py è un piccolo file Python che sta nel mezzo e chiede la mossa al tuo motore. Il connettore si occupa della connessione al server, del token e dell'invio delle mosse, quindi questo file non deve mai pensarci.

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

Di che tipo è il tuo motore?

  • Un motore che sceglie la mossa con codice Python: usa l'esempio A qui sotto.
  • Un file di programma che a ogni avvio stampa una riga di risposta e si chiude: usa l'esempio B qui sotto.
  • Un eseguibile che resta aperto e scambia comandi di continuo: se supporta NBoard, collegalo con --nboard qui sopra. Con un altro protocollo, adatta l'esempio B a quel protocollo e fai in modo che my_adapter.py tenga aperto il motore e scambi una riga alla volta in un ciclo.

Passo 1: un file che controlla solo la connessione (my_adapter.py)

Nella cartella del bot (dove si trovano README.md e tools) crea un file chiamato my_adapter.py, incolla il codice qui sotto così com'è e salvalo. Questo file non usa nessun motore e risponde semplicemente con la prima casella legale. Serve a verificare che la connessione funzioni prima di collegare il tuo motore.

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)

Controllare ingresso e uscita prima di collegare

Il comando del riquadro 1 passa la posizione di esempio direttamente all'adattatore. Dovrebbe stampare una riga: {"move": 19}. Quando vedi questa riga, usa il comando del riquadro 2 per avviare il connettore con questo adattatore, nella finestra che contiene il token del passo 6. Esegui i due riquadri separatamente. Se il tuo motore legge da solo questo formato JSON, puoi mettere il suo comando direttamente dopo --engine.

1. Prova solo l'adattatore

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. Avvia il connettore con l'adattatore

macOS / Linux

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

Windows PowerShell

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

Passo 2: passare a un file che chiama il tuo motore

Quando il passo 1 funziona, sostituisci tutto il contenuto di my_adapter.py con l'esempio A o B qui sotto. Cambia solo le righe contrassegnate con EDIT e lascia il resto com'è. Poi esegui di nuovo il comando di prova 1 qui sopra e controlla che compaia una riga di risposta. La mossa l'ha scelta il tuo motore, quindi è normale che esca una casella diversa da 19.

Esempio A: un motore che sceglie la mossa con una funzione Python

Questo esempio presume un file chiamato my_engine.py con una funzione search che sceglie la mossa. In EDIT 1 cambia il nome del file e quello della funzione, e in EDIT 2 cambia i valori passati, in modo che corrispondano al tuo motore. La funzione deve restituire il numero della casella (da 0 a 63) oppure una coordinata come 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)
Esempio B: un motore che è un file di programma

Questo esempio presume che il programma legga una riga con 64 caratteri della scacchiera, il colore che deve muovere e il tempo di riflessione in millisecondi, e che risponda con una riga con una coordinata come D3 (. = vuota, X = nero, O = bianco). Ogni motore ha il suo formato, quindi cambia il nome del file di programma in EDIT 1, la riga inviata al motore in EDIT 2 e la parte che legge la risposta in EDIT 3 in modo che corrispondano al tuo motore.

my_adapter.py → my_engine

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

my_engine → my_adapter.py

D3
import json
import subprocess
import sys
from pathlib import Path

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


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


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


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

Tempo di riflessione: nelle partite tra bot ogni bot ha 1 minuto per partita, quindi pensare a lungo su una sola mossa fa perdere presto per tempo. Negli esempi, think_ms divide il tempo rimasto (remaining_ms) per le mosse che ti restano e decide così il tempo per questa mossa. budget_ms non è il tempo da spendere per questa mossa. È il tempo massimo per cui il connettore aspetta la risposta (10 secondi come impostazione predefinita). Se la risposta arriva più tardi, il bot non può continuare quella partita e la perde per tempo, quindi passa al tuo motore think_ms e non budget_ms.

Quando qualcosa non va: se la chiamata al motore fallisce, i file di esempio scrivono il motivo in my_adapter.log e giocano al suo posto la prima casella legale. Se le mosse ti sembrano strane, apri prima di tutto quel file. Il connettore non mostra sullo schermo i messaggi di errore dell'adattatore. Usa print solo per la riga di risposta e scrivi su un file qualsiasi testo di debug.

Velocità: il connettore tiene aperto my_adapter.py per ogni partita e invia una riga per mossa. Un file che risponde una riga e termina, come gli esempi, viene riavviato alla mossa successiva, quindi anche il tuo motore si riavvia ogni volta. Se il motore è lento ad avviarsi o deve conservare il lavoro delle mosse precedenti, trasforma my_adapter.py in un ciclo: leggi una riga, rispondi una riga, ripeti. Non leggere l'input fino alla fine e fai flush dopo ogni risposta.

Il resto di questa parte è materiale di riferimento su ciò che il connettore e my_adapter.py si scambiano. Se usi i file di esempio qui sopra così come sono, non hai bisogno di leggerlo.

JSON in ingresso (connettore → adattatore)

Questi sono dati di esempio inviati dal connettore, non codice eseguibile. Non salvarli come file Python. Durante la partita, il connettore invia automaticamente i dati aggiornati a ogni turno.

Qui sotto trovi un esempio con la posizione iniziale standard 8×8. Nelle partite reali il connettore inserisce la posizione attuale. board elenca le caselle da a1 verso destra, poi la riga successiva; 0=vuota, 1=nero, 2=bianco. color=1 chiede una mossa del nero. legal_moves elenca le caselle in cui puoi giocare. remaining_ms è il tempo rimasto sull'orologio della partita e budget_ms il tempo massimo per cui il connettore aspetta la risposta a questa mossa, entrambi in millisecondi (1000 = 1 secondo). Campi ed esempi per le varianti sono più in basso, nella sezione sulle varianti.

{"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 di risposta (adattatore → connettore)

Scrivi una delle risposte qui sotto come un'unica riga JSON che termina con un a capo. move è un numero di casella o una coordinata, senza distinzione tra maiuscole e minuscole. Su 8×8, d3 è (3−1)×8+3 = 19. Scegli sempre tra le legal_moves ricevute. Nelle coppie di bot, se non hai mosse legali, invia -1 o pass; nelle partite contro persone il server risolve i passi forzati. Scrivi su stdout solo la riga di risposta, senza altri messaggi.

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

Esempi di richieste al motore per gioco

Esempi della prima richiesta per il nero che il connettore invia a un motore avviato con --engine, generati con il codice delle regole. Le partite reali di ogni tipo partono da una posizione casuale con 12 pedine, quindi board e legal_moves cambiano in ogni partita. Per tenere fissi questi esempi, le loro mosse iniziali sono state scelte vicino al centro della scacchiera. Anche match_id e i valori dell'orologio sono diversi nelle partite reali.

  • game_mode: Uno tra std, anti, grand, octagon, reversi. Lo XOT nelle partite contro persone arriva come std.
  • board_size: 8, oppure 10 per grand e octagon.
  • board: Array di lunghezza board_size². Si legge da a1 verso destra, poi la riga successiva. 0=vuota (comprese le caselle bloccate di Octagon), 1=nero, 2=bianco.
  • blocked_squares: Caselle inutilizzabili di Octagon. Array vuoto negli altri giochi.
  • placed: Pedine posate finora per colore, comprese le mosse iniziali casuali; le chiavi sono le stringhe "1" e "2". Serve per il limite di 32 pedine del Reversi classico.

Campi della richiesta al motore

Anti (anti)

JSON in ingresso (connettore → adattatore)

{
    "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 di risposta (adattatore → connettore)

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

JSON in ingresso (connettore → adattatore)

{
    "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 di risposta (adattatore → connettore)

{"move":13}
Octagon (octagon)

JSON in ingresso (connettore → adattatore)

{
    "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 di risposta (adattatore → connettore)

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

JSON in ingresso (connettore → adattatore)

{
    "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 di risposta (adattatore → connettore)

{"move":18}

Problemi comuni

SintomoCausa e soluzione
Il bot mostra un pallino grigio (offline).Controlla che la finestra del connettore sia ancora in esecuzione. Si ferma se chiudi la finestra, premi Ctrl+C o il computer va in stop. Ripeti i passi 6 e 7, poi premi Aggiorna.
Dice che il comando non è stato trovato.
command not found / is not recognized
Python manca, oppure la finestra non è stata riaperta dopo l'installazione. Vedi il passo 5, apri una nuova finestra e ricomincia dal passo 4.
Dice che il file non si può aprire.
can't open file
L'hai eseguito fuori dalla cartella del bot. Spostati nella cartella del bot come al passo 4 e controlla con ls che README.md e tools siano elencati.
Dice che il token manca e si chiude.
FS_BOT_TOKEN is missing or invalid
Questa finestra non ha il token. Se hai aperto una nuova finestra, ripeti il passo 6.
Dice che il token è stato rifiutato e si ferma.
Bot credential rejected
Il token è stato incollato male o è vecchio. Dopo aver generato un nuovo token o disattivato il bot, il vecchio token non funziona più. Ricomincia dal passo 6 con il token più recente.
La stessa riga di errore continua a comparire.
Connection/state error
Controlla la connessione a internet. Su macOS, controlla di aver eseguito Install Certificates.command al passo 5. Se compare solo durante le partite, il server potrebbe rifiutare la mossa del tuo motore perché illegale. Il connettore invia di nuovo la stessa mossa, quindi bisogna correggere il motore.
Il pulsante di sfida non funziona, o la sfida non viene accettata.Entrambi i bot devono avere il pallino verde. Controlla che il tuo bot sia selezionato in Il mio bot giocatore. Un bot già impegnato in un'altra partita non accetta nuove sfide. Una sfida non accettata scade dopo 120 secondi.
Il bot non fa nessuna mossa e poi perde per tempo, oppure la partita viene annullata.
Engine failed for match
Il motore non ha risposto in tempo o la sua risposta aveva un formato sbagliato. Controlla il comando del motore e la sua uscita JSON su una riga. Se un bot non fa la prima mossa entro 30 secondi dall'inizio, la partita viene annullata.
Non arrivano sfide nelle varianti.La finestra di sfida elenca solo i giochi supportati da entrambi i bot. Controlla che il gioco sia selezionato in Giochi supportati del tuo bot e che anche l'altro bot lo supporti. Un bot collegato tramite NBoard dovrebbe avere selezionato solo Standard.

Glossario

TermineSignificato
APIUn modo concordato con cui i programmi si scambiano richieste e risposte. Se ne occupa il connettore per te.
TokenUna password solo per il tuo bot. Il connettore la usa per dimostrare al server di quale bot si tratta.
TerminaleUna finestra in cui si scrivono comandi. Su Windows usa PowerShell.
Variabile d'ambienteUn'impostazione temporanea passata ai programmi avviati da una finestra del terminale. Scompare quando la finestra viene chiusa.
JSONUn formato per scrivere dati come testo. Esempio: {"move":19}
MotoreUn programma che esamina una posizione e sceglie una mossa.
AdattatoreUn piccolo programma che converte i dati tra il tuo motore e il connettore.
NBoardNBoard è un’applicazione grafica di Othello utilizzabile con NTest. Il protocollo NBoard è il suo formato pubblico di comunicazione con i motori e funziona con motori che lo supportano, come NTest ed Edax.
Mossa legaleUna casella in cui puoi giocare adesso. Il server le elenca in legal_moves.

Download

Scarica esempi Python

OpenAPI 3.1 JSON

README.md

Accedi per scaricare. .zip contiene guida, connettore e motore di esempio; .json è la specifica API e .md è la guida.

Specifica dettagliata

Il resto di questa pagina è un riferimento dettagliato per sviluppatori che lavorano direttamente con un motore o con l'API.

Opzioni del connettore

Opzioni di tools/bot_match_client.py. È richiesta esattamente una tra --engine e --nboard / --edax / --egaroucid. Il token non è mai un'opzione; viene letto solo dalla variabile d'ambiente FS_BOT_TOKEN. Codici di uscita: 0 arresto normale, 2 token rifiutato, 3 raggiunto --max-runtime.

ParametriDescrizione
--engine "COMMAND"Avvia questo comando una volta per partita e scambia una riga JSON per mossa senza chiuderne l'input. Un motore che risponde una riga e termina viene riavviato alla mossa successiva.
--nboard "COMMAND"Mantiene un motore NBoard per partita, due in totale. Solo standard (std).
--edax PATHEdax può collegarsi anche indicando solo il percorso dell’eseguibile con --edax. Non serve un adattatore; il connettore distribuisce il tempo per ogni mossa in base al tempo di partita rimasto.
--edax-eval PATHPercorso di eval.dat di Edax. Se omesso, cerca data/eval.dat nella cartella dell’eseguibile o in quella superiore.
--egaroucid PATHPreparate Egaroucid for Console. Scegliete l’eseguibile console e mantenete resources al suo posto. Su macOS e Linux, usate un eseguibile compilato seguendo le istruzioni ufficiali. Il connettore invia posizione e tempo rimasto con setboard, settime e go, senza adattatore.
--nboard-depth NInvia set depth N al motore NBoard. Da 0 a 60; 0 mantiene il valore predefinito del motore. (--edax: 0 → 60).
--max-think-seconds SSecondi massimi di attesa del motore per mossa. Predefinito 10, massimo 300. Mai più del tempo rimasto sull'orologio meno 0,75 secondi. È un limite di sicurezza del connettore; il server non ha un limite di tempo per mossa. Con 0 si elimina il limite per mossa e si usa il tempo di partita rimasto.
--engine-name NAMENome del motore riportato nelle informazioni del bot (massimo 120 caratteri). Non includere comandi o percorsi.
--cpu-name NAMENome della CPU da riportare (massimo 120 caratteri). Se omesso, viene rilevato automaticamente.
--once-match IDGestisce solo questo incontro ed esce quando finisce (concluso, annullato, scaduto, rifiutato). Funzionano anche gli ID negativi delle partite contro persone. Per i test, combinalo con --max-runtime.
--max-runtime SChiude i motori ed esce dopo questo numero di secondi (codice di uscita 3).
--no-streamNon usa il flusso di eventi; interroga solo periodicamente.
--api URL, --stream-url URLIndirizzi dell'API e del flusso. Di norma mantieni i valori predefiniti. È accettato solo HTTPS e, poiché il token viene inviato insieme, il flusso deve trovarsi sull'host dell'API.

Implementare l'API da soli

Ricevi sfide e posizioni con agent_poll o con il flusso di eventi autenticato. Invia agent_accept per le sfide in attesa in cui sei bot_b, poi calcola solo le posizioni in cui tocca a te in ciascuna partita di un incontro in corso. Invia al massimo una volta per match_id, partita (leg) e revision, e scarta il lavoro superato quando lo stato cambia. Le partite contro persone usano un match_id negativo e leg=0, e richiedono round_no e room_created_at esattamente come ricevuti. Nelle stanze normali il server gestisce i passi forzati; solo le coppie di bot inviano i passi.

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

Varianti

Un bot riceve solo i giochi selezionati in Giochi supportati. Il game_mode nelle richieste al motore e nello stato dell'incontro indica il gioco. Il server calcola dimensione della scacchiera, caselle bloccate e caselle legali, quindi il motore deve solo scegliere tra le legal_moves ricevute. Non generare le mosse del 10×10 o del Reversi classico con la generazione delle mosse dell'8×8 standard.

Giocogame_modeScacchieraPedine inizialiOrologio (per partita, per colore)Prime legal_moves del nero
Standardstd8×812 pedine casuali60,000 msDipende dalla posizione
Antianti8×812 pedine casuali60,000 msDipende dalla posizione
Grand (10x10)grand10×1012 pedine casuali60,000 msDipende dalla posizione
Octagonoctagon10×10 (88)12 pedine casuali60,000 msDipende dalla posizione
Reversi classicoreversi8×812 pedine casuali60,000 msDipende dalla posizione

Regole comuni: muove per primo il nero (1). Le due partite partono dalla stessa posizione con i colori invertiti e si giocano contemporaneamente. Nelle coppie di bot, quando non hai mosse legali, legal_moves arriva come array vuoto e il bot deve inviare da sé move=-1 (passo). Il server non passa al posto tuo, e il tuo orologio continua a scorrere. Passare quando esiste una mossa legale è illegale. Una partita finisce quando nessun colore può muovere. Le caselle vuote non vanno a nessuno; si sommano solo le pedine effettive di entrambe le partite, e a totali uguali è patta. Tutte le partite tra bot iniziano da una posizione casuale con 12 pedine. In ogni tipo di gioco, ogni bot ha 1 minuto per partita.

Anti: posare e capovolgere funzionano come nell'Othello standard. Vince il lato con meno pedine sul totale delle due partite.

10x10: regole standard su una scacchiera 10×10 (100 caselle). Le pedine iniziali occupano le quattro caselle centrali; le coppie di bot aggiungono 8 mosse casuali e partono da 12 pedine.

Octagon: stessa scacchiera e stessa disposizione iniziale del 10x10, ma tre caselle in ogni angolo, 12 in totale, sono inutilizzabili (88 caselle giocabili). Le caselle bloccate arrivano come numeri di casella in blocked_squares e come 0, lo stesso valore di vuota, in board. L'array non è in ordine numerico. Non si può giocare su una casella bloccata, e le linee di pedine si fermano lì.

Reversi classico: secondo le regole si parte da una scacchiera vuota. Finché una delle quattro caselle centrali (27, 28, 35, 36) è vuota, si posa solo lì e non si capovolge nulla. Quando tutte e quattro sono occupate, valgono le regole standard. Ogni colore posa al massimo 32 pedine, comprese le prime quattro mosse. placed conta le pedine posate finora da ciascun colore; le sue chiavi sono le stringhe "1" (nero) e "2" (bianco). Le coppie di bot partono dopo che il server ha posato 12 pedine a caso, comprese le quattro mosse centrali, quindi nella prima richiesta i due valori di placed sommano 12 (di solito {"1":6,"2":6}). Un colore che ha posato tutte le 32 pedine riceve legal_moves vuoto e invia -1 a ogni suo turno.

Esempi di richieste al motore per gioco

Esempi della prima richiesta per il nero che il connettore invia a un motore avviato con --engine, generati con il codice delle regole. Le partite reali di ogni tipo partono da una posizione casuale con 12 pedine, quindi board e legal_moves cambiano in ogni partita. Per tenere fissi questi esempi, le loro mosse iniziali sono state scelte vicino al centro della scacchiera. Anche match_id e i valori dell'orologio sono diversi nelle partite reali.

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

Campi della richiesta al motore

CampoTipoDescrizione
game_modestringUno tra std, anti, grand, octagon, reversi. Lo XOT nelle partite contro persone arriva come std.
board_sizeinteger 8 | 108, oppure 10 per grand e octagon.
boardinteger[board_size²]Array di lunghezza board_size². Si legge da a1 verso destra, poi la riga successiva. 0=vuota (comprese le caselle bloccate di Octagon), 1=nero, 2=bianco.
blocked_squaresinteger[]Caselle inutilizzabili di Octagon. Array vuoto negli altri giochi.
placed{"1": integer, "2": integer}Pedine posate finora per colore, comprese le mosse iniziali casuali; le chiavi sono le stringhe "1" e "2". Serve per il limite di 32 pedine del Reversi classico.
legal_movesinteger[]Caselle in cui puoi giocare adesso. Se è vuoto, rispondi move=-1.
colorinteger 1 | 2Colore che deve muovere. 1=nero, 2=bianco.
budget_msintegerTempo concesso per questa mossa in millisecondi: il minore tra --max-think-seconds e il tempo rimasto sull'orologio meno 0,75 secondi. Se viene superato, il connettore ferma il motore.
remaining_msintegerTempo rimasto al tuo colore in questa partita, in millisecondi, dopo aver sottratto il tempo già usato in questo turno.
match_idinteger ≠ 0ID dell'incontro. Negativo (-numero della stanza) nelle partite contro persone.
leginteger 0 | 1Numero di partita 0 o 1. Sempre 0 nelle partite contro persone.
revisionintegerNumero di stato di questa partita. Invialo invariato con agent_move.
historyarrayMosse giocate in questa partita. Decidi in base a board.
round_nointeger | nullNumero del turno di gioco di una partita contro una persona. null per le coppie di bot.
room_created_atstring | nullOra di creazione della stanza di una partita contro una persona. null per le coppie di bot.

Sfidare un altro bot in una variante

Aggiungi game_mode ad agent_challenge; il valore predefinito è std. Entrambi i bot devono averlo in supported_games. Leggi i giochi dell'avversario da bots[].supported_games nella risposta di agent_poll. Se a uno dei due bot manca il gioco, la risposta è HTTP 409 con l'errore qui sotto.

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

Modificare supported_games

Il modulo di registrazione parte con Standard selezionato, e i bot più vecchi senza un elenco salvato supportano std. Puoi modificare l'elenco solo in Gestisci nella pagina web dei bot, mai con il token del bot (API). La modifica viene rifiutata mentre un incontro è in corso o in attesa di accettazione. Un elenco vuoto non si può salvare.

NBoard e varianti

Una connessione --nboard supporta solo lo standard (std). Quando arriva una variante, il connettore non interpella il motore e rinuncia a quel turno (messaggio Engine failed for match). Non viene inviata nessuna mossa, quindi l'incontro viene annullato prima della prima mossa, oppure quella partita viene persa per tempo più avanti. Assegna a un bot NBoard lo standard come unico gioco supportato.

Persone contro bot registrati

Le persone possono sfidare il tuo bot in qualsiasi gioco registrato per lui. Se supporta lo standard, possono arrivare sia STD sia XOT. Lo XOT arriva con game_mode std e parte da una scacchiera in cui sono già state giocate 8 mosse (sono incluse in history). Chi sfida sceglie l'orologio, e la partita non è classificata. Un incontro contro una persona ha una sola partita (leg 0) e un match_id negativo. Il server gestisce i passi quando non ci sono mosse possibili, quindi non inviare -1. I bot registrati online con abbinamento automatico attivo partecipano anche alle partite rapide STD/XOT supportate. Queste sono classificate per i membri e non classificate per gli ospiti.

Mosse illegali e tempo

Una mossa illegale o una revision superata viene rifiutata con HTTP 409 (illegal_move, stale_revision, not_your_turn) e la scacchiera non cambia. Nel frattempo l'orologio continua a scorrere. Il connettore invia di nuovo la stessa mossa con la stessa revision, quindi, se il motore non viene corretto, quella partita finisce per essere persa per tempo. Il server non ha limiti per mossa e controlla solo il tempo rimasto di ciascun colore. I 10 secondi sono il valore predefinito di --max-think-seconds del connettore.

Autenticazione

Usa HTTPS, JSON POST e Authorization: Bearer. Ogni token vale per un solo bot. Non inserirlo in URL, codice pubblico o log.

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

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

Regole dell’incontro

Tutte le partite tra bot iniziano da una posizione casuale con 12 pedine. Due partite si svolgono contemporaneamente a colori invertiti. Vince il totale maggiore di pedine, oppure quello minore in Anti.

Tutte le partite tra bot iniziano da una posizione casuale con 12 pedine. In ogni tipo di gioco, ogni bot ha 1 minuto per partita.

Se un bot non ha giocato la prima mossa entro 30 secondi dall’inizio, l’incontro viene annullato e non è classificato. Se durante l’incontro la connessione di un bot si interrompe per più di 30 secondi mentre l’altro è connesso, vince il bot connesso (finish_reason: disconnect) e il risultato è classificato.

Il server calcola le mosse dei bot FlipSight in tutti i tipi di gioco. Sono accettate sia le sfide dal web sia quelle tramite API (agent_challenge), e il server avvia subito l'incontro, quindi non serve tenere aperta la stanza di gioco. Se il calcolo del server si ferma per più di 30 secondi, l'incontro viene annullato (finish_reason: technical) e non è classificato. Le varianti sono costose da calcolare, quindi si giocano al massimo 2 incontri di variante contro bot FlipSight alla volta. Se i posti sono pieni ricevi HTTP 409 con site bot variant matches are full; riprova tra poco.

Incontri tra bot automatici: il sito tiene sempre aperte due stanze per gli incontri tra bot. Finché un bot registrato con l'abbinamento automatico attivo è online e non sta giocando, riceve una sfida a un incontro tra bot standard (std) contro un bot con punteggio simile (prima un altro bot registrato, altrimenti un bot FlipSight), come bot_b. Il connettore la accetta automaticamente e l'incontro inizia. Se il bot non accetta entro 120 secondi o rifiuta, resta escluso dall'abbinamento automatico per 10 minuti. Gli incontri automatici sono classificati. Gli incontri automatici tra bot continuano anche durante le partite dell’Arena. L'abbinamento automatico è attivo per impostazione predefinita e si disattiva da Gestisci nella pagina dei bot. Un bot avviato con il motore di prova (environment.practice è true) resta escluso dagli incontri automatici, e i suoi incontri contro altri bot in quel periodo non sono classificati.

Una sfida umana diretta si gioca in una stanza normale, con gioco e tempo supportati. Il server rende sempre non classificate le sfide dirette. I bot registrati online con abbinamento automatico attivo partecipano anche alle partite rapide STD/XOT supportate. Queste sono classificate per i membri e non classificate per gli ospiti. Un bot registrato accetta solo tante partite quante ne indica l'impostazione delle partite simultanee (predefinito 2). Una partita contro una persona e un incontro tra bot (due scacchiere) contano 1 ciascuno.

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)

Svolgimento dell’incontro

Tutte le partite tra bot iniziano da una posizione casuale con 12 pedine. Due partite si svolgono contemporaneamente a colori invertiti. Vince il totale maggiore di pedine, oppure quello minore in Anti.

Il tuo numero di partecipante è 0 se sei bot_a e 1 se sei bot_b. Il partecipante 0 gioca col nero (1) in legs[0] e col bianco (2) in legs[1]; il partecipante 1 il contrario. Tocca a te quando leg.status è active e leg.turn corrisponde al tuo colore. Può toccare a te in entrambe le partite contemporaneamente.

Se non hai mosse legali, devi inviare tu stesso move -1 (passo); il server non passa al posto tuo. Nelle partite contro una persona il server gestisce i passi come nelle normali sale di gioco.

Nel tuo turno, tempo residuo = clocks_ms[tuo colore - 1] - (server_now_ms - turn_started_ms). clocks_ms segue l’ordine dei colori (nero, bianco); totals e winner seguono l’ordine dei partecipanti (winner 0 o 1, null in caso di patta).

Numeri delle caselle

Numero di casella = riga × 8 + colonna, quindi a1=0, h1=7, a2=8 e h8=63. Ad esempio, 19 è d3 e 37 è f5. L’array board usa lo stesso ordine, con 0=vuota, 1=nera, 2=bianca.

    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

Su 10×10 (grand, octagon), casella = riga × 10 + colonna. Le colonne vanno da a a j, e la decima riga si scrive 10 o 0 nelle coordinate. a1=0, j1=9, a2=10, j10=99 (uguale a j0). La stessa coordinata ha un numero diverso per ogni dimensione della scacchiera: d3 è 19 su 8×8 e 23 su 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 ## ##

Flusso di eventi

Connettiti con lo stesso token Bearer a GET https://othelloclub.com/flipsight_api/bot-stream per ricevere un evento JSON per riga (NDJSON): hello conferma la connessione, match indica che uno dei tuoi incontri è cambiato (gli eventi tra bot includono l’oggetto completo dell’incontro) e ping è un segnale keep-alive ogni 10 secondi. Quando ricevi match, chiama agent_poll una volta. Se la connessione cade, riconnettiti con un ritardo crescente da 1 a 30 secondi e nel frattempo continua a interrogare. Finché il flusso è connesso il tuo bot risulta online; ogni bot può aprire fino a 3 flussi.

Esempi di eventi

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}

Adattatore motore

Il connettore dei bot FlipSight (Python 3.9+, solo libreria standard) funziona su Windows, macOS e Linux e collega un motore in due modi. --engine avvia il motore una volta per partita, invia una riga JSON a ogni mossa e legge una riga {"move": casella}. Un motore che risponde una riga e termina funziona comunque, perché viene riavviato alla mossa successiva, ma un motore che legge l'input fino alla fine (sys.stdin.read() ecc.) non risponde mai: leggi una riga alla volta. legal_moves nella richiesta elenca le caselle legali; se è vuota, rispondi -1. --nboard tiene in esecuzione un motore NBoard, uno per partita; imposta la profondità con --nboard-depth.

Edax può collegarsi anche indicando solo il percorso dell’eseguibile con --edax. Non serve un adattatore; il connettore distribuisce il tempo per ogni mossa in base al tempo di partita rimasto.

Preparate Egaroucid for Console. Scegliete l’eseguibile console e mantenete resources al suo posto. Su macOS e Linux, usate un eseguibile compilato seguendo le istruzioni ufficiali. Il connettore invia posizione e tempo rimasto con setboard, settime e go, senza adattatore.

JSON in ingresso (connettore → adattatore)

Questi sono dati di esempio inviati dal connettore, non codice eseguibile. Non salvarli come file Python. Durante la partita, il connettore invia automaticamente i dati aggiornati a ogni 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 di risposta (adattatore → connettore)

{"move":2}

Sincronizzazione

Con il flusso connesso, chiama agent_poll all’arrivo di un evento e ogni 10 secondi. Se interroghi senza flusso, fallo ogni secondo durante un incontro e ogni 5 secondi in attesa, mai più di una volta al secondo. Il tempo di rete si scala dal tuo orologio. Se la risposta a una mossa va persa, rileggi il nuovo stato e reinvia la stessa mossa solo se la revision è ancora la stessa.

Azioni API

Gli esempi di risposta mostrano il formato. Il pulsante di copia copia solo la richiesta e non chiama l’API.

POST agent_poll

Leggi stato

ParametriTipoObbligatorioDescrizione
actionstring "agent_poll"ObbligatorioNome dell’azione da chiamare
environmentobjectFacoltativoCPU, sistema operativo e nome del motore del PC del connettore (fino a 120 caratteri ciascuno, facoltativo) e practice (true o false, facoltativo). practice a true significa che il bot si sta allenando: resta escluso dagli incontri automatici e gli incontri tra bot richiesti o accettati nel frattempo non sono classificati. Il connettore lo invia da solo quando viene avviato con il motore di prova. Il server ricorda l'ultimo valore, quindi a fine allenamento invialo senza practice o con false.

Esempio richiesta

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

Esempio risposta

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

Oggetto incontro (matches[])

CampoTipoDescrizione
idintegerID dell’incontro (negativo nelle partite contro una persona)
kindstringhuman nelle partite contro una persona
bot_aintegerID bot del partecipante 0 (0 per una persona)
bot_bintegerID bot del partecipante 1 (0 per una persona)
game_modestringGioco (std, anti, grand, octagon, reversi)
time_limit_secintegerSecondi per partita e per colore
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerScadenza per l’accettazione (millisecondi)
rankedbooleanSe l’incontro è classificato
state.statusstringactive, finished o aborted
state.game_modestringGioco (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Caselle per lato, 8 o 10
state.blocked_squaresinteger[]Caselle inutilizzabili (solo Octagon, non ordinate)
state.legsarrayPartite (2 tra bot, 1 contro una persona)
state.totals[integer, integer]Totale pedine delle due partite per partecipante
state.winnerinteger | nullPartecipante vincitore 0 o 1, null in caso di patta
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Oggetto partita (state.legs[])

CampoTipoDescrizione
boardinteger[64 | 100]64 / 100 caselle, 0=vuota 1=nera 2=bianca
turnintegerColore che deve muovere, 1 (nero) o 2 (bianco)
legal_movesinteger[]Caselle legali per il colore che deve muovere. Vuoto per le partite concluse
placed{"1": integer, "2": integer}Pedine posate per colore, chiavi "1" (nero) e "2" (bianco)
black_participantintegerPartecipante che gioca col nero in questa partita
clocks_ms[number, number]Valori base del tempo residuo in ordine di colore (nero, bianco)
turn_started_msintegerOra del server all’inizio del turno corrente
revisionintegerAumenta di 1 a ogni modifica di questa partita
statusstringactive, finished, timeout, stopped
historyarrayMosse giocate (color, participant, move, at_ms)
discs[integer, integer]Numero di pedine in ordine di colore (nero, bianco)
result_discs[integer, integer] (optional)Punteggio aggiudicato facoltativo [nero, bianco] per una partita persa per tempo; viene sommato al posto delle pedine. Punteggi per il lato che ha esaurito il tempo e per il suo avversario: std e reversi da 0 a 64, grand da 0 a 100, octagon da 0 a 88, anti da 64 a 0. L'altra partita continua, e i totali decidono il vincitore quando entrambe le partite sono finite. I risultati già conclusi più vecchi non vengono ricalcolati.

POST agent_challenge

Sfida

Indica target_id (numero del bot) o target_name (nome del bot), presenti nell'elenco bots della risposta di agent_poll. Anche i bot FlipSight integrati (is_builtin: true) si possono sfidare tramite API in tutti i tipi di gioco, e il server avvia subito l'incontro. Se ci sono già 2 incontri di variante contro bot integrati, ricevi HTTP 409 con site bot variant matches are full.

Sfida dalla pagina dei bot

ParametriTipoObbligatorioDescrizione
actionstring "agent_challenge"ObbligatorioNome dell’azione da chiamare
target_idinteger ≥ 1FacoltativoID del bot da sfidare
target_namestringFacoltativoNome del bot da sfidare (al posto di target_id)
game_modestring std | anti | octagon | grand | reversiFacoltativoGioco per una coppia di bot. Predefinito std. Entrambi i bot devono supportarlo. Standard / Anti / Octagon / Grand (10x10) / Reversi classico

Esempio richiesta

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

Esempio risposta

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

POST agent_accept

Accetta

ParametriTipoObbligatorioDescrizione
actionstring "agent_accept"ObbligatorioNome dell’azione da chiamare
match_idinteger ≥ 1ObbligatorioID dell’incontro (negativo nelle partite contro una persona)

Esempio richiesta

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

Esempio risposta

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

POST agent_reject

Rifiuta

ParametriTipoObbligatorioDescrizione
actionstring "agent_reject"ObbligatorioNome dell’azione da chiamare
match_idinteger ≥ 1ObbligatorioID dell’incontro (negativo nelle partite contro una persona)

Esempio richiesta

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

Esempio risposta

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

POST agent_move

Invia mossa

ParametriTipoObbligatorioDescrizione
actionstring "agent_move"ObbligatorioNome dell’azione da chiamare
match_idinteger ≠ 0ObbligatorioID dell’incontro (negativo nelle partite contro una persona)
round_nointeger ≥ 0FacoltativoSolo partite contro una persona: round_no dall’oggetto incontro
room_created_atstringFacoltativoSolo partite contro una persona: room_created_at dall’oggetto incontro
leginteger 0 | 1ObbligatorioPartita 0 o 1 (0 nelle partite contro una persona)
revisioninteger ≥ 0Obbligatoriorevision della partita per cui è stata calcolata la mossa; 409 stale_revision se la partita è cambiata
moveinteger -1..99 | stringObbligatorioCasella 0-(board_size²−1), -1 per passare / d3 / D3 / pass

Esempio richiesta

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

Esempio risposta

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

Errori

Gli errori hanno la forma {"ok": false, "error": "…"}. Con 401 fermati e verifica il token. Con 409 rileggi lo stato attuale e controlla turno, revision e mosse legali. 429 indica troppi flussi per un solo bot. Riprova gli errori di connessione e 503 con attese crescenti e non reinviare mai una mossa illegale.

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