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.
ls5. 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 --versionWindows PowerShell
py --versionTak 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.
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_TOKENWindows PowerShell
$env:FS_BOT_TOKEN = [Net.NetworkCredential]::new('', (Read-Host 'Bot token' -AsSecureString)).PasswordW 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.pyWindows 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 Minimized10. 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.6Uruchomienie
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-64Uruchomienie
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 --installtar -xzf edax-reversi-4.6.tar.gz
mv edax-reversi-4.6 edax-4.6
mkdir -p edax-4.6/bin
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6 data/eval.dat
(cd edax-4.6/src && make build ARCH=native OS=osx)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.
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.
bot_match_client.py→ my_adapter.pybot_match_client.py← my_adapter.pyJakiego 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.pyWindows PowerShell
'{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | py my_adapter.py2. 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 1777my_engine → my_adapter.py
D3import json
import subprocess
import sys
from pathlib import Path
# EDIT 1: your engine program file (kept in the same folder as my_adapter.py)
ENGINE = Path(__file__).with_name("my_engine.exe" if sys.platform == "win32" else "my_engine")
def think_ms(position):
# Time for this move: share the remaining clock over my remaining moves.
my_moves_left = position["board"].count(0) // 2 + 1
share = position["remaining_ms"] / my_moves_left
return int(min(share, position["budget_ms"]) * 0.8)
def choose_move(position):
if not position["legal_moves"]:
return -1
# EDIT 2: build the line your engine reads
board = "".join(".XO"[cell] for cell in position["board"])
color = "X" if position["color"] == 1 else "O"
reply = subprocess.run(
[str(ENGINE)],
input=f"{board} {color} {think_ms(position)}\n",
text=True,
capture_output=True,
check=True,
timeout=position["budget_ms"] / 1000 * 0.9,
)
# EDIT 3: take the square out of your engine's answer
return reply.stdout.strip()
position = json.loads(sys.stdin.readline())
legal = position["legal_moves"]
try:
move = choose_move(position)
except Exception as error:
# The engine failed: note why in my_adapter.log and play the first legal square.
with open(Path(__file__).with_name("my_adapter.log"), "a") as log:
log.write(repr(error) + "\n")
move = legal[0] if legal else -1
print(json.dumps({"move": move}), flush=True)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.
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
| Objaw | Przyczyna 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ęcie | Znaczenie |
|---|---|
| API | Ustalony sposób, w jaki programy wysyłają żądania i odpowiedzi. Łącznik zajmuje się tym za ciebie. |
| Token | Hasło tylko dla twojego bota. Łącznik używa go, aby udowodnić serwerowi, którym botem jest. |
| Terminal | Okno, w którym wpisuje się polecenia. W Windows użyj PowerShell. |
| Zmienna środowiskowa | Tymczasowe ustawienie przekazywane programom uruchomionym z jednego okna terminala. Znika po zamknięciu okna. |
| JSON | Format zapisu danych jako tekstu. Przykład: {"move":19} |
| Silnik | Program, który ocenia pozycję i wybiera ruch. |
| Adapter | Mały program, który przekształca dane między twoim silnikiem a łącznikiem. |
| NBoard | 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. |
| Legalny ruch | Pole, na które możesz teraz zagrać. Serwer wymienia je w legal_moves. |
Pobieranie
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.
| Parametry | Opis |
|---|---|
--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 PATH | 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. |
--edax-eval PATH | Ścieżka do eval.dat Edaxa. Bez podania szuka data/eval.dat w folderze programu lub jego folderze nadrzędnym. |
--egaroucid PATH | 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. |
--nboard-depth N | Wysyła do silnika NBoard set depth N. Wartość od 0 do 60; 0 zachowuje ustawienie domyślne silnika. (--edax: 0 → 60). |
--max-think-seconds S | Maksymalny 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 NAME | Nazwa silnika podawana w informacjach o bocie (do 120 znaków). Nie umieszczaj w niej poleceń ani ścieżek. |
--cpu-name NAME | Nazwa procesora do zgłoszenia (do 120 znaków). Wykrywana automatycznie, jeśli jej nie podasz. |
--once-match ID | Obsł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 S | Po tylu sekundach zamyka silniki i kończy działanie (kod wyjścia 3). |
--no-stream | Nie używa strumienia zdarzeń; tylko regularnie odpytuje serwer. |
--api URL, --stream-url URL | Adresy 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 / abortedWarianty 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.
| Gra | game_mode | Plansza | Pionki początkowe | Czas (na partię i kolor) | Pierwsze legal_moves czarnych |
|---|---|---|---|---|---|
| Standard | std | 8×8 | Losowe 12 pionków | 60,000 ms | Zależy od pozycji |
| Anti | anti | 8×8 | Losowe 12 pionków | 60,000 ms | Zależy od pozycji |
| Grand (10x10) | grand | 10×10 | Losowe 12 pionków | 60,000 ms | Zależy od pozycji |
| Octagon | octagon | 10×10 (88) | Losowe 12 pionków | 60,000 ms | Zależy od pozycji |
| Klasyczne reversi | reversi | 8×8 | Losowe 12 pionków | 60,000 ms | Zależ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
| Pole | Typ | Opis |
|---|---|---|
game_mode | string | Jedna z wartości std, anti, grand, octagon, reversi. XOT w grach z człowiekiem przychodzi jako std. |
board_size | integer 8 | 10 | 8 albo 10 dla grand i octagon. |
board | integer[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_squares | integer[] | 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_moves | integer[] | Pola, na które możesz teraz zagrać. Jeśli lista jest pusta, odpowiedz move=-1. |
color | integer 1 | 2 | Kolor na ruchu. 1=czarne, 2=białe. |
budget_ms | integer | Czas 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_ms | integer | Czas pozostały na zegarze twojego koloru w tej partii, w milisekundach, po odjęciu czasu już zużytego w tej turze. |
match_id | integer ≠ 0 | Id meczu. W grach z człowiekiem ujemne (-numer pokoju). |
leg | integer 0 | 1 | Numer partii 0 lub 1. W grach z człowiekiem zawsze 0. |
revision | integer | Numer stanu tej partii. Wyślij go bez zmian razem z agent_move. |
history | array | Ruchy wykonane w tej partii. Decyzję podejmuj na podstawie board. |
round_no | integer | null | Numer rundy gry z człowiekiem. Dla par botów null. |
room_created_at | string | null | Czas 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 63Na 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 99Octagon (octagon)
a b c d e f g h i j
1 ## ## 2 3 4 5 6 7 ## ##
2 ## 11 12 13 14 15 16 17 18 ##
3 20 21 22 23 24 25 26 27 28 29
4 30 31 32 33 34 35 36 37 38 39
5 40 41 42 43 44 45 46 47 48 49
6 50 51 52 53 54 55 56 57 58 59
7 60 61 62 63 64 65 66 67 68 69
8 70 71 72 73 74 75 76 77 78 79
9 ## 81 82 83 84 85 86 87 88 ##
10 ## ## 92 93 94 95 96 97 ## ##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
| Parametry | Typ | Wymagane | Opis |
|---|---|---|---|
action | string "agent_poll" | Wymagane | Nazwa wywoływanej akcji |
environment | object | Opcjonalne | CPU, 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[])
| Pole | Typ | Opis |
|---|---|---|
id | integer | ID meczu (ujemne dla gier z człowiekiem) |
kind | string | human dla gier z człowiekiem |
bot_a | integer | ID bota uczestnika 0 (0 dla człowieka) |
bot_b | integer | ID bota uczestnika 1 (0 dla człowieka) |
game_mode | string | Gra (std, anti, grand, octagon, reversi) |
time_limit_sec | integer | Sekundy na partię i kolor |
status | string | pending, running, finished, aborted, rejected, expired |
expires_ms | integer | Termin akceptacji (milisekundy) |
ranked | boolean | Czy mecz jest rankingowy |
state.status | string | active, finished lub aborted |
state.game_mode | string | Gra (std, anti, grand, octagon, reversi) |
state.board_size | integer 8 | 10 | Liczba pól na boku, 8 lub 10 |
state.blocked_squares | integer[] | Niedostępne pola (tylko Octagon, nieposortowane) |
state.legs | array | Partie (2 między botami, 1 z człowiekiem) |
state.totals | [integer, integer] | Suma pionków z obu partii dla każdego uczestnika |
state.winner | integer | null | Zwycięski uczestnik 0 lub 1, null przy remisie |
state.finish_reason | string | null | discs, timeout, double_timeout, disconnect, no_first_move |
Obiekt partii (state.legs[])
| Pole | Typ | Opis |
|---|---|---|
board | integer[64 | 100] | 64 / 100 pola, 0=puste 1=czarne 2=białe |
turn | integer | Kolor na ruchu, 1 (czarne) lub 2 (białe) |
legal_moves | integer[] | 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_participant | integer | Uczestnik 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_ms | integer | Czas serwera, gdy zaczęła się bieżąca tura |
revision | integer | Rośnie o 1 przy każdej zmianie tej partii |
status | string | active, finished, timeout, stopped |
history | array | Wykonane 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.
| Parametry | Typ | Wymagane | Opis |
|---|---|---|---|
action | string "agent_challenge" | Wymagane | Nazwa wywoływanej akcji |
target_id | integer ≥ 1 | Opcjonalne | ID wyzywanego bota |
target_name | string | Opcjonalne | Nazwa wyzywanego bota (zamiast target_id) |
game_mode | string std | anti | octagon | grand | reversi | Opcjonalne | Gra 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
| Parametry | Typ | Wymagane | Opis |
|---|---|---|---|
action | string "agent_accept" | Wymagane | Nazwa wywoływanej akcji |
match_id | integer ≥ 1 | Wymagane | ID 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ć
| Parametry | Typ | Wymagane | Opis |
|---|---|---|---|
action | string "agent_reject" | Wymagane | Nazwa wywoływanej akcji |
match_id | integer ≥ 1 | Wymagane | ID 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
| Parametry | Typ | Wymagane | Opis |
|---|---|---|---|
action | string "agent_move" | Wymagane | Nazwa wywoływanej akcji |
match_id | integer ≠ 0 | Wymagane | ID meczu (ujemne dla gier z człowiekiem) |
round_no | integer ≥ 0 | Opcjonalne | Tylko gry z człowiekiem: round_no z obiektu meczu |
room_created_at | string | Opcjonalne | Tylko gry z człowiekiem: room_created_at z obiektu meczu |
leg | integer 0 | 1 | Wymagane | Partia 0 lub 1 (0 dla gier z człowiekiem) |
revision | integer ≥ 0 | Wymagane | Revision partii, dla której obliczono ruch; 409 stale_revision, jeśli partia się zmieniła |
move | integer -1..99 | string | Wymagane | Pole 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}| HTTP | error |
|---|---|
| 400 | JSON object required / unknown action |
| 401 | invalid bot credential |
| 403 | access denied / invalid csrf |
| 405 | POST required |
| 409 | stale_revision / not_your_turn / illegal_move / leg_finished / match not running / match timed out / stale room revision / invalid move coordinate / move outside board / unsupported game mode / both bots must be connected / bot already has an active match / bot busy / challenge expired or answered / site bot variant matches are full / target bot not found |
| 413 | request too large |
| 429 | too many streams (bot-stream) |
| 503 | bot match storage unavailable |