Пошаговое руководство для новичков
Что понадобится
- Учётная запись FlipSight. Зарегистрировать бота и скачать программу подключения можно после входа.
- Одно фото бота: файл GIF, JPG или PNG размером не более 5 МБ, шириной и высотой не меньше 175 px.
- Ваш компьютер для работы бота Ходы вашего бота считаются на компьютере, где работает программа подключения. Даже с тем же движком сила может отличаться в зависимости от мощности компьютера и настроек движка, а если вы смените компьютер или настройки, рейтинг может соответственно измениться.
- Python 3, бесплатная программа, которая запускает программу подключения. Подходит версия 3.9 или новее; рекомендуется 3.11 или новее. Вы проверите это на шаге 5.
- Движок вашего бота. В комплект программы подключения входит тренировочный движок для тестирования.
1. Зарегистрируйте бота
Войдите и откройте страницу ботов (othelloclub.com/ru/flipsight/bot). В блоке Зарегистрировать бота вверху введите имя в поле Имя бота (2–24 символа). В Поддерживаемые игры пока оставьте отмеченным только Стандарт. Выберите изображение в Загрузить фото бота, затем нажмите кнопку Зарегистрировать бота.
Признак успеха: появляется блок Токен подключения (показывается один раз) с длинным текстом, который начинается с fsbm_.
2. Сохраните токен
Этот длинный текст и есть токен, пароль только для вашего бота. С его помощью программа подключения доказывает серверу, что действует от имени этого бота. Скопируйте его целиком и временно вставьте куда-нибудь, например в текстовый редактор. После закрытия блока этот же токен снова увидеть нельзя.
Обращайтесь с токеном как с паролем. Никому его не показывайте и не публикуйте его снимки экрана. Если вы его потеряли или его кто-то увидел, откройте Управление в списке ботов и нажмите Создать токен подключения. Как только вы получите новый токен, старый сразу перестанет работать.
3. Скачайте программу подключения
Нажмите Скачать примеры Python, чтобы скачать ZIP-файл, и распакуйте его. В этом руководстве распакованная папка называется «папкой бота». Положите её туда, где её легко найти, например на рабочий стол.
Признак успеха: в папке бота есть файл README.md и папка tools.
4. Откройте терминал в папке бота
Терминал представляет собой окно, в котором вводят команды. Откройте его так, как описано для вашей системы.
macOS: нажмите Command+Space, введите Terminal и откройте его. Введите cd и один пробел, перетащите папку бота в окно и нажмите Enter.
Linux: в файловом менеджере щёлкните папку бота правой кнопкой мыши и выберите «Открыть в терминале». Название пункта меню может отличаться в разных дистрибутивах. Если такого пункта нет, откройте терминал и используйте cd, как в macOS.
Windows: откройте папку бота в Проводнике. Введите powershell в адресной строке вверху и нажмите Enter. В этой папке откроется окно PowerShell.
Проверка: введите команду ниже и нажмите Enter. Если в списке появились README.md и tools, всё в порядке. Эта команда одинакова в macOS, Linux и Windows PowerShell.
ls5. Проверьте Python
В том же окне скопируйте одну команду для своей системы, вставьте её и нажмите Enter. Можно нажать кнопку копирования в правом верхнем углу каждого блока кода.
macOS / Linux
python3 --versionWindows PowerShell
py --versionПризнак успеха: версия, которая начинается с 3, например Python 3.11.9. Подходит версия 3.9 или новее. Если команда не найдена, установите Python, как описано ниже, закройте окно и начните снова с шага 4.
macOS: скачайте установщик для macOS (.pkg) с python.org и установите его. Затем один раз запустите Install Certificates.command из папки Python в «Программах». Так подготавливаются сертификаты, нужные для защищённых (HTTPS) соединений.
Linux: установите python3 через центр приложений или менеджер пакетов своего дистрибутива.
Windows: установите менеджер установки Python (Python install manager) с python.org.
6. Введите токен
Поместите токен в переменную окружения, то есть во временную настройку, которая существует только внутри этого окна терминала. Её имя FS_BOT_TOKEN. Скопируйте ниже однострочную команду для своей системы, вставьте её и один раз нажмите Enter. После этого появится Bot token и программа будет ждать ввода. Теперь вставьте токен, скопированный на шаге 2, и нажмите 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)).PasswordВ macOS и Linux вставленный токен не отображается, а в Windows вместо его символов видны *. И то и другое нормально. Токен не записывается в команду, поэтому не остаётся в истории команд. Настройка действует только в этом окне. Если вы закрыли его или открыли новое окно, повторите шаги 4 и 6.
Windows: в PowerShell вставляйте с помощью Ctrl+V или правой кнопкой мыши. Эта однострочная команда написана так, что работает и в Windows PowerShell 5.1, который входит в Windows, и в более новом PowerShell 7, но на настоящем компьютере с Windows её ещё не проверяли.
Признак успеха: снова появляется приглашение для следующей команды, и ошибки нет.
7. Запустите программу подключения
В том же окне скопируйте команду ниже без изменений, вставьте её и нажмите Enter. Не меняйте в ней имена файлов. Ваш бот подключится с тренировочным движком. Это очень слабый движок, который ходит на допустимую клетку с наименьшим номером.
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'Это нормально, если во время работы команда не выводит текст и не возвращает приглашение ввода. Чтобы бот оставался онлайн, держите это окно открытым, программу подключения и движок — запущенными, а компьютеру не давайте переходить в спящий режим. Закрытие окна или Ctrl+C завершает обе программы, и бот становится офлайн. Для повторного подключения снова выполните команду выше. Программа подключения автоматически принимает вызовы от других ботов.
Пока бот работает с тренировочным движком, автоматические матчи ему не назначаются, а его матчи с другими ботами, даже те, что вы запросили сами, не рейтинговые. Перезапустите его со своим движком (шаг 10), и с этого момента он участвует в автоподборе и рейтинге.
Признак успеха: в списке Зарегистрированные боты на странице ботов точка справа от имени вашего бота зелёная. При наведении на неё появляется В сети. Если она серая (Не в сети), нажмите Обновить.
8. Проверьте первой игрой
На странице ботов выберите своего бота в Мой играющий бот. Выберите любого бота в списке Боты FlipSight, нажмите Вызвать и подтвердите в открывшемся окне. Во всех видах игр ходы ботов FlipSight рассчитывает сервер, поэтому держать игровую комнату открытой не нужно. При включённом автоподборе (по умолчанию) сайт также назначает матчи ботов, пока работает программа подключения.
Признак успеха: в игровой комнате видны две доски, и на каждом ходу вашего бота ставится фишка. Тренировочный движок очень слабый, поэтому проигрывать нормально.
9. Работа после закрытия окна (необязательно)
Запустите команду ниже вместо шага 7, и бот продолжит работать после закрытия окна. Выполняйте её в том же окне, где вводили токен (шаг 6). Работа прекращается, если компьютер выключается или уходит в спящий режим. Если остановить бота во время игры, эта игра будет проиграна по времени или из-за отключения.
macOS / Linux
macOS / Linux: после выполнения команды запуска окно можно закрыть. Сообщения записываются в файл bot.log в папке бота. Чтобы остановить бота, откройте новый терминал и выполните команду остановки. Копируйте только нужный блок: если вставить оба блока сразу, бот остановится сразу после запуска.
Запуск
nohup python3 tools/bot_match_client.py --engine 'python3 tools/bot_match_example_engine.py' > bot.log 2>&1 &Остановка
pkill -f bot_match_client.pyWindows PowerShell
Windows: на панели задач появляется свёрнутое окно, в котором работает бот. Текущее окно PowerShell можно закрыть. Чтобы остановить бота, откройте свёрнутое окно и нажмите Ctrl+C или закройте это окно.
Запуск
Start-Process py -ArgumentList 'tools/bot_match_client.py --engine "py tools/bot_match_example_engine.py"' -WorkingDirectory $PWD -WindowStyle Minimized10. Переход на свой движок
После проверки соединения переключитесь на свой движок: специальные параметры ниже для Edax или Egaroucid, --nboard для других совместимых движков, JSON для собственного. Токен остаётся прежним.
Отмечайте вариант игры (Anti, 10x10, Octagon, классический Reversi) в Поддерживаемые игры, только если ваш движок знает его правила. Другие боты и люди могут вызвать вашего бота в любой отмеченной игре. Чтобы изменить выбор, откройте Управление в списке ботов, измените Поддерживаемые игры и нажмите Сохранить. Менять его нельзя, пока идёт игра или ожидает вызов.
Примеры запросов к движку по играм
Подключение движка с поддержкой NBoard, например Edax
NBoard — графическая программа для Othello, которую можно использовать с NTest. Протокол NBoard — её открытый формат связи с движками. Он работает с поддерживающими его движками, например NTest и Edax.
Примеры установки и запуска ниже используют Edax 4.6 и Egaroucid for Console 7.8.2.
Edax 4.6 не входит в программу подключения. Скачайте файл для своей ОС из раздела Assets на Edax 4.6, поместите его в папку бота и распакуйте командами подготовки ниже. Команды Windows и Linux рассчитаны на x86-64.
Windows PowerShell (x86-64): edax-4.6-MS-windows-x86.zip
Подготовка (только в первый раз)
Expand-Archive -LiteralPath edax-4.6-MS-windows-x86.zip -DestinationPath edax-4.6Запуск
py tools/bot_match_client.py --edax "edax-4.6\wEdax-x86-64.exe"Linux (x86-64): edax-4.6-linux-x86.tar.gz
Подготовка (только в первый раз)
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Запуск
python3 tools/bot_match_client.py --edax "edax-4.6/lEdax-x86-64"macOS: edax-reversi-4.6.tar.gz
Официального исполняемого файла Edax 4.6 для macOS нет. Скачайте Source code (tar.gz) и edax-4.6-linux-x86.tar.gz из того же выпуска. Соберите программу для Mac из исходников; из архива Linux извлекается только общий файл оценки data/eval.dat. Нужны инструменты Xcode Command Line Tools. Команды подготовки компилируют исходный код.
Подготовка (только в первый раз)
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)Запуск
python3 tools/bot_match_client.py --edax "edax-4.6/bin/mEdax-native"Edax также можно подключить, указав только путь к программе через --edax ниже. Отдельный адаптер не нужен: время на ход распределяется по оставшимся часам партии.
Замените путь в команде полным путём к своей программе. Путь с пробелами заключите в двойные кавычки. Если файл оценки не найден автоматически, укажите eval.dat через --edax-eval.
Подключение через NBoard играет только стандартные партии 8×8. Для этого бота оставьте в Поддерживаемые игры отмеченным только Стандарт. Если придёт вариант игры, программа подключения не обратится к движку, ход не будет сделан, и игра будет прервана или проиграна по времени.
Egaroucid: --egaroucid
Подготовьте Egaroucid for Console. Выберите консольную программу и оставьте папку resources на месте. В macOS и Linux используйте программу, собранную по официальной инструкции. Позиция и оставшееся время передаются командами setboard, settime и go без адаптера.
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"Оба режима поддерживают только обычную игру 8×8, по одному движку на каждую одновременную партию. --max-think-seconds — защитный лимит на ход (по умолчанию 10 секунд). Если Egaroucid достигает его, поиск останавливается, делается допустимый запасной ход, а на следующем ходу движок перезапускается.
Самописный движок: подключение через JSON
Читайте эту часть, только если хотите подключить движок, который написали сами. Если вы используете Edax, Egaroucid или другой движок NBoard, у вас уже всё готово выше, и эту часть можно пропустить.
Каждый раз, когда наступает ход вашего бота, программа подключения один раз запускает my_adapter.py. Она передаёт текущую позицию одной строкой (где стоят фишки, чей ход, какие клетки допустимы, сколько осталось времени), my_adapter.py отвечает одной клеткой в одной строке, и программа подключения делает этот ход на сервере. my_adapter.py представляет собой небольшой файл на Python, который стоит посередине и запрашивает ход у вашего движка. Соединением с сервером, токеном и отправкой ходов занимается программа подключения, поэтому этот файл с ними никогда не работает.
bot_match_client.py→ my_adapter.pybot_match_client.py← my_adapter.pyКакого типа ваш движок?
- Движок, который выбирает ход в коде на Python: используйте пример A ниже.
- Файл программы, который при каждом запуске выводит одну строку ответа и завершается: используйте пример B ниже.
- Исполняемый файл, который остаётся открытым и постоянно обменивается командами: если он поддерживает NBoard, подключите его через --nboard выше. Для другого протокола переделайте пример B под этот протокол и пусть my_adapter.py держит движок открытым и обменивается по одной строке в цикле.
Шаг 1: файл, который только проверяет подключение (my_adapter.py)
В папке бота (там, где README.md и tools) создайте файл с именем my_adapter.py, вставьте в него код ниже без изменений и сохраните. Этот файл не использует никакой движок и просто отвечает первой допустимой клеткой. Он нужен, чтобы убедиться, что подключение работает, прежде чем вы подключите свой движок.
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)
Проверка ввода и вывода перед подключением
Команда в блоке 1 передаёт пример позиции прямо в адаптер. Она должна вывести одну строку: {"move": 19}. Когда эта строка появится, команда в блоке 2 запустит программу подключения с этим адаптером в окне, где задан токен из шага 6. Выполняйте два блока по отдельности. Если ваш движок сам читает этот формат JSON, его команду можно указать сразу после --engine.
1. Проверка одного адаптера
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. Запуск программы подключения с адаптером
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'Шаг 2: переход на файл, который вызывает ваш движок
Когда шаг 1 заработает, замените всё содержимое my_adapter.py примером A или B ниже. Меняйте только строки с пометкой EDIT, остальное оставьте как есть. Затем снова выполните тестовую команду 1 выше и убедитесь, что появляется одна строка ответа. Ход выбрал ваш движок, поэтому клетка, отличная от 19, считается нормой.
Пример A: движок, который выбирает ход функцией Python
В этом примере предполагается, что есть файл my_engine.py с функцией search, которая выбирает ход. В месте EDIT 1 измените имя файла и имя функции, а в месте EDIT 2 измените передаваемые значения, чтобы они подходили вашему движку. Функция должна возвращать номер клетки (от 0 до 63) или координату, например 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)Пример B: движок в виде файла программы
В этом примере предполагается, что программа читает одну строку с 64 символами доски, стороной, которая ходит, и временем на обдумывание в миллисекундах и отвечает одной строкой с координатой, например D3 (. = пусто, X = чёрные, O = белые). У каждого движка свой формат, поэтому измените имя файла программы в месте EDIT 1, строку, которая отправляется движку, в месте EDIT 2 и часть, которая читает ответ, в месте EDIT 3, чтобы всё подходило вашему движку.
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)Время на обдумывание: в партиях ботов каждому боту даётся 1 минута на каждую партию, поэтому долгое обдумывание одного хода быстро приводит к проигрышу по времени. think_ms в примерах делит оставшееся время (remaining_ms) на число оставшихся у вас ходов и так определяет время на этот ход. budget_ms не означает время, которое нужно потратить на этот ход. Это наибольшее время, которое программа подключения ждёт ответа (по умолчанию 10 секунд). Если ответ придёт позже, бот не сможет продолжить эту партию и проиграет её по времени, поэтому передавайте своему движку think_ms, а не budget_ms.
Если что-то сломалось: когда вызов движка завершается ошибкой, файлы примеров записывают причину в my_adapter.log и вместо этого играют первую допустимую клетку. Если ходы выглядят странно, сначала откройте этот файл. Программа подключения не показывает на экране сообщения об ошибках адаптера. Используйте print только для одной строки ответа, а любой отладочный текст записывайте в файл.
Скорость: программа подключения держит my_adapter.py запущенным для каждой партии и отправляет по одной строке за ход. Файл, который отвечает одной строкой и завершается, как в примерах, запускается снова на следующем ходе, поэтому и ваш движок каждый раз запускается заново. Если движок долго запускается или должен сохранять работу прошлых ходов, сделайте из my_adapter.py цикл: прочитать строку, ответить строкой, повторить. Не читайте ввод до конца и делайте flush после каждого ответа.
Остальная часть этого раздела служит справочным материалом о том, чем обмениваются программа подключения и my_adapter.py. Если вы используете файлы примеров выше без изменений, читать её не нужно.
Входной JSON (коннектор → адаптер)
Это пример данных от коннектора, а не исполняемый код. Не сохраняйте его как файл Python. Во время партии коннектор автоматически отправляет актуальные данные на каждом ходу.
Ниже пример стандартной начальной позиции 8×8. В настоящих партиях программа подключения подставляет текущую позицию. board перечисляет клетки от a1 вправо, затем следующий ряд; 0=пусто, 1=чёрные, 2=белые. color=1 запрашивает ход чёрных. legal_moves перечисляет клетки, на которые можно сходить. remaining_ms означает оставшееся время на часах партии, а budget_ms означает наибольшее время, которое программа подключения ждёт ответа на этот ход, оба в миллисекундах (1000 = 1 секунда). Поля и примеры для вариантов игры приведены ниже в разделе о вариантах.
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Ответ JSON (адаптер → коннектор)
Запишите один из ответов ниже одной строкой JSON с переводом строки в конце. move задаётся номером клетки или координатой, регистр не важен. На доске 8×8 d3 равна (3−1)×8+3 = 19. Всегда выбирайте из полученного legal_moves. В парах ботов при отсутствии допустимого хода отправьте -1 или pass; в играх с людьми вынужденные пасы обрабатывает сервер. Пишите в stdout только строку ответа, без других сообщений.
{"move":19}{"move":"d3"}{"move":-1}Примеры запросов к движку по играм
Примеры первого запроса за чёрных, который программа подключения отправляет движку --engine, сгенерированы по коду правил. Настоящие партии всех вариантов начинаются со случайной позиции с 12 фишками, поэтому board и legal_moves в каждой партии разные. Чтобы примеры не менялись, их начальные ходы выбраны ближе к центру доски. match_id и значения часов в настоящих партиях тоже отличаются.
game_mode: Одно из std, anti, grand, octagon, reversi. XOT в играх с людьми приходит как std.board_size: 8, или 10 для grand и octagon.board: Массив длиной board_size². Читается от a1 вправо, затем следующий ряд. 0=пусто (включая заблокированные клетки Octagon), 1=чёрные, 2=белые.blocked_squares: Неиспользуемые клетки Octagon. В других играх пустой массив.placed: Сколько фишек уже поставил каждый цвет, включая случайные начальные ходы; ключами служат строки "1" и "2". Используется для лимита в 32 фишки в классическом Reversi.
Anti (anti)
Входной JSON (коннектор → адаптер)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [2, 3, 12, 25, 42, 44, 45],
"game_mode": "anti",
"board_size": 8,
"blocked_squares": [],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}Ответ JSON (адаптер → коннектор)
{"move":2}Grand (10x10) (grand)
Входной JSON (коннектор → адаптер)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [13, 14, 25, 42, 63, 65, 66],
"game_mode": "grand",
"board_size": 10,
"blocked_squares": [],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}Ответ JSON (адаптер → коннектор)
{"move":13}Octagon (octagon)
Входной JSON (коннектор → адаптер)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [13, 14, 25, 42, 63, 65, 66],
"game_mode": "octagon",
"board_size": 10,
"blocked_squares": [0, 1, 10, 8, 9, 19, 80, 90, 91, 89, 98, 99],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}Ответ JSON (адаптер → коннектор)
{"move":13}Классическое реверси (reversi)
Входной JSON (коннектор → адаптер)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [18, 25, 33, 41, 42, 50, 51],
"game_mode": "reversi",
"board_size": 8,
"blocked_squares": [],
"placed": {
"1": 6,
"2": 6
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}Ответ JSON (адаптер → коннектор)
{"move":18}Частые проблемы
| Признак | Причина и решение |
|---|---|
| У бота серая точка (не в сети). | Проверьте, что окно программы подключения всё ещё работает. Она останавливается, если закрыть окно, нажать Ctrl+C или если компьютер уходит в спящий режим. Повторите шаги 6 и 7, затем нажмите Обновить. |
Появляется сообщение, что команда не найдена.command not found / is not recognized | Python не установлен, или после установки окно не открыли заново. См. шаг 5, откройте новое окно и начните снова с шага 4. |
Появляется сообщение, что файл не удаётся открыть.can't open file | Команда запущена не из папки бота. Перейдите в папку бота, как в шаге 4, и проверьте командой ls, что в списке есть README.md и tools. |
Появляется сообщение, что токена нет, и программа завершается.FS_BOT_TOKEN is missing or invalid | В этом окне нет токена. Если вы открыли новое окно, повторите шаг 6. |
Появляется сообщение, что токен отклонён, и программа останавливается.Bot credential rejected | Токен вставлен неправильно или устарел. После выпуска нового токена или отключения бота старый токен перестаёт работать. Начните снова с шага 6 с последним токеном. |
Постоянно появляется одна и та же строка ошибки.Connection/state error | Проверьте подключение к интернету. В macOS проверьте, что вы запустили Install Certificates.command из шага 5. Если ошибка появляется только во время игр, сервер, возможно, отклоняет ход вашего движка как недопустимый. Программа подключения отправляет тот же ход снова, поэтому движок нужно исправить. |
| Кнопка вызова не работает, или вызов не принимается. | У обоих ботов должна быть зелёная точка. Проверьте, что ваш бот выбран в Мой играющий бот. Бот, который уже играет другую игру, не принимает новый вызов. Непринятый вызов истекает через 120 секунд. |
Бот не делает ход, а затем проигрывает по времени, или игра прерывается.Engine failed for match | Движок не ответил вовремя, или его ответ был в неверном формате. Проверьте команду движка и его однострочный вывод JSON. Если бот не сделает первый ход в течение 30 секунд после начала, игра прерывается. |
| Не приходят вызовы на варианты игры. | В окне вызова показаны только игры, которые поддерживают оба бота. Проверьте, что игра отмечена в Поддерживаемые игры вашего бота и что другой бот тоже её поддерживает. У бота, подключённого через NBoard, должен быть отмечен только Стандарт. |
Словарь терминов
| Термин | Значение |
|---|---|
| API | Согласованный способ, которым программы отправляют запросы и ответы. Программа подключения берёт это на себя. |
| Токен | Пароль только для вашего бота. С его помощью программа подключения доказывает серверу, какой это бот. |
| Терминал | Окно, в котором вводят команды. В Windows используйте PowerShell. |
| Переменная окружения | Временная настройка, которая передаётся программам, запущенным из одного окна терминала. Она исчезает при закрытии окна. |
| JSON | Формат записи данных в виде текста. Пример: {"move":19} |
| Движок | Программа, которая смотрит на позицию и выбирает ход. |
| Адаптер | Небольшая программа, которая преобразует данные между вашим движком и программой подключения. |
| NBoard | NBoard — графическая программа для Othello, которую можно использовать с NTest. Протокол NBoard — её открытый формат связи с движками. Он работает с поддерживающими его движками, например NTest и Edax. |
| Допустимый ход | Клетка, на которую можно сходить сейчас. Сервер перечисляет такие клетки в legal_moves. |
Загрузки
Войдите для загрузки. .zip содержит руководство, программу подключения и пример движка; .json: спецификация API, .md: руководство.
Подробная спецификация
Остальная часть страницы содержит подробный справочник для разработчиков, которые работают с движком или API напрямую.
Параметры программы подключения
Параметры tools/bot_match_client.py. Нужно указать ровно один из параметров --engine и --nboard / --edax / --egaroucid. Токен никогда не передаётся параметром; он читается только из переменной окружения FS_BOT_TOKEN. Коды завершения: 0 обычная остановка, 2 токен отклонён, 3 достигнут --max-runtime.
| Параметры | Описание |
|---|---|
--engine "COMMAND" | Запускает эту команду один раз на партию и обменивается одной строкой JSON за ход, не закрывая её ввод. Движок, который отвечает одной строкой и завершается, запускается снова на следующем ходе. |
--nboard "COMMAND" | Держит по одному движку NBoard на партию, всего два. Только стандартная игра (std). |
--edax PATH | Edax также можно подключить, указав только путь к программе через --edax ниже. Отдельный адаптер не нужен: время на ход распределяется по оставшимся часам партии. |
--edax-eval PATH | Путь к eval.dat Edax. Без указания ищет data/eval.dat в папке программы или её родительской папке. |
--egaroucid PATH | Подготовьте Egaroucid for Console. Выберите консольную программу и оставьте папку resources на месте. В macOS и Linux используйте программу, собранную по официальной инструкции. Позиция и оставшееся время передаются командами setboard, settime и go без адаптера. |
--nboard-depth N | Отправляет движку NBoard set depth N. От 0 до 60; 0 сохраняет значение движка по умолчанию. (--edax: 0 → 60). |
--max-think-seconds S | Максимальное время ожидания движка на один ход в секундах. По умолчанию 10, максимум 300. Никогда не больше оставшегося времени на часах минус 0,75 секунды. Это защитное ограничение программы подключения; у сервера нет лимита времени на ход. Значение 0 убирает лимит на ход и использует оставшееся время партии. |
--engine-name NAME | Название движка, которое сообщается в сведениях о боте (не более 120 символов). Не указывайте команды или пути. |
--cpu-name NAME | Название процессора для сведений (не более 120 символов). Если не указано, определяется автоматически. |
--once-match ID | Обрабатывает только этот матч и завершается, когда он закончится (finished, aborted, expired, rejected). Отрицательные ID игр с людьми тоже подходят. Для тестов сочетайте с --max-runtime. |
--max-runtime S | Через указанное число секунд завершает движки и выходит (код завершения 3). |
--no-stream | Не использует поток событий, только опрос. |
--api URL, --stream-url URL | Адреса API и потока. Обычно оставляйте значения по умолчанию. Принимается только HTTPS, а поскольку вместе с запросами отправляется токен, поток должен быть на хосте API. |
Собственная реализация API
Получайте вызовы и позиции через agent_poll или аутентифицированный поток событий. Отправляйте agent_accept для ожидающих вызовов, где вы bot_b, а затем в каждой партии идущего матча рассчитывайте только позиции, где ваш ход. Отправляйте ход не больше одного раза на каждые match_id, партию и revision, а при изменении состояния отбрасывайте устаревшие расчёты. Игры с людьми используют отрицательный match_id и leg=0 и требуют round_no и room_created_at точно в том виде, в каком они пришли. В обычных комнатах вынужденные пасы обрабатывает сервер; пасы отправляют только пары ботов.
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Варианты игры
Бот получает только игры, отмеченные в Поддерживаемые игры. Игру указывает game_mode в запросах к движку и в состоянии матча. Размер доски, заблокированные клетки и допустимые клетки рассчитывает сервер, поэтому движку нужно только выбрать ход из полученного legal_moves. Не генерируйте ходы 10×10 или классического Reversi стандартным генератором ходов 8×8.
| Игра | game_mode | Доска | Начальные фишки | Время (на партию, на цвет) | Первые legal_moves чёрных |
|---|---|---|---|---|---|
| Стандарт | std | 8×8 | Случайные 12 фишек | 60,000 ms | Зависит от позиции |
| Anti | anti | 8×8 | Случайные 12 фишек | 60,000 ms | Зависит от позиции |
| Grand (10x10) | grand | 10×10 | Случайные 12 фишек | 60,000 ms | Зависит от позиции |
| Octagon | octagon | 10×10 (88) | Случайные 12 фишек | 60,000 ms | Зависит от позиции |
| Классическое реверси | reversi | 8×8 | Случайные 12 фишек | 60,000 ms | Зависит от позиции |
Общие правила: первыми ходят чёрные (1). Обе партии начинаются с одной позиции со сменой цветов и идут одновременно. В парах ботов, если у вас нет допустимого хода, legal_moves приходит пустым массивом, и бот должен сам отправить move=-1 (пас). Сервер не пасует за вас, и ваши часы продолжают идти. Пас при наличии допустимого хода недопустим. Партия заканчивается, когда ни один цвет не может ходить. Пустые клетки никому не засчитываются; по обеим партиям суммируются только реальные фишки, а при равных итогах ничья. Все партии ботов начинаются со случайной позиции с 12 фишками. Во всех вариантах каждому боту даётся 1 минута на каждую партию.
Anti: фишки ставятся и переворачиваются как в стандартном Othello. Побеждает сторона, у которой по обеим партиям меньше фишек.
10x10: стандартные правила на доске 10×10 (100 клеток). Начальная расстановка занимает четыре центральные клетки; в парах ботов к ней добавляются 8 случайных ходов, и игра начинается с 12 фишек.
Octagon: та же доска и начальная расстановка, что и в 10x10, но по три клетки в каждом углу, всего 12, использовать нельзя (88 игровых клеток). Заблокированные клетки приходят номерами в blocked_squares и значением 0, таким же, как у пустой клетки, в board. Массив не упорядочен по номерам. На заблокированную клетку ходить нельзя, и линии фишек на ней обрываются.
Классический Reversi: по правилам игра начинается с пустой доски. Пока хотя бы одна из четырёх центральных клеток (27, 28, 35, 36) пуста, ходить можно только туда, и ничего не переворачивается. Когда все четыре заполнены, действуют стандартные правила. Каждый цвет ставит не более 32 фишек, включая первые четыре хода. placed считает фишки, которые каждый цвет уже поставил; ключами служат строки "1" (чёрные) и "2" (белые). Пары ботов начинают после того, как сервер сделал 12 случайных постановок, включая четыре центральных хода, поэтому в первом запросе два значения placed в сумме дают 12 (обычно {"1":6,"2":6}). Цвет, который поставил все 32 фишки, получает пустой legal_moves и на каждом своём ходу отправляет -1.
Примеры запросов к движку по играм
Примеры первого запроса за чёрных, который программа подключения отправляет движку --engine, сгенерированы по коду правил. Настоящие партии всех вариантов начинаются со случайной позиции с 12 фишками, поэтому board и legal_moves в каждой партии разные. Чтобы примеры не менялись, их начальные ходы выбраны ближе к центру доски. match_id и значения часов в настоящих партиях тоже отличаются.
Anti (anti)
{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"anti","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Grand (10x10) (grand)
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"grand","board_size":10,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Octagon (octagon)
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"octagon","board_size":10,"blocked_squares":[0,1,10,8,9,19,80,90,91,89,98,99],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Классическое реверси (reversi)
{"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}Поля запроса к движку
| Поле | Тип | Описание |
|---|---|---|
game_mode | string | Одно из std, anti, grand, octagon, reversi. XOT в играх с людьми приходит как std. |
board_size | integer 8 | 10 | 8, или 10 для grand и octagon. |
board | integer[board_size²] | Массив длиной board_size². Читается от a1 вправо, затем следующий ряд. 0=пусто (включая заблокированные клетки Octagon), 1=чёрные, 2=белые. |
blocked_squares | integer[] | Неиспользуемые клетки Octagon. В других играх пустой массив. |
placed | {"1": integer, "2": integer} | Сколько фишек уже поставил каждый цвет, включая случайные начальные ходы; ключами служат строки "1" и "2". Используется для лимита в 32 фишки в классическом Reversi. |
legal_moves | integer[] | Клетки, на которые можно сходить сейчас. Если список пуст, ответьте move=-1. |
color | integer 1 | 2 | Цвет, который ходит. 1=чёрные, 2=белые. |
budget_ms | integer | Время на этот ход в миллисекундах: меньшее из --max-think-seconds и оставшегося времени на часах минус 0,75 секунды. При превышении программа подключения останавливает движок. |
remaining_ms | integer | Оставшееся время вашего цвета в этой партии в миллисекундах, за вычетом времени, уже потраченного на этом ходу. |
match_id | integer ≠ 0 | ID матча. Для игр с людьми отрицательный (-номер комнаты). |
leg | integer 0 | 1 | Номер партии 0 или 1. В играх с людьми всегда 0. |
revision | integer | Номер состояния этой партии. Отправляйте его без изменений вместе с agent_move. |
history | array | Ходы, сыгранные в этой партии. Решение принимайте по board. |
round_no | integer | null | Номер раунда игры с человеком. null для пар ботов. |
room_created_at | string | null | Время создания комнаты игры с человеком. null для пар ботов. |
Вызов другого бота на вариант игры
Добавьте game_mode в agent_challenge; по умолчанию std. Игра должна быть в supported_games у обоих ботов. Игры соперника смотрите в bots[].supported_games в ответе agent_poll. Если игры нет хотя бы у одного бота, придёт ответ HTTP 409 с ошибкой ниже.
{"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}Изменение supported_games
В форме регистрации изначально отмечен Стандарт, а старые боты без сохранённого списка поддерживают std. Изменить список можно только в Управление на веб-странице ботов, но не токеном бота (API). Изменение отклоняется, пока идёт матч или ожидается принятие вызова. Пустой список сохранить нельзя.
NBoard и варианты игры
Подключение --nboard поддерживает только стандартную игру (std). Когда приходит вариант игры, программа подключения не обращается к движку и пропускает этот ход (сообщение Engine failed for match). Ход не отправляется, поэтому игра прерывается до первого хода, или эта партия позже проигрывается по времени. Указывайте для бота NBoard стандартную игру как единственную поддерживаемую.
Люди против зарегистрированных ботов
Люди могут вызвать вашего бота в любой игре, для которой он зарегистрирован. Если он поддерживает стандартную игру, могут прийти и STD, и XOT. XOT приходит с game_mode std и начинается с доски, на которой уже сыграно 8 ходов (они есть в history). Время выбирает тот, кто вызывает, и игра не влияет на рейтинг. В игре с человеком одна партия (leg 0) и отрицательный match_id. Пасы при отсутствии ходов обрабатывает сервер, поэтому не отправляйте -1. Зарегистрированные онлайн-боты с включённым автоподбором участвуют и в поддерживаемых быстрых матчах STD/XOT. Для участников они рейтинговые, для гостей — нет.
Недопустимые ходы и время
Недопустимый ход или устаревшая revision отклоняются с HTTP 409 (illegal_move, stale_revision, not_your_turn), и доска не меняется. Часы при этом продолжают идти. Программа подключения повторно отправляет тот же ход с той же revision, поэтому, пока движок не исправлен, эта партия в итоге проигрывается по времени. У сервера нет лимита на ход, он проверяет только оставшееся время каждого цвета. 10 секунд соответствуют значению --max-think-seconds программы подключения по умолчанию.
Аутентификация
Используйте HTTPS, JSON POST и Authorization: Bearer. Токен относится к одному боту. Не помещайте его в URL, открытый код или журналы.
Content-Type: application/json
Authorization: Bearer <FS_BOT_TOKEN>POST https://othelloclub.com/flipsight/bot_match_api.php
Правила матча
Все партии ботов начинаются со случайной позиции с 12 фишками. Две партии идут одновременно со сменой цветов. Победителя определяет сумма дисков; в Anti выигрывает меньшая сумма.
Все партии ботов начинаются со случайной позиции с 12 фишками. Во всех вариантах каждому боту даётся 1 минута на каждую партию.
Если бот не сделал первый ход в течение 30 секунд после начала, матч прерывается и не учитывается в рейтинге. Если во время матча связь одного бота потеряна более чем на 30 секунд, а другой подключён, побеждает подключённый бот (finish_reason: disconnect), и результат учитывается в рейтинге.
Ходы ботов FlipSight во всех видах игр рассчитывает сервер. Принимаются вызовы и с сайта, и через API (agent_challenge), а сервер сразу начинает матч, поэтому держать игровую комнату открытой не нужно. Если расчёт на сервере останавливается дольше чем на 30 секунд, матч прерывается (finish_reason: technical) и не влияет на рейтинг. Варианты дорого рассчитывать, поэтому одновременно идёт не больше 2 матчей вариантов с ботами FlipSight. Если мест нет, придёт HTTP 409 с site bot variant matches are full; попробуйте ещё раз чуть позже.
Автоматические матчи ботов: сайт постоянно держит открытыми две комнаты для матчей ботов. Пока зарегистрированный бот с включённым автоподбором в сети и не играет, ему приходит вызов на обычный (std) матч ботов против бота с похожим рейтингом (сначала другого зарегистрированного бота, иначе бота FlipSight), в роли bot_b. Программа подключения принимает вызов автоматически, и матч начинается. Если бот не примет вызов за 120 секунд или отклонит его, он на 10 минут исключается из автоподбора. Автоматические матчи рейтинговые. Автоматические матчи ботов продолжаются и во время партий Арены. Автоподбор включён по умолчанию, отключить его можно в разделе Управление на странице ботов. Бот, запущенный с тренировочным движком (environment.practice равно true), исключается из автоматических матчей, а его матчи с другими ботами в это время не рейтинговые.
Прямой вызов от человека — одна партия в обычной комнате с поддерживаемой игрой и временем. Сервер всегда делает прямые вызовы нерейтинговыми. Зарегистрированные онлайн-боты с включённым автоподбором участвуют и в поддерживаемых быстрых матчах STD/XOT. Для участников они рейтинговые, для гостей — нет. Зарегистрированный бот принимает только столько партий, сколько позволяет настройка одновременных партий (по умолчанию 2). Партия с человеком и матч ботов (две доски) считаются каждая за 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)Ход матча
Все партии ботов начинаются со случайной позиции с 12 фишками. Две партии идут одновременно со сменой цветов. Победителя определяет сумма дисков; в Anti выигрывает меньшая сумма.
Ваш номер участника 0, если вы bot_a, и 1, если вы bot_b. Участник 0 играет чёрными (1) в legs[0] и белыми (2) в legs[1]; участник 1 наоборот. Ваш ход, когда leg.status равен active, а leg.turn совпадает с вашим цветом. Ход может быть вашим в обеих партиях одновременно.
Если у вас нет допустимого хода, вы должны сами отправить move -1 (пас); сервер не пасует за вас. В играх с человеком сервер обрабатывает пасы так же, как в обычных игровых комнатах.
В ваш ход оставшееся время = clocks_ms[ваш цвет - 1] - (server_now_ms - turn_started_ms). clocks_ms идёт в порядке цветов (чёрные, белые); totals и winner идут в порядке участников (winner 0 или 1, null при ничьей).
Номера клеток
Номер клетки = строка × 8 + столбец, то есть a1=0, h1=7, a2=8 и h8=63. Например, 19 это d3, а 37 это f5. Массив board использует тот же порядок: 0=пусто, 1=чёрные, 2=белые.
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На доске 10×10 (grand, octagon) клетка = ряд × 10 + столбец. Столбцы обозначаются от a до j, а десятый ряд в координатах записывается как 10 или 0. a1=0, j1=9, a2=10, j10=99 (то же, что j0). Одна и та же координата имеет разный номер на досках разного размера: d3 равна 19 на 8×8 и 23 на 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 ## ##Поток событий
Подключитесь с тем же токеном Bearer к GET https://othelloclub.com/flipsight_api/bot-stream, чтобы получать по одному событию JSON на строку (NDJSON): hello подтверждает подключение, match означает, что изменился один из ваших матчей (события матчей между ботами содержат полный объект матча), а ping каждые 10 секунд поддерживает соединение. Получив match, один раз вызовите agent_poll. При обрыве переподключайтесь с задержкой, растущей от 1 до 30 секунд, и тем временем продолжайте опрос. Пока поток подключён, бот отображается в сети; каждый бот может открыть до 3 потоков.
Примеры событий
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}Адаптер движка
Программа подключения ботов FlipSight (Python 3.9+, только стандартная библиотека) работает в Windows, macOS и Linux и подключает движок двумя способами. --engine запускает движок один раз на партию, на каждом ходе отправляет одну строку JSON и читает одну строку {"move": клетка}. Движок, который отвечает одной строкой и завершается, продолжит работать, потому что на следующем ходе запускается снова, но движок, читающий ввод до конца (sys.stdin.read() и т. п.), никогда не ответит, поэтому читайте по одной строке. legal_moves в запросе перечисляет допустимые клетки; если список пуст, отвечайте -1. --nboard держит запущенным движок NBoard, по одному на партию; глубина задаётся через --nboard-depth.
Edax также можно подключить, указав только путь к программе через --edax ниже. Отдельный адаптер не нужен: время на ход распределяется по оставшимся часам партии.
Подготовьте Egaroucid for Console. Выберите консольную программу и оставьте папку resources на месте. В macOS и Linux используйте программу, собранную по официальной инструкции. Позиция и оставшееся время передаются командами setboard, settime и go без адаптера.
Входной JSON (коннектор → адаптер)
Это пример данных от коннектора, а не исполняемый код. Не сохраняйте его как файл Python. Во время партии коннектор автоматически отправляет актуальные данные на каждом ходу.
{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":40000,"match_id":123,"leg":1,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Ответ JSON (адаптер → коннектор)
{"move":2}Синхронизация
При подключённом потоке вызывайте agent_poll при поступлении события и каждые 10 секунд. Без потока опрашивайте каждую секунду во время матча и каждые 5 секунд в ожидании, но не чаще раза в секунду. Сетевое время расходует ваши часы. Если ответ на ход потерян, получите новое состояние и повторите тот же ход, только если revision не изменилась.
Действия API
Примеры ответов показывают формат. Кнопка копирования копирует только запрос и не вызывает API.
POST agent_poll
Получить состояние
| Параметры | Тип | Обязательно | Описание |
|---|---|---|---|
action | string "agent_poll" | Обязательно | Имя вызываемого действия |
environment | object | Необязательно | CPU, ОС и имя движка на компьютере с программой подключения (каждое до 120 символов, необязательно) и practice (true или false, необязательно). practice, равное true, означает, что бот тренируется: он исключается из автоматических матчей, а матчи ботов, запрошенные или принятые в это время, не рейтинговые. Программа подключения отправляет это значение сама, когда запущена с тренировочным движком. Сервер запоминает последнее значение, поэтому после тренировки отправьте его без practice или с false. |
Пример запроса
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"}}'Пример ответа
{
"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
}Объект матча (matches[])
| Поле | Тип | Описание |
|---|---|---|
id | integer | ID матча (отрицательный для игр с человеком) |
kind | string | human для игр с человеком |
bot_a | integer | ID бота участника 0 (0 для человека) |
bot_b | integer | ID бота участника 1 (0 для человека) |
game_mode | string | Игра (std, anti, grand, octagon, reversi) |
time_limit_sec | integer | Секунды на партию для каждого цвета |
status | string | pending, running, finished, aborted, rejected, expired |
expires_ms | integer | Срок принятия (миллисекунды) |
ranked | boolean | Учитывается ли матч в рейтинге |
state.status | string | active, finished или aborted |
state.game_mode | string | Игра (std, anti, grand, octagon, reversi) |
state.board_size | integer 8 | 10 | Клеток на сторону, 8 или 10 |
state.blocked_squares | integer[] | Неиспользуемые клетки (только Octagon, не отсортированы) |
state.legs | array | Партии (2 между ботами, 1 с человеком) |
state.totals | [integer, integer] | Сумма фишек за обе партии по участникам |
state.winner | integer | null | Победивший участник 0 или 1, null при ничьей |
state.finish_reason | string | null | discs, timeout, double_timeout, disconnect, no_first_move |
Объект партии (state.legs[])
| Поле | Тип | Описание |
|---|---|---|
board | integer[64 | 100] | 64 / 100 клетки, 0=пусто 1=чёрные 2=белые |
turn | integer | Цвет, который ходит: 1 (чёрные) или 2 (белые) |
legal_moves | integer[] | Допустимые клетки для цвета, который ходит. Пусто для завершённых партий |
placed | {"1": integer, "2": integer} | Поставленные фишки по цветам, ключи "1" (чёрные) и "2" (белые) |
black_participant | integer | Участник, играющий чёрными в этой партии |
clocks_ms | [number, number] | Базовые значения оставшегося времени в порядке цветов (чёрные, белые) |
turn_started_ms | integer | Время сервера на начало текущего хода |
revision | integer | Увеличивается на 1 при каждом изменении этой партии |
status | string | active, finished, timeout, stopped |
history | array | Сделанные ходы (color, participant, move, at_ms) |
discs | [integer, integer] | Число фишек в порядке цветов (чёрные, белые) |
result_discs | [integer, integer] (optional) | Необязательный присуждённый счёт [чёрные, белые] для партии, проигранной по времени; итог считается по нему, а не по фишкам. Счёт стороны, у которой истекло время, и её соперника: std и reversi от 0 до 64, grand от 0 до 100, octagon от 0 до 88, anti от 64 до 0. Другая партия продолжается, а победителя определяют итоги после окончания обеих партий. Ранее завершённые записи не пересчитываются. |
POST agent_challenge
Вызвать
Укажите target_id (номер бота) или target_name (имя бота); их можно найти в списке bots в ответе agent_poll. Встроенным ботам FlipSight (is_builtin: true) можно бросить вызов через API во всех видах игр, и сервер сразу начинает матч. Если уже идут 2 матча вариантов со встроенными ботами, придёт HTTP 409 с site bot variant matches are full.
| Параметры | Тип | Обязательно | Описание |
|---|---|---|---|
action | string "agent_challenge" | Обязательно | Имя вызываемого действия |
target_id | integer ≥ 1 | Необязательно | ID вызываемого бота |
target_name | string | Необязательно | Имя вызываемого бота (вместо target_id) |
game_mode | string std | anti | octagon | grand | reversi | Необязательно | Игра для пары ботов. По умолчанию std. Оба бота должны её поддерживать. Стандарт / Anti / Octagon / Grand (10x10) / Классическое реверси |
Пример запроса
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"}'Пример ответа
{
"ok": true,
"match_id": 123,
"server_now_ms": 1800000000000
}POST agent_accept
Принять
| Параметры | Тип | Обязательно | Описание |
|---|---|---|---|
action | string "agent_accept" | Обязательно | Имя вызываемого действия |
match_id | integer ≥ 1 | Обязательно | ID матча (отрицательный для игр с человеком) |
Пример запроса
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}'Пример ответа
{
"ok": true,
"match": {
"id": 123,
"status": "running",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}POST agent_reject
Отклонить
| Параметры | Тип | Обязательно | Описание |
|---|---|---|---|
action | string "agent_reject" | Обязательно | Имя вызываемого действия |
match_id | integer ≥ 1 | Обязательно | ID матча (отрицательный для игр с человеком) |
Пример запроса
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}'Пример ответа
{
"ok": true,
"match": {
"id": 123,
"status": "rejected",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}POST agent_move
Отправить ход
| Параметры | Тип | Обязательно | Описание |
|---|---|---|---|
action | string "agent_move" | Обязательно | Имя вызываемого действия |
match_id | integer ≠ 0 | Обязательно | ID матча (отрицательный для игр с человеком) |
round_no | integer ≥ 0 | Необязательно | Только для игр с человеком: round_no из объекта матча |
room_created_at | string | Необязательно | Только для игр с человеком: room_created_at из объекта матча |
leg | integer 0 | 1 | Обязательно | Партия 0 или 1 (0 для игр с человеком) |
revision | integer ≥ 0 | Обязательно | Revision партии, для которой рассчитан ход; 409 stale_revision, если партия изменилась |
move | integer -1..99 | string | Обязательно | Клетка 0-(board_size²−1), -1 для паса / d3 / D3 / pass |
Пример запроса
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}'Пример ответа
{
"ok": true,
"match": {
"id": 123,
"status": "running",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}Ошибки
Ошибки имеют вид {"ok": false, "error": "…"}. При 401 остановитесь и проверьте токен. При 409 получите текущее состояние и проверьте очередь хода, revision и допустимые ходы. 429 означает слишком много потоков у одного бота. Ошибки соединения и 503 повторяйте с растущей задержкой и никогда не отправляйте повторно недопустимый ход.
{"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 |