Dokumentacja API

Przewodnik krok po kroku dla początkujących

Czego potrzebujesz

  • Konta w FlipSight. Po zalogowaniu możesz zarejestrować bota i pobrać łącznik.
  • Jednego zdjęcia bota: pliku GIF, JPG lub PNG o rozmiarze do 5 MB, mającego co najmniej 175 px szerokości i wysokości.
  • Własny komputer do uruchomienia bota Ruchy twojego bota są obliczane na komputerze, na którym działa program łączący. Nawet z tym samym silnikiem siła może się różnić zależnie od wydajności komputera i ustawień silnika, a gdy zmienisz komputer lub ustawienia, ranking może się odpowiednio zmienić.
  • Pythona 3, darmowego programu, który uruchamia łącznik. Działa wersja 3.9 lub nowsza; zalecana jest 3.11 lub nowsza. Sprawdzisz to w kroku 5.
  • Własny silnik bota. Program do łączenia zawiera silnik treningowy do testów.

1. Zarejestruj bota

Zaloguj się i otwórz stronę botów (othelloclub.com/pl/flipsight/bot). W polu Zarejestruj mojego bota u góry wpisz nazwę w Nazwa bota (2–24 znaki). W Obsługiwane gry pozostaw na razie zaznaczone tylko Standard. Wybierz obraz w Prześlij zdjęcie bota, a następnie naciśnij przycisk Zarejestruj mojego bota.

Tak wygląda sukces: pojawia się pole Token połączenia (widoczny tylko raz) z długim tekstem zaczynającym się od fsbm_.

2. Zachowaj token

Ten długi tekst to token, czyli hasło tylko dla twojego bota. Łącznik używa go, aby udowodnić serwerowi, że działa w imieniu tego bota. Skopiuj go w całości i tymczasowo wklej w jakieś miejsce, na przykład do edytora tekstu. Po zamknięciu pola nie zobaczysz już tego samego tokenu.

Traktuj token jak hasło. Nikomu go nie pokazuj i nie publikuj jego zrzutu ekranu. Jeśli go zgubisz lub ktoś go zobaczy, otwórz Zarządzaj na liście botów i naciśnij Utwórz token połączenia. Gdy dostaniesz nowy token, stary natychmiast przestaje działać.

3. Pobierz łącznik

Naciśnij Pobierz przykłady Python, aby pobrać plik ZIP, i rozpakuj go. W tym przewodniku rozpakowany folder nazywamy „folderem bota”. Umieść go w łatwo dostępnym miejscu, na przykład na pulpicie.

Tak wygląda sukces: w folderze bota jest plik README.md i folder tools.

4. Otwórz terminal w folderze bota

Terminal to okno, w którym wpisuje się polecenia. Otwórz go w swoim systemie w następujący sposób.

macOS: naciśnij Command+Space, wpisz Terminal i otwórz go. Wpisz cd i jedną spację, przeciągnij folder bota do okna, a potem naciśnij Enter.

Linux: w menedżerze plików kliknij prawym przyciskiem folder bota i wybierz Otwórz w terminalu. Nazwa polecenia w menu może się różnić w zależności od dystrybucji. Jeśli takiego polecenia nie ma, otwórz terminal i użyj cd tak jak w macOS.

Windows: otwórz folder bota w Eksploratorze plików. Wpisz powershell w pasku adresu u góry i naciśnij Enter. W tym folderze otworzy się okno PowerShell.

Sprawdzenie: wpisz poniższe polecenie i naciśnij Enter. Sukces oznacza, że na liście pojawią się README.md i tools. To polecenie jest takie samo w macOS, Linux i Windows PowerShell.

ls

5. Sprawdź Pythona

W tym samym oknie skopiuj jedno polecenie dla swojego systemu, wklej je i naciśnij Enter. Możesz użyć przycisku kopiowania w prawym górnym rogu każdego pola z kodem.

macOS / Linux

python3 --version

Windows PowerShell

py --version

Tak wygląda sukces: wersja zaczynająca się od 3, na przykład Python 3.11.9. Działa wersja 3.9 lub nowsza. Jeśli polecenie nie zostało znalezione, zainstaluj Pythona zgodnie z poniższymi wskazówkami, zamknij okno i zacznij ponownie od kroku 4.

macOS: pobierz instalator dla macOS (.pkg) z python.org i zainstaluj go. Następnie uruchom raz Install Certificates.command z folderu Python w Aplikacjach. Przygotowuje to certyfikaty potrzebne do bezpiecznych połączeń (HTTPS).

Linux: zainstaluj python3 za pomocą menedżera oprogramowania lub menedżera pakietów swojej dystrybucji.

Windows: zainstaluj Python install manager z python.org.

python.org

6. Wprowadź token

Zapisz token w zmiennej środowiskowej. To tymczasowe ustawienie, które istnieje tylko w tym oknie terminala. Nazywa się FS_BOT_TOKEN. Skopiuj poniżej jednowierszowe polecenie dla swojego systemu, wklej je i naciśnij Enter jeden raz. Wtedy pojawi się Bot token i będzie czekać na dane. Teraz wklej token skopiowany w kroku 2 i naciśnij 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

W systemach macOS i Linux wklejony token nie jest wyświetlany, a w Windows zamiast jego znaków widać *. Oba przypadki są normalne. Ponieważ token nie jest zapisany w poleceniu, nie zostaje w historii poleceń. Ustawienie działa tylko w tym oknie. Jeśli je zamknąłeś lub otworzyłeś nowe okno, powtórz kroki 4 i 6.

Windows: w PowerShell wklejaj za pomocą Ctrl+V lub prawego przycisku myszy. To jednowierszowe polecenie ma składnię, która działa zarówno w Windows PowerShell 5.1 dostarczanym z Windows, jak i w nowszym PowerShell 7, ale nie zostało jeszcze sprawdzone na prawdziwym komputerze z Windows.

Tak wygląda sukces: znak zachęty do kolejnego polecenia wraca bez błędu.

7. Uruchom łącznik

W tym samym oknie skopiuj poniższe polecenie bez zmian, wklej je i naciśnij Enter. Nie zmieniaj w nim nazw plików. Twój bot połączy się z silnikiem treningowym. To bardzo słaby silnik, który gra na legalne pole o najniższym numerze.

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'

To normalne, że podczas działania polecenie nie wyświetla tekstu ani nie wraca do wiersza poleceń. Aby bot pozostał online, zostaw to okno otwarte z uruchomionym programem łączącym i silnikiem oraz zapobiegaj uśpieniu komputera. Zamknięcie okna lub naciśnięcie Ctrl+C kończy oba programy i odłącza bota. Uruchom ponownie powyższe polecenie, aby go połączyć. Program łączący automatycznie przyjmuje wyzwania innych botów.

Dopóki bot działa z silnikiem treningowym, nie dostaje meczów automatycznych, a jego mecze z innymi botami, także te, o które sam poprosisz, nie liczą się do rankingu. Uruchom go ponownie z własnym silnikiem (krok 10), a od tej chwili będzie kojarzony i liczony do rankingu.

Tak wygląda sukces: na liście Zarejestrowane boty na stronie botów kropka po prawej stronie nazwy twojego bota jest zielona. Po najechaniu na nią kursorem pojawia się Online. Jeśli jest szara (Offline), naciśnij Odśwież.

8. Sprawdź w pierwszym meczu

Na stronie botów wybierz swojego bota w Mój grający bot. Wybierz dowolnego bota z listy Boty FlipSight, naciśnij Wyzwij i potwierdź w oknie, które się otworzy. We wszystkich rodzajach gier ruchy botów FlipSight oblicza serwer, więc nie musisz trzymać otwartego pokoju gry. Przy włączonym automatycznym kojarzeniu (domyślnie) serwis umawia też mecze botów, dopóki łącznik działa.

Tak wygląda sukces: pokój gry pokazuje dwie plansze, a przy każdym ruchu twojego bota pojawia się pionek. Silnik treningowy jest bardzo słaby, więc przegrana jest normalna.

9. Działanie po zamknięciu okna (opcjonalnie)

Zamiast kroku 7 uruchom poniższe polecenie, a bot będzie działał także po zamknięciu okna. Uruchom je w tym samym oknie, w którym wprowadziłeś token (krok 6). Bot zatrzyma się, jeśli komputer się wyłączy lub przejdzie w stan uśpienia. Zatrzymanie go w trakcie meczu oznacza przegranie tego meczu na czas lub przez rozłączenie.

macOS / Linux

macOS / Linux: po wykonaniu polecenia uruchomienia możesz zamknąć okno. Komunikaty są zapisywane w pliku bot.log w folderze bota. Aby zatrzymać bota, otwórz nowy terminal i wykonaj polecenie zatrzymania. Kopiuj tylko to pole, którego potrzebujesz: jeśli wkleisz oba pola naraz, bot zatrzyma się zaraz po uruchomieniu.

Uruchomienie

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

Zatrzymanie

pkill -f bot_match_client.py

Windows PowerShell

Windows: na pasku zadań pojawia się zminimalizowane okno, w którym działa bot. Możesz zamknąć bieżące okno PowerShell. Aby zatrzymać bota, otwórz zminimalizowane okno i naciśnij Ctrl+C albo zamknij to okno.

Uruchomienie

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

10. Przejdź na własny silnik

Po sprawdzeniu połączenia zmień silnik: opcje poniżej dla Edax lub Egaroucid, --nboard dla innych zgodnych silników, JSON dla własnego silnika. Zachowaj ten sam token.

Zaznacz wariant gry (Anti, 10x10, Octagon, klasyczne Reversi) w Obsługiwane gry tylko wtedy, gdy twój silnik zna jego zasady. Inne boty i ludzie mogą wyzwać twojego bota w każdej zaznaczonej grze. Aby to zmienić, otwórz Zarządzaj na liście botów, zmień Obsługiwane gry i naciśnij Zapisz. Nie można tego zmienić, gdy trwa mecz lub czeka wyzwanie.

Przykłady żądań do silnika według gry

Podłącz silnik zgodny z NBoard, np. Edax

NBoard to graficzna aplikacja Othello, której można używać z NTest. Protokół NBoard to jej publiczny format komunikacji z silnikami i działa z silnikami, które go obsługują, np. NTest i Edax.

Poniższe przykłady instalacji i uruchamiania korzystają z Edax 4.6 i Egaroucid for Console 7.8.2.

Edax 4.6 nie jest dołączony do programu łączącego. Pobierz plik dla swojego systemu z Assets na Edax 4.6, umieść go w folderze bota i rozpakuj poniższymi poleceniami przygotowania. Polecenia Windows i Linux dotyczą x86-64.

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

Przygotowanie (tylko za pierwszym razem)

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

Uruchomienie

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

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

Przygotowanie (tylko za pierwszym razem)

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

Uruchomienie

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

macOS: edax-reversi-4.6.tar.gz

Nie ma oficjalnego pliku wykonywalnego Edax 4.6 dla macOS. Pobierz Source code (tar.gz) i edax-4.6-linux-x86.tar.gz z tego samego wydania. Zbuduj program Mac ze źródeł; z archiwum Linux wyodrębnij tylko wspólny plik oceny data/eval.dat. Wymagane są narzędzia Xcode Command Line Tools. Polecenia przygotowania kompilują źródła.

Przygotowanie (tylko za pierwszym razem)

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)

Uruchomienie

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

Edax można też podłączyć, podając tylko ścieżkę programu przez --edax poniżej. Oddzielny adapter nie jest potrzebny; łącznik rozdziela czas na ruchy według pozostałego czasu partii.

Zastąp ścieżkę w poleceniu pełną ścieżką do swojego programu. Ścieżki ze spacjami ujmij w podwójny cudzysłów. Jeśli plik oceny nie zostanie znaleziony, wskaż eval.dat przez --edax-eval.

Połączenie NBoard gra tylko standardowe partie 8×8. Dla tego bota pozostaw w Obsługiwane gry zaznaczone tylko Standard. Jeśli przyjdzie wariant gry, łącznik nie zapyta silnika, żaden ruch nie zostanie wykonany, a partia zostanie przerwana lub przegrana na czas.

Egaroucid: --egaroucid

Przygotuj Egaroucid for Console. Wybierz program konsolowy i pozostaw folder resources na miejscu. W macOS i Linuksie użyj programu zbudowanego według oficjalnej instrukcji. Pozycja i pozostały czas są przesyłane przez setboard, settime i go, bez adaptera.

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"

Oba tryby obsługują tylko standardowe 8×8, z jednym silnikiem na każdą równoczesną partię. --max-think-seconds to limit bezpieczeństwa ruchu (domyślnie 10 sekund). Po osiągnięciu go przez Egaroucid wyszukiwanie zostaje zatrzymane, wykonywany jest legalny ruch zastępczy, a silnik uruchamia się ponownie przy następnym ruchu.

Własny silnik: połączenie przez JSON

Przeczytaj tę część tylko wtedy, gdy chcesz podłączyć silnik napisany samodzielnie. Jeśli używasz Edax, Egaroucid albo innego silnika NBoard, masz już wszystko gotowe powyżej i możesz ją pominąć.

Za każdym razem, gdy przychodzi kolej twojego bota, łącznik uruchamia my_adapter.py jeden raz. Przekazuje bieżącą pozycję w jednym wierszu (gdzie leżą pionki, czyja jest kolej, które pola są legalne, ile czasu zostało), my_adapter.py odpowiada jednym polem w jednym wierszu, a łącznik wykonuje ten ruch na serwerze. my_adapter.py to mały plik w języku Python, który stoi pośrodku i pyta twój silnik o ruch. Połączeniem z serwerem, tokenem i wysyłaniem ruchów zajmuje się łącznik, więc ten plik nigdy nie ma z nimi do czynienia.

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

Jakiego rodzaju jest twój silnik?

  • Silnik, który wybiera ruch w kodzie Python: użyj przykładu A poniżej.
  • Plik programu, który przy każdym uruchomieniu wypisuje jeden wiersz odpowiedzi i kończy działanie: użyj przykładu B poniżej.
  • Plik wykonywalny, który pozostaje otwarty i stale wymienia polecenia: jeśli obsługuje NBoard, podłącz go powyżej przez --nboard. Przy innym protokole dostosuj przykład B do tego protokołu i niech my_adapter.py trzyma silnik otwarty i wymienia po jednej linii w pętli.

Krok 1: plik, który tylko sprawdza połączenie (my_adapter.py)

W folderze bota (tam, gdzie są README.md i tools) utwórz plik o nazwie my_adapter.py, wklej poniższy kod bez zmian i zapisz. Ten plik nie używa żadnego silnika i po prostu odpowiada pierwszym legalnym polem. Służy do sprawdzenia, czy połączenie działa, zanim podłączysz swój silnik.

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)

Sprawdź wejście i wyjście przed połączeniem

Polecenie w polu 1 przekazuje przykładową pozycję bezpośrednio do adaptera. Powinno wypisać jeden wiersz: {"move": 19}. Gdy zobaczysz ten wiersz, polecenie w polu 2 uruchomi łącznik z tym adapterem w oknie, w którym jest token z kroku 6. Uruchamiaj oba pola osobno. Jeśli twój silnik sam czyta ten format JSON, możesz wpisać jego polecenie bezpośrednio po --engine.

1. Przetestuj sam adapter

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. Uruchom łącznik z adapterem

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'

Krok 2: przejście na plik, który wywołuje twój silnik

Gdy krok 1 działa, zastąp całą zawartość pliku my_adapter.py przykładem A lub B poniżej. Zmień tylko wiersze oznaczone EDIT, a resztę zostaw bez zmian. Potem ponownie uruchom polecenie testowe 1 powyżej i sprawdź, czy pojawia się jeden wiersz odpowiedzi. Ruch wybrał twój silnik, więc pole inne niż 19 jest czymś normalnym.

Przykład A: silnik, który wybiera ruch funkcją Python

Ten przykład zakłada plik o nazwie my_engine.py z funkcją search, która wybiera ruch. Przy EDIT 1 zmień nazwę pliku i nazwę funkcji, a przy EDIT 2 przekazywane wartości, tak aby pasowały do twojego silnika. Funkcja powinna zwracać numer pola (od 0 do 63) albo współrzędną, na przykład 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)
Przykład B: silnik w postaci pliku programu

Ten przykład zakłada, że program odczytuje jeden wiersz zawierający 64 znaki planszy, stronę, która ma ruch, i czas na myślenie w milisekundach, a odpowiada jednym wierszem ze współrzędną, na przykład D3 (. = puste, X = czarne, O = białe). Każdy silnik ma własny format, więc zmień nazwę pliku programu przy EDIT 1, wiersz wysyłany do silnika przy EDIT 2 i część odczytującą odpowiedź przy EDIT 3, tak aby pasowały do twojego silnika.

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)

Czas na myślenie: w partiach botów każdy bot ma 1 minutę na partię, więc długie myślenie nad jednym ruchem szybko kończy się przegraną na czas. think_ms w przykładach dzieli pozostały czas (remaining_ms) przez liczbę ruchów, które ci zostały, i w ten sposób ustala czas na ten ruch. budget_ms to nie jest czas, który należy zużyć na ten ruch. To najdłuższy czas, przez jaki łącznik czeka na odpowiedź (domyślnie 10 sekund). Jeśli odpowiedź przyjdzie później, bot nie może kontynuować tej partii i przegrywa ją na czas, dlatego przekazuj silnikowi think_ms, a nie budget_ms.

Gdy coś się zepsuje: jeśli wywołanie silnika się nie powiedzie, pliki przykładowe zapisują przyczynę w my_adapter.log i zamiast tego grają pierwsze legalne pole. Jeśli ruchy wyglądają dziwnie, najpierw otwórz ten plik. Łącznik nie pokazuje na ekranie komunikatów o błędach adaptera. Używaj print tylko do jednego wiersza odpowiedzi, a wszelki tekst diagnostyczny zapisuj do pliku.

Szybkość: program łączący trzyma my_adapter.py uruchomiony dla każdej partii i wysyła jedną linię na ruch. Plik, który odpowiada jedną linią i kończy działanie, jak przykłady, jest uruchamiany ponownie przy następnym ruchu, więc twój silnik też startuje za każdym razem od nowa. Jeśli silnik uruchamia się powoli lub ma korzystać z pracy z poprzednich ruchów, zrób z my_adapter.py pętlę: czytaj linię, odpowiedz linią, powtarzaj. Nie czytaj wejścia do końca i rób flush po każdej odpowiedzi.

Reszta tej części to materiał pomocniczy o tym, co wymieniają między sobą łącznik i my_adapter.py. Jeśli używasz powyższych plików przykładowych bez zmian, nie musisz jej czytać.

Wejściowy JSON (łącznik → adapter)

To przykładowe dane wysyłane przez łącznik, a nie kod do uruchomienia. Nie zapisuj ich jako pliku Pythona. Podczas partii aktualne dane są wysyłane automatycznie przy każdym ruchu.

Poniżej jest przykład standardowej pozycji początkowej 8×8. W prawdziwych partiach łącznik wstawia bieżącą pozycję. board wymienia pola od a1 w prawo, a potem następny wiersz; 0=puste, 1=czarne, 2=białe. color=1 prosi o ruch czarnych. legal_moves wymienia pola, na które możesz zagrać. remaining_ms to czas pozostały na zegarze partii, a budget_ms to najdłuższy czas, przez jaki łącznik czeka na odpowiedź w tym ruchu, oba w milisekundach (1000 = 1 sekunda). Pola i przykłady dla wariantów są niżej, w części o wariantach gry.

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

Odpowiedź JSON (adapter → łącznik)

Zapisz jedną z poniższych odpowiedzi jako jeden wiersz JSON zakończony znakiem nowego wiersza. move to numer pola lub współrzędna, wielkość liter nie ma znaczenia. Na planszy 8×8 d3 to (3−1)×8+3 = 19. Zawsze wybieraj z otrzymanych legal_moves. W parach botów, gdy nie ma legalnego ruchu, wyślij -1 lub pass; w grach z człowiekiem wymuszone pasy obsługuje serwer. Na stdout zapisuj tylko wiersz odpowiedzi, bez innych komunikatów.

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

Przykłady żądań do silnika według gry

Przykłady pierwszego żądania dla czarnych, które łącznik wysyła do silnika --engine, wygenerowane kodem zasad. Prawdziwe partie każdego rodzaju zaczynają się od losowej pozycji z 12 pionkami, więc board i legal_moves są w każdej partii inne. Aby te przykłady się nie zmieniały, ich ruchy otwarcia wybrano blisko środka planszy. W prawdziwych partiach inne są też match_id i wartości zegara.

  • game_mode: Jedna z wartości std, anti, grand, octagon, reversi. XOT w grach z człowiekiem przychodzi jako std.
  • board_size: 8 albo 10 dla grand i octagon.
  • board: Tablica o długości board_size². Czytana od a1 w prawo, potem następny wiersz. 0=puste (w tym zablokowane pola Octagonu), 1=czarne, 2=białe.
  • blocked_squares: Niedostępne pola Octagonu. W innych grach pusta tablica.
  • placed: Pionki postawione dotąd przez każdy kolor, wliczając losowe ruchy otwarcia; klucze to ciągi "1" i "2". Używane do limitu 32 pionków w klasycznym Reversi.

Pola żądania do silnika

Anti (anti)

Wejściowy JSON (łącznik → 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
}

Odpowiedź JSON (adapter → łącznik)

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

Wejściowy JSON (łącznik → 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
}

Odpowiedź JSON (adapter → łącznik)

{"move":13}
Octagon (octagon)

Wejściowy JSON (łącznik → 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
}

Odpowiedź JSON (adapter → łącznik)

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

Wejściowy JSON (łącznik → 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
}

Odpowiedź JSON (adapter → łącznik)

{"move":18}

Częste problemy

ObjawPrzyczyna i rozwiązanie
Bot ma szarą kropkę (offline).Sprawdź, czy okno łącznika nadal działa. Łącznik zatrzymuje się, gdy zamkniesz okno, naciśniesz Ctrl+C lub komputer przejdzie w stan uśpienia. Powtórz kroki 6 i 7, a potem naciśnij Odśwież.
Pojawia się komunikat, że nie znaleziono polecenia.
command not found / is not recognized
Brakuje Pythona albo po instalacji nie otwarto ponownie okna. Zobacz krok 5, otwórz nowe okno i zacznij ponownie od kroku 4.
Pojawia się komunikat, że nie można otworzyć pliku.
can't open file
Uruchomiłeś polecenie poza folderem bota. Przejdź do folderu bota jak w kroku 4 i sprawdź poleceniem ls, czy na liście są README.md i tools.
Pojawia się komunikat o braku tokenu i program kończy działanie.
FS_BOT_TOKEN is missing or invalid
W tym oknie nie ma tokenu. Jeśli otworzyłeś nowe okno, powtórz krok 6.
Pojawia się komunikat, że token został odrzucony, i program się zatrzymuje.
Bot credential rejected
Token został wklejony niepoprawnie albo jest stary. Po utworzeniu nowego tokenu lub wyłączeniu bota stary token przestaje działać. Zacznij ponownie od kroku 6 z najnowszym tokenem.
Ciągle pojawia się ten sam wiersz błędu.
Connection/state error
Sprawdź połączenie z internetem. W macOS sprawdź, czy uruchomiłeś Install Certificates.command z kroku 5. Jeśli błąd pojawia się tylko podczas partii, serwer może odrzucać ruch twojego silnika jako nielegalny. Łącznik wysyła ten sam ruch ponownie, więc trzeba poprawić silnik.
Przycisk wyzwania nie działa albo wyzwanie nie jest akceptowane.Oba boty muszą mieć zieloną kropkę. Sprawdź, czy twój bot jest wybrany w Mój grający bot. Bot, który już gra inny mecz, nie przyjmuje nowego wyzwania. Niezaakceptowane wyzwanie wygasa po 120 sekundach.
Bot nie wykonuje ruchu, a potem przegrywa na czas albo mecz zostaje przerwany.
Engine failed for match
Silnik nie odpowiedział na czas albo jego odpowiedź miała zły format. Sprawdź polecenie silnika i jego jednowierszowe wyjście JSON. Jeśli bot nie wykona pierwszego ruchu w ciągu 30 sekund od startu, mecz zostaje przerwany.
Nie przychodzą wyzwania na warianty gry.Okno wyzwania pokazuje tylko gry obsługiwane przez oba boty. Sprawdź, czy gra jest zaznaczona w Obsługiwane gry twojego bota i czy drugi bot też ją obsługuje. Bot połączony przez NBoard powinien mieć zaznaczone tylko Standard.

Słowniczek

PojęcieZnaczenie
APIUstalony sposób, w jaki programy wysyłają żądania i odpowiedzi. Łącznik zajmuje się tym za ciebie.
TokenHasło tylko dla twojego bota. Łącznik używa go, aby udowodnić serwerowi, którym botem jest.
TerminalOkno, w którym wpisuje się polecenia. W Windows użyj PowerShell.
Zmienna środowiskowaTymczasowe ustawienie przekazywane programom uruchomionym z jednego okna terminala. Znika po zamknięciu okna.
JSONFormat zapisu danych jako tekstu. Przykład: {"move":19}
SilnikProgram, który ocenia pozycję i wybiera ruch.
AdapterMały program, który przekształca dane między twoim silnikiem a łącznikiem.
NBoardNBoard to graficzna aplikacja Othello, której można używać z NTest. Protokół NBoard to jej publiczny format komunikacji z silnikami i działa z silnikami, które go obsługują, np. NTest i Edax.
Legalny ruchPole, na które możesz teraz zagrać. Serwer wymienia je w legal_moves.

Pobieranie

Pobierz przykłady Python

OpenAPI 3.1 JSON

README.md

Zaloguj się, aby pobrać. .zip zawiera instrukcję, łącznik i przykładowy silnik; .json to specyfikacja API, a .md to instrukcja.

Szczegółowa specyfikacja

Dalsza część tej strony to szczegółowa dokumentacja dla programistów, którzy pracują bezpośrednio z silnikiem lub API.

Opcje łącznika

Opcje programu tools/bot_match_client.py. Wymagana jest dokładnie jedna z opcji --engine i --nboard / --edax / --egaroucid. Token nigdy nie jest opcją; jest odczytywany tylko ze zmiennej środowiskowej FS_BOT_TOKEN. Kody wyjścia: 0 normalne zatrzymanie, 2 token odrzucony, 3 osiągnięto --max-runtime.

ParametryOpis
--engine "COMMAND"Uruchamia to polecenie raz na partię i wymienia jedną linię JSON na ruch, nie zamykając jego wejścia. Silnik, który odpowiada jedną linią i kończy działanie, jest uruchamiany ponownie przy następnym ruchu.
--nboard "COMMAND"Utrzymuje jeden silnik NBoard na partię, łącznie dwa. Tylko gra standardowa (std).
--edax PATHEdax można też podłączyć, podając tylko ścieżkę programu przez --edax poniżej. Oddzielny adapter nie jest potrzebny; łącznik rozdziela czas na ruchy według pozostałego czasu partii.
--edax-eval PATHŚcieżka do eval.dat Edaxa. Bez podania szuka data/eval.dat w folderze programu lub jego folderze nadrzędnym.
--egaroucid PATHPrzygotuj Egaroucid for Console. Wybierz program konsolowy i pozostaw folder resources na miejscu. W macOS i Linuksie użyj programu zbudowanego według oficjalnej instrukcji. Pozycja i pozostały czas są przesyłane przez setboard, settime i go, bez adaptera.
--nboard-depth NWysyła do silnika NBoard set depth N. Wartość od 0 do 60; 0 zachowuje ustawienie domyślne silnika. (--edax: 0 → 60).
--max-think-seconds SMaksymalny czas w sekundach oczekiwania na silnik przy jednym ruchu. Domyślnie 10, maksymalnie 300. Nigdy dłużej niż czas pozostały na zegarze minus 0,75 sekundy. To zabezpieczenie łącznika; serwer nie ma limitu czasu na ruch. Wartość 0 usuwa limit na ruch i wykorzystuje pozostały czas partii.
--engine-name NAMENazwa silnika podawana w informacjach o bocie (do 120 znaków). Nie umieszczaj w niej poleceń ani ścieżek.
--cpu-name NAMENazwa procesora do zgłoszenia (do 120 znaków). Wykrywana automatycznie, jeśli jej nie podasz.
--once-match IDObsługuje tylko ten mecz i kończy działanie, gdy mecz się skończy (zakończony, przerwany, wygasły, odrzucony). Działają też ujemne ID gier z człowiekiem. Do testów łącz ją z --max-runtime.
--max-runtime SPo tylu sekundach zamyka silniki i kończy działanie (kod wyjścia 3).
--no-streamNie używa strumienia zdarzeń; tylko regularnie odpytuje serwer.
--api URL, --stream-url URLAdresy API i strumienia. Zwykle pozostaw wartości domyślne. Akceptowany jest tylko HTTPS, a ponieważ token jest wysyłany razem z żądaniem, strumień musi być na hoście API.

Samodzielna implementacja API

Odbieraj wyzwania i pozycje przez agent_poll lub uwierzytelniony strumień zdarzeń. Dla oczekujących wyzwań, w których jesteś bot_b, wyślij agent_accept, a potem obliczaj tylko pozycje, w których masz ruch, w każdej partii trwającego meczu. Wysyłaj najwyżej raz dla każdej kombinacji match_id, partii i revision, a gdy stan się zmieni, porzuć nieaktualne obliczenia. Gry z człowiekiem mają ujemne match_id i leg=0 oraz wymagają round_no i room_created_at dokładnie w otrzymanej postaci. W zwykłych pokojach wymuszone pasy obsługuje serwer; pasy wysyłają tylko pary botów.

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

Warianty gry

Bot otrzymuje tylko gry zaznaczone w Obsługiwane gry. Grę określa game_mode w żądaniach do silnika i w stanie meczu. Rozmiar planszy, zablokowane pola i legalne pola oblicza serwer, więc silnik musi tylko wybrać z otrzymanych legal_moves. Nie generuj ruchów dla 10×10 ani klasycznego Reversi generatorem ruchów dla standardowej planszy 8×8.

Gragame_modePlanszaPionki początkoweCzas (na partię i kolor)Pierwsze legal_moves czarnych
Standardstd8×8Losowe 12 pionków60,000 msZależy od pozycji
Antianti8×8Losowe 12 pionków60,000 msZależy od pozycji
Grand (10x10)grand10×10Losowe 12 pionków60,000 msZależy od pozycji
Octagonoctagon10×10 (88)Losowe 12 pionków60,000 msZależy od pozycji
Klasyczne reversireversi8×8Losowe 12 pionków60,000 msZależy od pozycji

Wspólne zasady: Czarne (1) zaczynają. Obie partie zaczynają się z tej samej pozycji z zamienionymi kolorami i toczą się jednocześnie. W parach botów, gdy nie masz legalnego ruchu, legal_moves przychodzi jako pusta tablica i bot musi sam wysłać move=-1 (pas). Serwer nie pasuje za ciebie, a twój zegar nadal biegnie. Pasowanie, gdy istnieje legalny ruch, jest nielegalne. Partia kończy się, gdy żaden kolor nie może wykonać ruchu. Puste pola nie są nikomu przyznawane; sumowane są tylko rzeczywiste pionki z obu partii, a równe sumy oznaczają remis. Wszystkie partie botów zaczynają się od losowej pozycji z 12 pionkami. W każdym wariancie każdy bot ma 1 minutę na partię.

Anti: stawianie i odwracanie pionków działa jak w standardowym Othello. Wygrywa strona, która ma mniej pionków łącznie w obu partiach.

10x10: standardowe zasady na planszy 10×10 (100 pól). Ustawienie początkowe to cztery środkowe pola; pary botów dodają do niego 8 losowych ruchów i zaczynają od 12 pionków.

Octagon: ta sama plansza i ustawienie początkowe co w 10x10, ale trzy pola w każdym rogu, łącznie 12, są niedostępne (88 grywalnych pól). Zablokowane pola przychodzą jako numery pól w blocked_squares oraz jako 0, ta sama wartość co puste, w board. Tablica nie jest uporządkowana numerycznie. Na zablokowane pole nie można zagrać, a linie pionków się na nim kończą.

Klasyczne Reversi: według zasad gra zaczyna się na pustej planszy. Dopóki któreś z czterech środkowych pól (27, 28, 35, 36) jest puste, stawia się pionki tylko na nich i nic się nie odwraca. Gdy wszystkie cztery są zajęte, obowiązują standardowe zasady. Każdy kolor stawia najwyżej 32 pionki, wliczając pierwsze cztery ruchy. placed liczy pionki postawione dotąd przez każdy kolor; jego klucze to ciągi "1" (czarne) i "2" (białe). Pary botów zaczynają po tym, jak serwer wykona 12 losowych postawień, w tym cztery ruchy w środku, więc w pierwszym żądaniu obie wartości placed dają razem 12 (zwykle {"1":6,"2":6}). Kolor, który postawił wszystkie 32, dostaje puste legal_moves i przy każdym swoim ruchu wysyła -1.

Przykłady żądań do silnika według gry

Przykłady pierwszego żądania dla czarnych, które łącznik wysyła do silnika --engine, wygenerowane kodem zasad. Prawdziwe partie każdego rodzaju zaczynają się od losowej pozycji z 12 pionkami, więc board i legal_moves są w każdej partii inne. Aby te przykłady się nie zmieniały, ich ruchy otwarcia wybrano blisko środka planszy. W prawdziwych partiach inne są też match_id i wartości zegara.

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}

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

Pola żądania do silnika

PoleTypOpis
game_modestringJedna z wartości std, anti, grand, octagon, reversi. XOT w grach z człowiekiem przychodzi jako std.
board_sizeinteger 8 | 108 albo 10 dla grand i octagon.
boardinteger[board_size²]Tablica o długości board_size². Czytana od a1 w prawo, potem następny wiersz. 0=puste (w tym zablokowane pola Octagonu), 1=czarne, 2=białe.
blocked_squaresinteger[]Niedostępne pola Octagonu. W innych grach pusta tablica.
placed{"1": integer, "2": integer}Pionki postawione dotąd przez każdy kolor, wliczając losowe ruchy otwarcia; klucze to ciągi "1" i "2". Używane do limitu 32 pionków w klasycznym Reversi.
legal_movesinteger[]Pola, na które możesz teraz zagrać. Jeśli lista jest pusta, odpowiedz move=-1.
colorinteger 1 | 2Kolor na ruchu. 1=czarne, 2=białe.
budget_msintegerCzas dozwolony na ten ruch w milisekundach: mniejsza z wartości --max-think-seconds oraz czasu pozostałego na zegarze minus 0,75 sekundy. Po przekroczeniu łącznik zatrzymuje silnik.
remaining_msintegerCzas pozostały na zegarze twojego koloru w tej partii, w milisekundach, po odjęciu czasu już zużytego w tej turze.
match_idinteger ≠ 0Id meczu. W grach z człowiekiem ujemne (-numer pokoju).
leginteger 0 | 1Numer partii 0 lub 1. W grach z człowiekiem zawsze 0.
revisionintegerNumer stanu tej partii. Wyślij go bez zmian razem z agent_move.
historyarrayRuchy wykonane w tej partii. Decyzję podejmuj na podstawie board.
round_nointeger | nullNumer rundy gry z człowiekiem. Dla par botów null.
room_created_atstring | nullCzas utworzenia pokoju gry z człowiekiem. Dla par botów null.

Wyzwanie innego bota na wariant

Dodaj game_mode do agent_challenge; domyślnie jest std. Oba boty muszą mieć tę grę w supported_games. Gry przeciwnika odczytasz z bots[].supported_games w odpowiedzi agent_poll. Jeśli któremuś z botów brakuje tej gry, odpowiedzią jest HTTP 409 z poniższym błędem.

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

Zmiana supported_games

Formularz rejestracji ma domyślnie zaznaczone Standard, a starsze boty bez zapisanej listy obsługują std. Listę można zmienić tylko w Zarządzaj na stronie botów w przeglądarce, nigdy tokenem bota (API). Zmiana jest odrzucana, gdy trwa mecz lub mecz czeka na akceptację. Pustej listy nie można zapisać.

NBoard i warianty

Połączenie --nboard obsługuje tylko grę standardową (std). Gdy przyjdzie wariant gry, łącznik nie pyta silnika i rezygnuje z tego ruchu (komunikat Engine failed for match). Żaden ruch nie zostaje wysłany, więc mecz zostaje przerwany przed pierwszym ruchem albo ta partia zostaje później przegrana na czas. Botowi NBoard ustaw grę standardową jako jedyną obsługiwaną grę.

Ludzie przeciwko zarejestrowanym botom

Ludzie mogą wyzwać twojego bota w każdej grze, którą ma zarejestrowaną. Jeśli obsługuje grę standardową, może przyjść zarówno STD, jak i XOT. XOT przychodzi z game_mode std i zaczyna się od planszy, na której wykonano już 8 ruchów (są one zawarte w history). Czas wybiera wyzywający, a partia jest nierankingowa. Gra z człowiekiem ma jedną partię (leg 0) i ujemne match_id. Gdy ruch nie jest możliwy, pasy obsługuje serwer, więc nie wysyłaj -1. Zarejestrowane boty online z włączonym automatycznym dobieraniem uczestniczą też w obsługiwanych szybkich meczach STD/XOT. Są rankingowe dla członków, a nierankingowe dla gości.

Nielegalne ruchy i czas

Nielegalny ruch lub nieaktualna revision są odrzucane z HTTP 409 (illegal_move, stale_revision, not_your_turn), a plansza się nie zmienia. W tym czasie zegar biegnie. Łącznik wysyła ten sam ruch ponownie z tą samą revision, więc jeśli silnik nie zostanie poprawiony, ta partia w końcu zostanie przegrana na czas. Serwer nie ma limitu na ruch i sprawdza tylko czas pozostały każdemu kolorowi. Te 10 sekund to domyślna wartość --max-think-seconds łącznika.

Uwierzytelnianie

Używaj HTTPS, JSON POST i Authorization: Bearer. Token dotyczy jednego bota. Nie umieszczaj go w URL, publicznym kodzie ani logach.

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

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

Zasady meczu

Wszystkie partie botów zaczynają się od losowej pozycji z 12 pionkami. Dwie partie toczą się jednocześnie ze zmianą kolorów. Decyduje łączna liczba pionków; w Anti wygrywa mniejsza liczba.

Wszystkie partie botów zaczynają się od losowej pozycji z 12 pionkami. W każdym wariancie każdy bot ma 1 minutę na partię.

Jeśli bot nie wykona pierwszego ruchu w ciągu 30 sekund od startu, mecz zostaje przerwany i nie liczy się do rankingu. Jeśli w trakcie meczu połączenie jednego bota zostanie utracone na dłużej niż 30 sekund, a drugi jest połączony, wygrywa połączony bot (finish_reason: disconnect), a wynik liczy się do rankingu.

Ruchy botów FlipSight we wszystkich rodzajach gier oblicza serwer. Przyjmowane są wyzwania z sieci i przez API (agent_challenge), a serwer od razu rozpoczyna mecz, więc nie musisz trzymać otwartego pokoju gry. Jeśli obliczenia serwera zatrzymają się na ponad 30 sekund, mecz zostaje przerwany (finish_reason: technical) i nie wpływa na ranking. Warianty są kosztowne w obliczeniach, więc naraz toczą się najwyżej 2 mecze wariantów z botami FlipSight. Gdy miejsca są zajęte, dostaniesz HTTP 409 z site bot variant matches are full; spróbuj ponownie za chwilę.

Automatyczne mecze botów: serwis stale utrzymuje dwa pokoje meczów botów. Dopóki zarejestrowany bot z włączonym automatycznym kojarzeniem jest online i nie gra, dostaje wyzwanie na standardowy mecz botów (std) z botem o podobnym rankingu (najpierw z innym zarejestrowanym botem, a jeśli go brak, z botem FlipSight) jako bot_b. Łącznik przyjmuje je automatycznie i mecz się zaczyna. Jeśli bot nie przyjmie wyzwania w ciągu 120 sekund albo je odrzuci, na 10 minut zostaje wyłączony z automatycznego kojarzenia. Mecze automatyczne liczą się do rankingu. Automatyczne mecze botów trwają również podczas partii Areny. Automatyczne kojarzenie jest domyślnie włączone i można je wyłączyć w sekcji Zarządzaj na stronie botów. Bot uruchomiony z silnikiem treningowym (environment.practice ma wartość true) jest wyłączony z meczów automatycznych, a jego mecze z innymi botami w tym czasie nie liczą się do rankingu.

Bezpośrednie wyzwanie człowieka to jedna partia w zwykłym pokoju z obsługiwaną grą i czasem. Serwer zawsze ustawia bezpośrednie wyzwania jako nierankingowe. Zarejestrowane boty online z włączonym automatycznym dobieraniem uczestniczą też w obsługiwanych szybkich meczach STD/XOT. Są rankingowe dla członków, a nierankingowe dla gości. Zarejestrowany bot przyjmuje tylko tyle partii, ile pozwala ustawienie jednoczesnych partii (domyślnie 2). Partia z człowiekiem i mecz botów (dwie plansze) liczą się każda jako 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)

Przebieg meczu

Wszystkie partie botów zaczynają się od losowej pozycji z 12 pionkami. Dwie partie toczą się jednocześnie ze zmianą kolorów. Decyduje łączna liczba pionków; w Anti wygrywa mniejsza liczba.

Twój numer uczestnika to 0, jeśli jesteś bot_a, i 1, jeśli jesteś bot_b. Uczestnik 0 gra czarnymi (1) w legs[0] i białymi (2) w legs[1]; uczestnik 1 odwrotnie. Masz ruch, gdy leg.status ma wartość active, a leg.turn jest równe twojemu kolorowi. Możesz mieć ruch w obu partiach jednocześnie.

Jeśli nie masz legalnego ruchu, musisz sam wysłać move -1 (pas); serwer nie pasuje za ciebie. W grach z człowiekiem serwer obsługuje pasy tak jak w zwykłych pokojach gry.

W twojej turze pozostały czas = clocks_ms[twój kolor - 1] - (server_now_ms - turn_started_ms). clocks_ms jest w kolejności kolorów (czarne, białe); totals i winner są w kolejności uczestników (winner 0 lub 1, null przy remisie).

Numery pól

Numer pola = wiersz × 8 + kolumna, więc a1=0, h1=7, a2=8, a h8=63. Na przykład 19 to d3, a 37 to f5. Tablica board używa tej samej kolejności: 0=puste, 1=czarne, 2=białe.

    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

Na planszy 10×10 (grand, octagon) pole = wiersz × 10 + kolumna. Kolumny to a do j, a dziesiąty wiersz zapisuje się we współrzędnych jako 10 lub 0. a1=0, j1=9, a2=10, j10=99 (to samo co j0). Ta sama współrzędna ma inny numer na każdym rozmiarze planszy: d3 to 19 na 8×8 i 23 na 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 ## ##

Strumień zdarzeń

Połącz się z tym samym tokenem Bearer z GET https://othelloclub.com/flipsight_api/bot-stream, aby otrzymywać jedno zdarzenie JSON na wiersz (NDJSON): hello potwierdza połączenie, match oznacza zmianę w jednym z twoich meczów (zdarzenia meczów między botami zawierają pełny obiekt meczu), a ping co 10 sekund podtrzymuje połączenie. Po otrzymaniu match wywołaj raz agent_poll. Gdy połączenie zostanie zerwane, łącz się ponownie z opóźnieniem rosnącym od 1 do 30 sekund i w międzyczasie dalej odpytuj. Gdy strumień jest połączony, twój bot jest widoczny jako online; każdy bot może otworzyć do 3 strumieni.

Przykłady zdarzeń

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}

Adapter silnika

Program łączący botów FlipSight (Python 3.9+, tylko biblioteka standardowa) działa w systemach Windows, macOS i Linux i podłącza silnik na dwa sposoby. --engine uruchamia silnik raz na partię, przy każdym ruchu wysyła jedną linię JSON i odczytuje jedną linię {"move": pole}. Silnik, który odpowiada jedną linią i kończy działanie, nadal działa, bo przy następnym ruchu zostaje uruchomiony ponownie, ale silnik czytający wejście do końca (sys.stdin.read() itp.) nigdy nie odpowie, więc czytaj po jednej linii. legal_moves w żądaniu zawiera legalne pola; jeśli lista jest pusta, odpowiedz -1. --nboard utrzymuje działający silnik NBoard, jeden na partię; głębokość ustawia --nboard-depth.

Edax można też podłączyć, podając tylko ścieżkę programu przez --edax poniżej. Oddzielny adapter nie jest potrzebny; łącznik rozdziela czas na ruchy według pozostałego czasu partii.

Przygotuj Egaroucid for Console. Wybierz program konsolowy i pozostaw folder resources na miejscu. W macOS i Linuksie użyj programu zbudowanego według oficjalnej instrukcji. Pozycja i pozostały czas są przesyłane przez setboard, settime i go, bez adaptera.

Wejściowy JSON (łącznik → adapter)

To przykładowe dane wysyłane przez łącznik, a nie kod do uruchomienia. Nie zapisuj ich jako pliku Pythona. Podczas partii aktualne dane są wysyłane automatycznie przy każdym ruchu.

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

Odpowiedź JSON (adapter → łącznik)

{"move":2}

Synchronizacja stanu

Przy połączonym strumieniu wywołuj agent_poll po nadejściu zdarzenia i co 10 sekund. Bez strumienia odpytuj co sekundę w trakcie meczu i co 5 sekund w bezczynności, ale nigdy częściej niż raz na sekundę. Czas komunikacji sieciowej jest odliczany z twojego zegara. Jeśli odpowiedź na ruch zostanie utracona, odczytaj nowy stan i wyślij ten sam ruch ponownie tylko wtedy, gdy revision jest nadal taka sama.

Operacje API

Przykłady odpowiedzi pokazują format. Przycisk kopiowania kopiuje tylko żądanie i nie wywołuje API.

POST agent_poll

Odczytaj stan

ParametryTypWymaganeOpis
actionstring "agent_poll"WymaganeNazwa wywoływanej akcji
environmentobjectOpcjonalneCPU, system i nazwa silnika na komputerze z programem łączącym (każde do 120 znaków, opcjonalne) oraz practice (true lub false, opcjonalne). practice równe true oznacza, że bot ćwiczy: jest wyłączony z meczów automatycznych, a mecze botów zgłoszone lub przyjęte w tym czasie nie liczą się do rankingu. Program łączący wysyła tę wartość sam, gdy działa z silnikiem treningowym. Serwer pamięta ostatnią wartość, więc po zakończeniu ćwiczeń wyślij ją bez practice albo z false.

Przykład żądania

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

Przykład odpowiedzi

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

Obiekt meczu (matches[])

PoleTypOpis
idintegerID meczu (ujemne dla gier z człowiekiem)
kindstringhuman dla gier z człowiekiem
bot_aintegerID bota uczestnika 0 (0 dla człowieka)
bot_bintegerID bota uczestnika 1 (0 dla człowieka)
game_modestringGra (std, anti, grand, octagon, reversi)
time_limit_secintegerSekundy na partię i kolor
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerTermin akceptacji (milisekundy)
rankedbooleanCzy mecz jest rankingowy
state.statusstringactive, finished lub aborted
state.game_modestringGra (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Liczba pól na boku, 8 lub 10
state.blocked_squaresinteger[]Niedostępne pola (tylko Octagon, nieposortowane)
state.legsarrayPartie (2 między botami, 1 z człowiekiem)
state.totals[integer, integer]Suma pionków z obu partii dla każdego uczestnika
state.winnerinteger | nullZwycięski uczestnik 0 lub 1, null przy remisie
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Obiekt partii (state.legs[])

PoleTypOpis
boardinteger[64 | 100]64 / 100 pola, 0=puste 1=czarne 2=białe
turnintegerKolor na ruchu, 1 (czarne) lub 2 (białe)
legal_movesinteger[]Legalne pola dla koloru na ruchu. Puste w zakończonych partiach
placed{"1": integer, "2": integer}Postawione pionki według koloru, klucze "1" (czarne) i "2" (białe)
black_participantintegerUczestnik grający czarnymi w tej partii
clocks_ms[number, number]Bazowe wartości pozostałego czasu w kolejności kolorów (czarne, białe)
turn_started_msintegerCzas serwera, gdy zaczęła się bieżąca tura
revisionintegerRośnie o 1 przy każdej zmianie tej partii
statusstringactive, finished, timeout, stopped
historyarrayWykonane ruchy (color, participant, move, at_ms)
discs[integer, integer]Liczba pionków w kolejności kolorów (czarne, białe)
result_discs[integer, integer] (optional)Opcjonalny przyznany wynik [czarne, białe] dla partii przegranej na czas; sumuje się go zamiast pionków. Wyniki strony, której skończył się czas, i jej przeciwnika: std i reversi 0 i 64, grand 0 i 100, octagon 0 i 88, anti 64 i 0. Druga partia toczy się dalej, a o zwycięzcy decydują sumy po zakończeniu obu partii. Starsze zakończone zapisy nie są przeliczane.

POST agent_challenge

Wyzwij

Podaj target_id (numer bota) lub target_name (nazwę bota); znajdziesz je na liście bots w odpowiedzi agent_poll. Wbudowane boty FlipSight (is_builtin: true) można wyzwać przez API we wszystkich rodzajach gier, a serwer od razu rozpoczyna mecz. Gdy trwają już 2 mecze wariantów z wbudowanymi botami, dostaniesz HTTP 409 z site bot variant matches are full.

Wyzwij ze strony botów

ParametryTypWymaganeOpis
actionstring "agent_challenge"WymaganeNazwa wywoływanej akcji
target_idinteger ≥ 1OpcjonalneID wyzywanego bota
target_namestringOpcjonalneNazwa wyzywanego bota (zamiast target_id)
game_modestring std | anti | octagon | grand | reversiOpcjonalneGra dla pary botów. Domyślnie std. Oba boty muszą ją obsługiwać. Standard / Anti / Octagon / Grand (10x10) / Klasyczne reversi

Przykład żądania

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

Przykład odpowiedzi

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

POST agent_accept

Akceptuj

ParametryTypWymaganeOpis
actionstring "agent_accept"WymaganeNazwa wywoływanej akcji
match_idinteger ≥ 1WymaganeID meczu (ujemne dla gier z człowiekiem)

Przykład żądania

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

Przykład odpowiedzi

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

POST agent_reject

Odrzuć

ParametryTypWymaganeOpis
actionstring "agent_reject"WymaganeNazwa wywoływanej akcji
match_idinteger ≥ 1WymaganeID meczu (ujemne dla gier z człowiekiem)

Przykład żądania

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

Przykład odpowiedzi

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

POST agent_move

Wyślij ruch

ParametryTypWymaganeOpis
actionstring "agent_move"WymaganeNazwa wywoływanej akcji
match_idinteger ≠ 0WymaganeID meczu (ujemne dla gier z człowiekiem)
round_nointeger ≥ 0OpcjonalneTylko gry z człowiekiem: round_no z obiektu meczu
room_created_atstringOpcjonalneTylko gry z człowiekiem: room_created_at z obiektu meczu
leginteger 0 | 1WymaganePartia 0 lub 1 (0 dla gier z człowiekiem)
revisioninteger ≥ 0WymaganeRevision partii, dla której obliczono ruch; 409 stale_revision, jeśli partia się zmieniła
moveinteger -1..99 | stringWymaganePole 0-(board_size²−1), -1 dla pasa / d3 / D3 / pass

Przykład żądania

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

Przykład odpowiedzi

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

Błędy

Błędy mają postać {"ok": false, "error": "…"}. Przy 401 zatrzymaj się i sprawdź token. Przy 409 odczytaj aktualny stan i sprawdź turę, revision oraz legalne ruchy. 429 oznacza zbyt wiele strumieni jednego bota. Błędy połączenia i 503 ponawiaj z rosnącymi odstępami i nigdy nie wysyłaj ponownie nielegalnego ruchu.

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