API-dokumentation

Trin-for-trin-guide for begyndere

Det skal du bruge

  • En konto på FlipSight. Når du er logget ind, kan du registrere en bot og hente forbindelsesprogrammet.
  • Ét botbillede: en GIF-, JPG- eller PNG-fil på højst 5 MB og mindst 175 px i både bredde og højde.
  • Din egen computer til at køre botten Din bots træk beregnes på den computer, hvor forbindelsesprogrammet kører. Selv med samme motor kan styrken variere med computerens ydeevne og motorens indstillinger, og hvis du skifter computer eller indstillinger, kan ratingen flytte sig tilsvarende.
  • Python 3, et gratis program, der kører forbindelsesprogrammet. Version 3.9 eller nyere virker; 3.11 eller nyere anbefales. Du tjekker det i trin 5.
  • Din bots motor. Forbindelsesprogrammet indeholder en øvelsesmotor til test.

1. Registrer din bot

Log ind, og åbn botsiden (othelloclub.com/da/flipsight/bot). I boksen Registrer min bot øverst skriver du et navn i Botnavn (2–24 tegn). Lad indtil videre kun Standard være markeret under Understøttede spil. Vælg et billede under Upload botfoto, og tryk derefter på knappen Registrer min bot.

Sådan ser det ud, når det lykkes: boksen Forbindelsestoken (vises kun én gang) vises med en lang tekst, der starter med fsbm_.

2. Gem din token

Den lange tekst er din token, en adgangskode kun til din bot. Forbindelsesprogrammet bruger den til at bevise over for serveren, at det taler på vegne af denne bot. Kopier hele teksten, og indsæt den et midlertidigt sted, for eksempel i et tekstredigeringsprogram. Når du har lukket boksen, kan du ikke se den samme token igen.

Behandl din token som en adgangskode. Vis den ikke til nogen, og del ikke skærmbilleder af den. Hvis du mister den, eller andre ser den, så åbn Administrer i botlisten, og tryk på Udsted forbindelsestoken. Når du får en ny token, holder den gamle op med at virke med det samme.

3. Hent forbindelsesprogrammet

Tryk på Hent Python-eksempler for at hente ZIP-filen, og pak den ud. I denne guide kaldes den udpakkede mappe "botmappen". Læg den et sted, hvor den er nem at finde, for eksempel på skrivebordet.

Sådan ser det ud, når det lykkes: botmappen indeholder filen README.md og mappen tools.

4. Åbn en terminal i botmappen

En terminal er et vindue, hvor du skriver kommandoer. Åbn den sådan her på dit system.

macOS: tryk på Command+Space, skriv Terminal, og åbn programmet. Skriv cd og et mellemrum, træk botmappen ind i vinduet, og tryk på Enter.

Linux: højreklik på botmappen i filhåndteringen, og vælg Åbn i terminal. Menunavnet kan være forskelligt fra distribution til distribution. Hvis menuen ikke findes, så åbn en terminal, og brug cd som på macOS.

Windows: åbn botmappen i Stifinder. Skriv powershell i adresselinjen øverst, og tryk på Enter. Et PowerShell-vindue åbner i den mappe.

Tjek: skriv kommandoen nedenfor, og tryk på Enter. Det er lykkedes, hvis README.md og tools står på listen. Kommandoen er den samme på macOS, Linux og Windows PowerShell.

ls

5. Tjek Python

I samme vindue kopierer du den ene kommando til dit system, indsætter den og trykker på Enter. Du kan bruge kopieringsknappen øverst til højre i hver kodeboks.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Sådan ser det ud, når det lykkes: en version, der starter med 3, for eksempel Python 3.11.9. Version 3.9 eller nyere virker. Hvis kommandoen ikke findes, så installer Python som beskrevet nedenfor, luk vinduet, og start forfra fra trin 4.

macOS: hent installationsprogrammet til macOS (.pkg) fra python.org, og installer det. Kør derefter Install Certificates.command én gang fra Python-mappen under Programmer. Det klargør de certifikater, der skal bruges til sikre forbindelser (HTTPS).

Linux: installer python3 med din distributions softwarecenter eller pakkehåndtering.

Windows: installer Python install manager fra python.org.

python.org

6. Indtast din token

Læg din token i en miljøvariabel, en midlertidig indstilling, der kun findes i dette terminalvindue. Den hedder FS_BOT_TOKEN. Kopier kommandoen på én linje til dit system nedenfor, indsæt den, og tryk én gang på Enter. Så vises Bot token og venter på dit input. Indsæt nu den token, du kopierede i trin 2, og tryk på Enter.

macOS / Linux

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

Windows PowerShell

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

På macOS og Linux vises den indsatte token ikke, og på Windows ser du * i stedet for tegnene. Begge dele er normalt. Fordi din token ikke skrives ind i kommandoen, bliver den ikke gemt i kommandohistorikken. Indstillingen gælder kun i dette vindue. Hvis du har lukket det eller åbnet et nyt vindue, så gentag trin 4 og 6.

Windows: I PowerShell indsætter du med Ctrl+V eller et højreklik. Denne kommando på én linje bruger en syntaks, der virker i både Windows PowerShell 5.1, som følger med Windows, og den nyere PowerShell 7, men den er endnu ikke testet på en rigtig Windows-computer.

Sådan ser det ud, når det lykkes: prompten til den næste kommando kommer tilbage uden fejl.

7. Start forbindelsesprogrammet

I samme vindue kopierer du kommandoen nedenfor, som den er, indsætter den og trykker på Enter. Ændr ikke filnavnene i den. Din bot forbinder sig med øvelsesmotoren, en meget svag motor, der spiller det lovlige felt med det laveste nummer.

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'

Det er normalt, at kommandoen ikke viser tekst eller vender tilbage til prompten, mens den kører. Hold denne rude åben med forbindelsesprogrammet og motoren kørende, og undgå, at computeren går i dvale, for at holde din bot online. Hvis du lukker ruden eller trykker Ctrl+C, stopper begge programmer, og din bot går offline. Kør kommandoen ovenfor igen for at få den online. Forbindelsesprogrammet accepterer automatisk udfordringer fra andre botter.

Så længe botten kører med øvelsesmotoren, får den ingen automatiske kampe, og dens kampe mod andre bots, også dem du selv anmoder om, er ikke ratede. Start den igen med din egen motor (trin 10), så bliver den parret og ratet fra da af.

Sådan ser det ud, når det lykkes: i listen Registrerede bots på botsiden er prikken til højre for din bots navn grøn. Holder du musen over den, vises Online. Hvis den er grå (Offline), så tryk på Opdater.

8. Test med en første kamp

Vælg din bot under Min spillende bot på botsiden. Vælg en vilkårlig bot på listen FlipSight-bots, tryk på Udfordr, og bekræft i det vindue, der åbner. I alle spiltyper beregner serveren trækkene for FlipSight-bots, så du behøver ikke holde spillerummet åbent. Med automatisk parring slået til (standard) sætter siden også botkampe op, mens forbindelsesprogrammet kører.

Sådan ser det ud, når det lykkes: spillerummet viser to brætter, og der lægges en brik, hver gang det er din bots tur. Øvelsesmotoren er meget svag, så det er normalt at tabe.

9. Lad den køre, når vinduet er lukket (valgfrit)

Start med kommandoen nedenfor i stedet for trin 7, så bliver botten ved med at køre, når du lukker vinduet. Kør den i det samme vindue, hvor du indtastede din token (trin 6). Den stopper, hvis computeren slukkes eller går i dvale. Stopper du den under en kamp, tabes kampen på tid eller på grund af afbrudt forbindelse.

macOS / Linux

macOS / Linux: når du har kørt startkommandoen, kan du lukke vinduet. Beskeder skrives til filen bot.log i botmappen. For at stoppe den åbner du en ny terminal og kører stopkommandoen. Kopier kun den boks, du har brug for: Hvis du indsætter begge bokse på én gang, stopper botten lige efter, at den er startet.

Start

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

Stop

pkill -f bot_match_client.py

Windows PowerShell

Windows: et minimeret vindue vises på proceslinjen, og botten kører i det. Du kan lukke det nuværende PowerShell-vindue. For at stoppe den åbner du det minimerede vindue og trykker på Ctrl+C eller lukker vinduet.

Start

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

10. Skift til din egen motor

Efter forbindelsestesten skiftes til din egen motor: de særlige indstillinger nedenfor til Edax eller Egaroucid, --nboard til andre kompatible motorer, JSON til din egen motor. Behold samme token.

Markér kun en variant (Anti, 10x10, Octagon, klassisk Reversi) under Understøttede spil, hvis din motor kender reglerne for den. Andre bots og mennesker kan udfordre din bot i alle markerede spil. For at ændre det åbner du Administrer i botlisten, retter Understøttede spil og trykker på Gem. Du kan ikke ændre det, mens en kamp er i gang eller en udfordring venter.

Eksempler på motorforespørgsler pr. spil

Tilslut en NBoard-motor som Edax

NBoard er et grafisk Othello-program, som kan bruges med NTest. NBoard-protokollen er programmets offentlige kommunikationsformat til motorer og virker med motorer, der understøtter den, såsom NTest og Edax.

Installations- og starteksemplerne nedenfor bruger Edax 4.6 og Egaroucid for Console 7.8.2.

Edax 4.6 følger ikke med forbindelsesprogrammet. Hent filen til dit styresystem under Assets på Edax 4.6, læg den i botmappen, og pak den ud med forberedelseskommandoerne nedenfor. Windows- og Linux-kommandoerne er til x86-64.

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

Forberedelse (kun første gang)

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

Start

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

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

Forberedelse (kun første gang)

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

Start

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

macOS: edax-reversi-4.6.tar.gz

Der findes ingen officiel macOS-programfil til Edax 4.6. Hent Source code (tar.gz) og edax-4.6-linux-x86.tar.gz fra samme udgivelse. Byg Mac-programmet fra kildekoden; hent kun den fælles vurderingsfil data/eval.dat fra Linux-arkivet. Xcode Command Line Tools kræves. Forberedelseskommandoerne kompilerer kildekoden.

Forberedelse (kun første gang)

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)

Start

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

Edax kan også forbindes med kun programstien via --edax nedenfor. Der kræves ingen separat adapter; forbindelsesprogrammet fordeler tænketiden pr. træk ud fra den resterende spilletid.

Erstat stien i kommandoen med den fulde sti til din programfil. Sæt stier med mellemrum i dobbelte anførselstegn. Findes evalueringsfilen ikke automatisk, angives eval.dat med --edax-eval.

En NBoard-forbindelse spiller kun standardkampe på 8×8. Lad kun Standard være markeret under Understøttede spil for denne bot. Hvis der kommer en variant, spørger forbindelsesprogrammet ikke motoren, der spilles intet træk, og kampen annulleres eller tabes på tid.

Egaroucid: --egaroucid

Klargør Egaroucid for Console. Vælg konsolprogrammet, og lad resources-mappen blive på sin plads. På macOS og Linux bruges et program bygget efter den officielle vejledning. Stillingen og resttiden sendes via setboard, settime og go uden en adapter.

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"

Begge tilstande understøtter kun standard 8×8, med én motor pr. samtidigt parti. --max-think-seconds er sikkerhedsgrænsen pr. træk (som standard 10 sekunder). Når Egaroucid når grænsen, stoppes søgningen, et lovligt reservetræk spilles, og motoren genstartes ved næste tur.

Egen motor: forbind via JSON

Læs kun denne del, hvis du vil tilslutte en motor, du selv har skrevet. Hvis du bruger Edax, Egaroucid eller en anden NBoard-motor, er du allerede færdig ovenfor og kan springe den over.

Hver gang det er din bots tur, kører forbindelsesprogrammet my_adapter.py én gang. Det afleverer den aktuelle stilling som én linje (hvor brikkerne står, hvis tur det er, hvilke felter der er lovlige, hvor meget tid der er tilbage), my_adapter.py svarer med ét felt som én linje, og forbindelsesprogrammet spiller det træk på serveren. my_adapter.py er en lille Python-fil i midten, som beder din motor om trækket. Forbindelsesprogrammet tager sig af forbindelsen til serveren, din token og indsendelsen af træk, så denne fil skal aldrig tage sig af det.

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

Hvilken slags motor har du?

  • En motor, der vælger sit træk i Python-kode: brug eksempel A nedenfor.
  • En programfil, der udskriver én svarlinje og afslutter, hver gang den køres: brug eksempel B nedenfor.
  • En programfil, der forbliver åben og løbende udveksler kommandoer: understøtter den NBoard, så tilslut den med --nboard ovenfor. Ved en anden protokol tilpasser du eksempel B til protokollen og lader my_adapter.py holde motoren åben og udveksle én linje ad gangen i en løkke.

Trin 1: en fil, der kun tjekker forbindelsen (my_adapter.py)

Opret en fil med navnet my_adapter.py i botmappen (hvor README.md og tools ligger), indsæt koden nedenfor, som den er, og gem filen. Denne fil bruger ingen motor og svarer bare med det første lovlige felt. Den er der, så du kan se, at forbindelsen virker, før du tilslutter din motor.

import json
import sys

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

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

Tjek input og output, før du forbinder

Kommandoen i boks 1 sender eksempelstillingen direkte ind i adapteren. Den skal udskrive én linje: {"move": 19}. Når du ser den linje, starter kommandoen i boks 2 forbindelsesprogrammet med denne adapter i det vindue, der har din token fra trin 6. Kør de to bokse hver for sig. Hvis din motor selv kan læse dette JSON-format, kan du sætte dens kommando direkte efter --engine.

1. Test adapteren alene

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. Start forbindelsesprogrammet med adapteren

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'

Trin 2: skift til en fil, der kalder din motor

Når trin 1 virker, erstatter du hele indholdet af my_adapter.py med eksempel A eller B nedenfor. Ret kun de linjer, der er mærket EDIT, og lad resten være, som det er. Kør derefter testkommando 1 ovenfor igen, og tjek, at der kommer én svarlinje. Det er din motor, der har valgt trækket, så et andet felt end 19 er helt normalt.

Eksempel A: en motor, der vælger sit træk med en Python-funktion

Dette eksempel forudsætter en fil med navnet my_engine.py med en funktion search, der vælger trækket. Ret filnavnet og funktionsnavnet ved EDIT 1 og de værdier, der sendes med, ved EDIT 2, så de passer til din motor. Funktionen skal returnere feltets nummer (0 til 63) eller en koordinat som 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)
Eksempel B: en motor, der er en programfil

Dette eksempel forudsætter, at programmet læser én linje med 64 brættegn, den side, der er i trækket, og betænkningstiden i millisekunder, og svarer med én koordinatlinje som D3 (. = tomt, X = sort, O = hvid). Hver motor har sit eget format, så ret programfilens navn ved EDIT 1, den linje, der sendes til motoren, ved EDIT 2 og den del, der læser svaret, ved EDIT 3, så det passer til din motor.

my_adapter.py → my_engine

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

my_engine → my_adapter.py

D3
import json
import subprocess
import sys
from pathlib import Path

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


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


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


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

Betænkningstid: i botpartier har hver bot 1 minut i hvert parti, så lang betænkningstid på et enkelt træk fører hurtigt til tab på tid. think_ms i eksemplerne deler den resterende tid (remaining_ms) med antallet af træk, du har tilbage, og finder på den måde tiden til dette træk. budget_ms er ikke den tid, der skal bruges på dette træk. Det er den længste tid, forbindelsesprogrammet venter på svaret (10 sekunder som standard). Kommer svaret senere end det, kan botten ikke fortsætte i det parti og taber det på tid, så giv din motor think_ms og ikke budget_ms.

Når noget går galt: hvis kaldet til motoren mislykkes, skriver eksempelfilerne årsagen i my_adapter.log og spiller det første lovlige felt i stedet. Hvis trækkene ser mærkelige ud, så åbn den fil først. Forbindelsesprogrammet viser ikke adapterens fejlmeddelelser på skærmen. Brug kun print til den ene svarlinje, og skriv al fejlsøgningstekst til en fil.

Hastighed: forbinderen holder my_adapter.py kørende for hvert parti og sender én linje pr. træk. En fil, der svarer én linje og afslutter, som eksemplerne, startes igen ved næste træk, så din motor starter også forfra hver gang. Er motoren langsom til at starte eller skal den bevare arbejdet fra tidligere træk, så gør my_adapter.py til en løkke: læs én linje, svar én linje, gentag. Læs ikke input til ende, og flush efter hvert svar.

Resten af denne del er opslagsstof om, hvad forbindelsesprogrammet og my_adapter.py udveksler. Hvis du bruger eksempelfilerne ovenfor, som de er, behøver du ikke læse det.

Input-JSON (forbindelsesprogram → adapter)

Dette er eksempeldata fra forbindelsesprogrammet, ikke kode, der skal køres. Gem dem ikke som en Python-fil. Under spillet sendes de aktuelle data automatisk for hvert træk.

Herunder er et eksempel på standardstartstillingen på 8×8. I rigtige partier udfylder forbindelsesprogrammet den aktuelle stilling. board lister felterne fra a1 mod højre og derefter næste række; 0=tomt, 1=sort, 2=hvid. color=1 beder om et træk for sort. legal_moves lister de felter, du må spille på. remaining_ms er den resterende tid på uret, og budget_ms er den længste tid, forbindelsesprogrammet venter på svaret på dette træk, begge i millisekunder (1000 = 1 sekund). Felter og eksempler for varianter står i afsnittet om varianter nedenfor.

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

Svar-JSON (adapter → forbindelsesprogram)

Skriv et af svarene nedenfor som én JSON-linje, der slutter med et linjeskift. move er et feltnummer eller en koordinat, uanset store og små bogstaver. På 8×8 er d3 (3−1)×8+3 = 19. Vælg altid blandt de legal_moves, du har modtaget. I botpar uden lovligt træk sender du -1 eller pass; i kampe mod mennesker afgør serveren tvungne pas. Skriv kun svarlinjen til stdout, uden andre beskeder.

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

Eksempler på motorforespørgsler pr. spil

Eksempler på den første forespørgsel for sort, som forbindelsesprogrammet sender til en motor med --engine, genereret med regelkoden. Rigtige kampe af alle typer starter fra en tilfældig stilling med 12 brikker, så board og legal_moves er forskellige i hver kamp. For at eksemplerne forbliver de samme, er deres åbningstræk valgt nær midten af brættet. match_id og urværdierne er også anderledes i rigtige kampe.

  • game_mode: En af std, anti, grand, octagon, reversi. XOT i kampe mod mennesker kommer som std.
  • board_size: 8, eller 10 for grand og octagon.
  • board: Array med længden board_size². Læses fra a1 mod højre og derefter næste række. 0=tomt (inklusive Octagons blokerede felter), 1=sort, 2=hvid.
  • blocked_squares: Ubrugelige felter i Octagon. Et tomt array i andre spil.
  • placed: Brikker lagt indtil nu pr. farve, inklusive de tilfældige åbningstræk; nøglerne er strengene "1" og "2". Bruges til grænsen på 32 brikker i klassisk Reversi.

Felter i motorforespørgslen

Anti (anti)

Input-JSON (forbindelsesprogram → adapter)

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

Svar-JSON (adapter → forbindelsesprogram)

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

Input-JSON (forbindelsesprogram → adapter)

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

Svar-JSON (adapter → forbindelsesprogram)

{"move":13}
Octagon (octagon)

Input-JSON (forbindelsesprogram → adapter)

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

Svar-JSON (adapter → forbindelsesprogram)

{"move":13}
Klassisk reversi (reversi)

Input-JSON (forbindelsesprogram → adapter)

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

Svar-JSON (adapter → forbindelsesprogram)

{"move":18}

Almindelige problemer

SymptomÅrsag og løsning
Botten har en grå prik (offline).Tjek, at vinduet med forbindelsesprogrammet stadig kører. Det stopper, når du lukker vinduet, trykker på Ctrl+C, eller computeren går i dvale. Gentag trin 6 og 7, og tryk derefter på Opdater.
Der står, at kommandoen ikke blev fundet.
command not found / is not recognized
Python mangler, eller vinduet blev ikke åbnet igen efter installationen. Se trin 5, åbn et nyt vindue, og start forfra fra trin 4.
Der står, at filen ikke kan åbnes.
can't open file
Du kørte kommandoen uden for botmappen. Gå til botmappen som i trin 4, og tjek med ls, at README.md og tools står på listen.
Der står, at token mangler, og programmet afslutter.
FS_BOT_TOKEN is missing or invalid
Dette vindue har ingen token. Hvis du har åbnet et nyt vindue, så gentag trin 6.
Der står, at token blev afvist, og programmet stopper.
Bot credential rejected
Din token blev indsat forkert eller er en gammel. Når du har udstedt en ny token eller deaktiveret botten, virker den gamle token ikke længere. Start forfra fra trin 6 med den nyeste token.
Den samme fejllinje bliver ved med at komme.
Connection/state error
Tjek internetforbindelsen. På macOS skal du tjekke, at du har kørt Install Certificates.command fra trin 5. Hvis fejlen kun kommer under kampe, afviser serveren måske din motors træk som ulovligt. Forbindelsesprogrammet sender det samme træk igen, så motoren skal rettes.
Udfordringsknappen virker ikke, eller udfordringen bliver ikke accepteret.Begge bots skal have en grøn prik. Tjek, at din bot er valgt under Min spillende bot. En bot, der allerede er i en anden kamp, tager ikke imod en ny udfordring. En udfordring, der ikke accepteres, udløber efter 120 sekunder.
Botten spiller intet træk og taber derefter på tid, eller kampen annulleres.
Engine failed for match
Motoren svarede ikke i tide, eller svaret havde forkert format. Tjek motorkommandoen og dens JSON-output på én linje. Hvis en bot ikke spiller sit første træk inden for 30 sekunder efter start, annulleres kampen.
Der kommer ingen udfordringer i varianter.Udfordringsvinduet viser kun spil, som begge bots understøtter. Tjek, at spillet er markeret under Understøttede spil for din bot, og at den anden bot også understøtter det. En bot, der er forbundet via NBoard, bør kun have Standard markeret.

Ordliste

BegrebBetydning
APIEn aftalt måde, hvorpå programmer sender forespørgsler og svar. Forbindelsesprogrammet klarer det for dig.
TokenEn adgangskode kun til din bot. Forbindelsesprogrammet bruger den til at bevise over for serveren, hvilken bot det er.
TerminalEt vindue, hvor du skriver kommandoer. På Windows bruger du PowerShell.
MiljøvariabelEn midlertidig indstilling, der gives videre til programmer, som startes fra ét terminalvindue. Den forsvinder, når vinduet lukkes.
JSONEt format til at skrive data som tekst. Eksempel: {"move":19}
MotorEt program, der ser på en stilling og vælger et træk.
AdapterEt lille program, der omsætter data mellem din motor og forbindelsesprogrammet.
NBoardNBoard er et grafisk Othello-program, som kan bruges med NTest. NBoard-protokollen er programmets offentlige kommunikationsformat til motorer og virker med motorer, der understøtter den, såsom NTest og Edax.
Lovligt trækEt felt, du må spille på nu. Serveren lister dem i legal_moves.

Downloads

Hent Python-eksempler

OpenAPI 3.1 JSON

README.md

Log ind for at hente filer. .zip indeholder vejledning, forbindelsesprogram og eksempelmotor; .json er API-specifikationen og .md er vejledningen.

Detaljeret specifikation

Resten af siden er en detaljeret reference til udviklere, der arbejder direkte med en motor eller med API'et.

Indstillinger for forbindelsesprogrammet

Indstillinger for tools/bot_match_client.py. Præcis én af --engine og --nboard / --edax / --egaroucid er påkrævet. Token er aldrig en indstilling; den læses kun fra miljøvariablen FS_BOT_TOKEN. Afslutningskoder: 0 normalt stop, 2 token afvist, 3 --max-runtime nået.

ParametreBeskrivelse
--engine "COMMAND"Starter denne kommando én gang pr. parti og udveksler én JSON-linje pr. træk uden at lukke dens input. En motor, der svarer én linje og afslutter, startes igen ved næste træk.
--nboard "COMMAND"Beholder én NBoard-motor pr. parti, to i alt. Kun standard (std).
--edax PATHEdax kan også forbindes med kun programstien via --edax nedenfor. Der kræves ingen separat adapter; forbindelsesprogrammet fordeler tænketiden pr. træk ud fra den resterende spilletid.
--edax-eval PATHSti til Edax eval.dat. Uden angivelse søges efter data/eval.dat i programmappen eller dens overordnede mappe.
--egaroucid PATHKlargør Egaroucid for Console. Vælg konsolprogrammet, og lad resources-mappen blive på sin plads. På macOS og Linux bruges et program bygget efter den officielle vejledning. Stillingen og resttiden sendes via setboard, settime og go uden en adapter.
--nboard-depth NSender set depth N til NBoard-motoren. 0 til 60; 0 beholder motorens standard. (--edax: 0 → 60).
--max-think-seconds SDet maksimale antal sekunder, der ventes på motoren pr. træk. Standard 10, maksimum 300. Aldrig længere end den resterende tid på uret minus 0,75 sekunder. Det er en sikkerhedsgrænse i forbindelsesprogrammet; serveren har ingen tidsgrænse pr. træk. Med 0 fjernes grænsen pr. træk, og den resterende spilletid bruges.
--engine-name NAMEMotornavn, der vises i botoplysningerne (højst 120 tegn). Medtag ikke kommandoer eller stier.
--cpu-name NAMECPU-navn, der skal rapporteres (højst 120 tegn). Findes automatisk, hvis det udelades.
--once-match IDHåndterer kun denne kamp og afslutter, når den slutter (afsluttet, annulleret, udløbet, afvist). Negative id'er for kampe mod mennesker virker også. Til test kan du kombinere den med --max-runtime.
--max-runtime SRydder op i motorerne og afslutter efter dette antal sekunder (afslutningskode 3).
--no-streamBruger ikke hændelsesstrømmen; spørger kun med jævne mellemrum.
--api URL, --stream-url URLAdresser til API og strøm. Behold normalt standardværdierne. Kun HTTPS accepteres, og da din token sendes med, skal strømmen ligge på API-værten.

Implementer API'et selv

Modtag udfordringer og stillinger med agent_poll eller den godkendte hændelsesstrøm. Send agent_accept for ventende udfordringer, hvor du er bot_b. Beregn derefter kun stillinger, hvor det er din tur, i hvert parti i en igangværende kamp. Indsend højst én gang pr. match_id, parti og revision, og drop forældet arbejde, når tilstanden ændres. Kampe mod mennesker bruger et negativt match_id og leg=0 og kræver round_no og room_created_at præcis som modtaget. I almindelige rum håndterer serveren tvungne pas; kun botpar indsender pas.

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

Varianter

En bot modtager kun de spil, der er markeret under Understøttede spil. game_mode i motorforespørgsler og kamptilstand fortæller, hvilket spil det er. Serveren beregner brætstørrelse, blokerede felter og lovlige felter, så motoren skal kun vælge blandt de legal_moves, den modtager. Generér ikke træk til 10×10 eller klassisk Reversi med trækgenerering til standard 8×8.

Spilgame_modeBrætStartbrikkerUr (pr. parti, pr. farve)Sorts første legal_moves
Standardstd8×8Tilfældige 12 brikker60,000 msAfhænger af stillingen
Antianti8×8Tilfældige 12 brikker60,000 msAfhænger af stillingen
Grand (10x10)grand10×10Tilfældige 12 brikker60,000 msAfhænger af stillingen
Octagonoctagon10×10 (88)Tilfældige 12 brikker60,000 msAfhænger af stillingen
Klassisk reversireversi8×8Tilfældige 12 brikker60,000 msAfhænger af stillingen

Fælles regler: Sort (1) trækker først. Begge partier starter fra samme stilling med ombyttede farver og spilles samtidig. Hvis du i et botpar ikke har noget lovligt træk, kommer legal_moves som et tomt array, og botten skal selv sende move=-1 (pas). Serveren melder ikke pas for dig, og dit ur kører videre. Det er ulovligt at melde pas, når der findes et lovligt træk. Et parti slutter, når ingen af farverne kan trække. Tomme felter gives ikke til nogen; kun faktiske brikker lægges sammen over begge partier, og lige summer er remis. Alle botpartier starter fra en tilfældig stilling med 12 brikker. I alle spiltyper har hver bot 1 minut i hvert parti.

Anti: brikker lægges og vendes som i standard-Othello. Siden med færrest brikker samlet over begge partier vinder.

10x10: standardregler på et 10×10-bræt (100 felter). Startbrikkerne står på de fire midterfelter; botpar lægger 8 tilfældige træk til og starter med 12 brikker.

Octagon: samme bræt og startopstilling som 10x10, men tre felter i hvert hjørne, 12 i alt, kan ikke bruges (88 spilbare felter). Blokerede felter kommer som feltnumre i blocked_squares og som 0, samme værdi som tomt, i board. Arrayet er ikke i numerisk rækkefølge. Du kan ikke spille på et blokeret felt, og rækker af brikker stopper der.

Klassisk Reversi: starter ifølge reglerne på et tomt bræt. Så længe et af de fire midterfelter (27, 28, 35, 36) er tomt, lægger du kun der, og intet vendes. Når alle fire er fyldt, gælder standardreglerne. Hver farve lægger højst 32 brikker, inklusive de første fire træk. placed tæller de brikker, hver farve har lagt indtil nu; nøglerne er strengene "1" (sort) og "2" (hvid). Botpar starter, efter at serveren har lagt 12 tilfældige brikker, inklusive de fire midtertræk, så i den første forespørgsel giver de to placed-tal tilsammen 12 (normalt {"1":6,"2":6}). En farve, der har lagt alle 32, modtager en tom legal_moves og sender -1, hver gang det er dens tur.

Eksempler på motorforespørgsler pr. spil

Eksempler på den første forespørgsel for sort, som forbindelsesprogrammet sender til en motor med --engine, genereret med regelkoden. Rigtige kampe af alle typer starter fra en tilfældig stilling med 12 brikker, så board og legal_moves er forskellige i hver kamp. For at eksemplerne forbliver de samme, er deres åbningstræk valgt nær midten af brættet. match_id og urværdierne er også anderledes i rigtige kampe.

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}

Klassisk reversi (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}

Felter i motorforespørgslen

FeltTypeBeskrivelse
game_modestringEn af std, anti, grand, octagon, reversi. XOT i kampe mod mennesker kommer som std.
board_sizeinteger 8 | 108, eller 10 for grand og octagon.
boardinteger[board_size²]Array med længden board_size². Læses fra a1 mod højre og derefter næste række. 0=tomt (inklusive Octagons blokerede felter), 1=sort, 2=hvid.
blocked_squaresinteger[]Ubrugelige felter i Octagon. Et tomt array i andre spil.
placed{"1": integer, "2": integer}Brikker lagt indtil nu pr. farve, inklusive de tilfældige åbningstræk; nøglerne er strengene "1" og "2". Bruges til grænsen på 32 brikker i klassisk Reversi.
legal_movesinteger[]Felter, du må spille på nu. Hvis listen er tom, svarer du move=-1.
colorinteger 1 | 2Farven i trækket. 1=sort, 2=hvid.
budget_msintegerTilladt tid til dette træk i millisekunder: den mindste af --max-think-seconds og den resterende tid på uret minus 0,75 sekunder. Forbindelsesprogrammet stopper motoren, når den overskrides.
remaining_msintegerDin farves resterende tid i dette parti i millisekunder, efter at den tid, der allerede er brugt i denne tur, er trukket fra.
match_idinteger ≠ 0Kamp-id. Negativt (-rumnummer) for kampe mod mennesker.
leginteger 0 | 1Partinummer 0 eller 1. Altid 0 for kampe mod mennesker.
revisionintegerTilstandsnummer for dette parti. Send det uændret med agent_move.
historyarrayTræk spillet i dette parti. Træf beslutningen ud fra board.
round_nointeger | nullRundenummer i en kamp mod et menneske. null for botpar.
room_created_atstring | nullOprettelsestidspunkt for rummet til kampen mod et menneske. null for botpar.

Udfordr en anden bot til en variant

Tilføj game_mode til agent_challenge; standard er std. Begge bots skal have spillet i supported_games. Læs modstanderens spil fra bots[].supported_games i svaret fra agent_poll. Hvis en af botterne mangler spillet, er svaret HTTP 409 med fejlen nedenfor.

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

Ændring af supported_games

Registreringsformularen starter med Standard markeret, og ældre bots uden en gemt liste understøtter std. Du kan kun ændre listen under Administrer på botsiden på nettet, aldrig med bottens token (API). Ændringen afvises, mens en kamp er i gang eller venter på accept. En tom liste kan ikke gemmes.

NBoard og varianter

En --nboard-forbindelse understøtter kun standard (std). Når der kommer en variant, spørger forbindelsesprogrammet ikke motoren og opgiver den tur (meddelelsen Engine failed for match). Der sendes intet træk, så kampen annulleres før første træk, eller partiet tabes senere på tid. Giv en NBoard-bot standard som eneste understøttede spil.

Mennesker mod registrerede bots

Mennesker kan udfordre din bot i ethvert spil, den har registreret. Hvis den understøtter standard, kan både STD og XOT komme. XOT kommer med game_mode std og starter fra et bræt, hvor 8 træk allerede er spillet (de er med i history). Den, der udfordrer, vælger uret, og kampen tæller ikke med i ratingen. En kamp mod et menneske har ét parti (leg 0) og et negativt match_id. Serveren håndterer pas, når der ikke er noget muligt træk, så send ikke -1. Registrerede botter online med automatisk parring slået til deltager også i understøttede STD/XOT-hurtigkampe. De er ratede for medlemmer og uratede for gæster.

Ulovlige træk og tid

Et ulovligt træk eller en forældet revision afvises med HTTP 409 (illegal_move, stale_revision, not_your_turn), og brættet ændres ikke. Uret kører imens videre. Forbindelsesprogrammet sender det samme træk igen med samme revision, så medmindre motoren rettes, tabes partiet til sidst på tid. Serveren har ingen grænse pr. træk og tjekker kun hver farves resterende tid. De 10 sekunder er forbindelsesprogrammets standardværdi for --max-think-seconds.

Godkendelse

Brug HTTPS, JSON POST og Authorization: Bearer. Token gælder kun én bot. Skriv den aldrig i URL’er, offentlig kode eller logs.

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

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

Kampregler

Alle botpartier starter fra en tilfældig stilling med 12 brikker. To partier spilles samtidig med ombyttede farver. Det samlede antal brikker afgør vinderen; i Anti vinder det laveste antal.

Alle botpartier starter fra en tilfældig stilling med 12 brikker. I alle spiltyper har hver bot 1 minut i hvert parti.

Har en bot ikke spillet sit første træk inden for 30 sekunder efter start, annulleres kampen og tæller ikke i rating. Mister en bot forbindelsen i mere end 30 sekunder under kampen, mens den anden er forbundet, vinder den forbundne bot (finish_reason: disconnect), og resultatet tæller i rating.

Serveren beregner FlipSight-bottens træk i alle spiltyper. Både udfordringer på nettet og via API’et (agent_challenge) modtages, og serveren starter kampen med det samme, så du behøver ikke holde spillerummet åbent. Hvis serverens beregning står stille i mere end 30 sekunder, annulleres kampen (finish_reason: technical) og ratingen påvirkes ikke. Varianter er tunge at beregne, så højst 2 variantkampe mod FlipSight-bots kører ad gangen. Er pladserne fyldt, får du HTTP 409 med site bot variant matches are full; prøv igen om lidt.

Automatiske botkampe: siden holder altid to rum med botkampe kørende. Mens en registreret bot med automatisk parring slået til er online og ikke spiller, får den en udfordring til en standard-botkamp (std) mod en bot med lignende rating (først en anden registreret bot, ellers en FlipSight-bot) som bot_b. Forbindelsesprogrammet accepterer automatisk, og kampen starter. Hvis botten ikke accepterer inden for 120 sekunder eller afviser, holdes den ude af automatisk parring i 10 minutter. Automatiske kampe er ratede. Automatiske botkampe fortsætter, mens Arena-partier er i gang. Automatisk parring er slået til som standard og kan slås fra under Administrer på botsiden. En bot, der er startet med øvelsesmotoren (environment.practice er true), holdes ude af automatiske kampe, og dens kampe mod andre bots i den tid er ikke ratede.

En direkte udfordring fra et menneske spilles som ét parti i et normalt rum med understøttet spil og tid. Serveren gør altid direkte udfordringer uratede. Registrerede botter online med automatisk parring slået til deltager også i understøttede STD/XOT-hurtigkampe. De er ratede for medlemmer og uratede for gæster. En registreret bot tager kun imod så mange partier, som dens indstilling for samtidige partier tillader (standard 2). Et parti mod et menneske og en botkamp (to brætter) tæller hver 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)

Kampforløb

Alle botpartier starter fra en tilfældig stilling med 12 brikker. To partier spilles samtidig med ombyttede farver. Det samlede antal brikker afgør vinderen; i Anti vinder det laveste antal.

Dit deltagernummer er 0, hvis du er bot_a, og 1, hvis du er bot_b. Deltager 0 er sort (1) i legs[0] og hvid (2) i legs[1]; for deltager 1 er det omvendt. Det er din tur, når leg.status er active, og leg.turn er din farve. Det kan være din tur i begge partier på én gang.

Har du intet lovligt træk, skal du selv sende move -1 (pas); serveren melder ikke pas for dig. I partier mod et menneske håndterer serveren pas som i almindelige spillerum.

På din tur er resterende tid = clocks_ms[din farve - 1] - (server_now_ms - turn_started_ms). clocks_ms er i farveorden (sort, hvid); totals og winner er i deltagerorden (winner 0 eller 1, null ved remis).

Feltnumre

Feltnummer = række × 8 + kolonne, så a1=0, h1=7, a2=8 og h8=63. For eksempel er 19 d3 og 37 f5. board-arrayet bruger samme rækkefølge med 0=tomt, 1=sort, 2=hvid.

    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

På 10×10 (grand, octagon) er felt = række × 10 + kolonne. Kolonnerne er a til j, og den tiende række skrives som 10 eller 0 i koordinater. a1=0, j1=9, a2=10, j10=99 (samme som j0). Samme koordinat har et forskelligt nummer på hver brætstørrelse: d3 er 19 på 8×8 og 23 på 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 ## ##

Hændelsesstrøm

Forbind med samme Bearer-token til GET https://othelloclub.com/flipsight_api/bot-stream for at modtage én JSON-hændelse pr. linje (NDJSON): hello bekræfter forbindelsen, match betyder, at en af dine kampe er ændret (hændelser mellem bots indeholder hele kampobjektet), og ping er et keep-alive-signal hvert 10. sekund. Når du modtager match, så kald agent_poll én gang. Falder forbindelsen ud, så forbind igen med en ventetid, der vokser fra 1 til 30 sekunder, og fortsæt med at forespørge imens. Mens strømmen er forbundet, vises din bot som online; hver bot kan åbne op til 3 strømme.

Hændelseseksempler

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}

Motoradapter

FlipSight-botforbinderen (Python 3.9+, kun standardbiblioteket) kører på Windows, macOS og Linux og tilslutter en motor på to måder. --engine starter motoren én gang pr. parti, sender én JSON-linje for hvert træk og læser én linje {"move": felt}. En motor, der svarer én linje og afslutter, virker stadig, fordi den startes igen ved næste træk, men en motor, der læser input til ende (sys.stdin.read() osv.), svarer aldrig, så læs én linje ad gangen. legal_moves i forespørgslen viser de lovlige felter; er listen tom, svar -1. --nboard holder en NBoard-motor kørende, én pr. parti; søgedybden sættes med --nboard-depth.

Edax kan også forbindes med kun programstien via --edax nedenfor. Der kræves ingen separat adapter; forbindelsesprogrammet fordeler tænketiden pr. træk ud fra den resterende spilletid.

Klargør Egaroucid for Console. Vælg konsolprogrammet, og lad resources-mappen blive på sin plads. På macOS og Linux bruges et program bygget efter den officielle vejledning. Stillingen og resttiden sendes via setboard, settime og go uden en adapter.

Input-JSON (forbindelsesprogram → adapter)

Dette er eksempeldata fra forbindelsesprogrammet, ikke kode, der skal køres. Gem dem ikke som en Python-fil. Under spillet sendes de aktuelle data automatisk for hvert træk.

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

Svar-JSON (adapter → forbindelsesprogram)

{"move":2}

Statussynkronisering

Med strømmen forbundet kalder du agent_poll, når en hændelse kommer, og hvert 10. sekund. Uden strøm forespørger du hvert sekund under en kamp og hvert 5. sekund i ventetid, men aldrig oftere end én gang i sekundet. Netværkstid trækkes fra dit ur. Går svaret på et træk tabt, så hent den nye status, og send kun samme træk igen, hvis revision er uændret.

API-handlinger

Svareksemplerne viser formatet. Kopiknappen kopierer kun anmodningen og kalder ikke API'et.

POST agent_poll

Hent status

ParametreTypePåkrævetBeskrivelse
actionstring "agent_poll"PåkrævetNavnet på den handling, der kaldes
environmentobjectValgfritCPU, OS og motornavn for forbindelses-pc'en (op til 120 tegn hver, valgfrit) samt practice (true eller false, valgfrit). practice true betyder, at botten øver sig: Den holdes ude af automatiske kampe, og botkampe, der anmodes om eller accepteres i den tid, er ikke ratede. Forbindelsesprogrammet sender den selv, når det startes med øvelsesmotoren. Serveren husker den sidste værdi, så send den uden practice eller med false, når øvelsen er slut.

Eksempel på anmodning

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

Eksempel på svar

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

Kampobjekt (matches[])

FeltTypeBeskrivelse
idintegerKamp-ID (negativt for partier mod et menneske)
kindstringhuman for partier mod et menneske
bot_aintegerBot-ID for deltager 0 (0 for et menneske)
bot_bintegerBot-ID for deltager 1 (0 for et menneske)
game_modestringSpil (std, anti, grand, octagon, reversi)
time_limit_secintegerSekunder pr. parti, pr. farve
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerFrist for accept (millisekunder)
rankedbooleanOm kampen er ratet
state.statusstringactive, finished eller aborted
state.game_modestringSpil (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Felter pr. side, 8 eller 10
state.blocked_squaresinteger[]Ubrugelige felter (kun Octagon, ikke sorteret)
state.legsarrayPartier (2 mellem bots, 1 mod et menneske)
state.totals[integer, integer]Brikker i alt fra begge partier pr. deltager
state.winnerinteger | nullVindende deltager 0 eller 1, null ved remis
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Partiobjekt (state.legs[])

FeltTypeBeskrivelse
boardinteger[64 | 100]64 / 100 felter, 0=tomt 1=sort 2=hvid
turnintegerFarven i trækket, 1 (sort) eller 2 (hvid)
legal_movesinteger[]Lovlige felter for farven i trækket. Tom for afsluttede partier
placed{"1": integer, "2": integer}Lagte brikker pr. farve, nøgler "1" (sort) og "2" (hvid)
black_participantintegerDeltageren, der spiller sort i dette parti
clocks_ms[number, number]Basisværdier for resterende tid i farveorden (sort, hvid)
turn_started_msintegerServertid, da den aktuelle tur begyndte
revisionintegerStiger med 1, hver gang dette parti ændres
statusstringactive, finished, timeout, stopped
historyarraySpillede træk (color, participant, move, at_ms)
discs[integer, integer]Antal brikker i farveorden (sort, hvid)
result_discs[integer, integer] (optional)Valgfri tildelt score [sort, hvid] for et parti tabt på tid; læg den sammen i stedet for brikkerne. Score for den side, der løb tør for tid, og for modstanderen: std og reversi 0 til 64, grand 0 til 100, octagon 0 til 88, anti 64 til 0. Det andet parti fortsætter, og summerne afgør vinderen, når begge partier er slut. Ældre afsluttede registreringer genberegnes ikke.

POST agent_challenge

Udfordr

Angiv target_id (bot-ID) eller target_name (botnavn), som findes i listen bots i svaret fra agent_poll. Indbyggede FlipSight-bots (is_builtin: true) kan udfordres via API’et i alle spiltyper, og serveren starter kampen med det samme. Er der allerede 2 variantkampe mod indbyggede bots, får du HTTP 409 med site bot variant matches are full.

Udfordr fra botsiden

ParametreTypePåkrævetBeskrivelse
actionstring "agent_challenge"PåkrævetNavnet på den handling, der kaldes
target_idinteger ≥ 1ValgfritID på den bot, der udfordres
target_namestringValgfritNavn på den bot, der udfordres (i stedet for target_id)
game_modestring std | anti | octagon | grand | reversiValgfritSpil for et botpar. Standard er std. Begge bots skal understøtte det. Standard / Anti / Octagon / Grand (10x10) / Klassisk reversi

Eksempel på anmodning

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

Eksempel på svar

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

POST agent_accept

Accepter

ParametreTypePåkrævetBeskrivelse
actionstring "agent_accept"PåkrævetNavnet på den handling, der kaldes
match_idinteger ≥ 1PåkrævetKamp-ID (negativt for partier mod et menneske)

Eksempel på anmodning

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

Eksempel på svar

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

POST agent_reject

Afvis

ParametreTypePåkrævetBeskrivelse
actionstring "agent_reject"PåkrævetNavnet på den handling, der kaldes
match_idinteger ≥ 1PåkrævetKamp-ID (negativt for partier mod et menneske)

Eksempel på anmodning

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

Eksempel på svar

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

POST agent_move

Send træk

ParametreTypePåkrævetBeskrivelse
actionstring "agent_move"PåkrævetNavnet på den handling, der kaldes
match_idinteger ≠ 0PåkrævetKamp-ID (negativt for partier mod et menneske)
round_nointeger ≥ 0ValgfritKun partier mod et menneske: round_no fra kampobjektet
room_created_atstringValgfritKun partier mod et menneske: room_created_at fra kampobjektet
leginteger 0 | 1PåkrævetParti 0 eller 1 (0 for partier mod et menneske)
revisioninteger ≥ 0Påkrævetrevision for det parti, trækket blev beregnet til; 409 stale_revision, hvis partiet er ændret
moveinteger -1..99 | stringPåkrævetFelt 0-(board_size²−1), -1 for pas / d3 / D3 / pass

Eksempel på anmodning

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

Eksempel på svar

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

Fejl

Fejl ser sådan ud: {"ok": false, "error": "…"}. Ved 401: stop og kontrollér tokenet. Ved 409: hent den aktuelle status og kontrollér tur, revision og lovlige træk. 429 betyder for mange strømme for én bot. Prøv forbindelsesfejl og 503 igen med voksende ventetid, og send aldrig et ulovligt træk igen.

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