Документация API

Пошаговое руководство для новичков

Что понадобится

  • Учётная запись 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.

ls

5. Проверьте Python

В том же окне скопируйте одну команду для своей системы, вставьте её и нажмите Enter. Можно нажать кнопку копирования в правом верхнем углу каждого блока кода.

macOS / Linux

python3 --version

Windows 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.

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_TOKEN

Windows 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.py

Windows 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 Minimized

10. Переход на свой движок

После проверки соединения переключитесь на свой движок: специальные параметры ниже для 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 --install
tar -xzf edax-reversi-4.6.tar.gz
mv edax-reversi-4.6 edax-4.6
mkdir -p edax-4.6/bin
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6 data/eval.dat
(cd edax-4.6/src && make build ARCH=native OS=osx)

Запуск

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 без адаптера.

Egaroucid for Console

Windows PowerShell
py tools/bot_match_client.py --egaroucid "egaroucid\Egaroucid_for_console.exe"
macOS / Linux
python3 tools/bot_match_client.py --egaroucid "egaroucid/bin/Egaroucid_for_Console.out"

Оба режима поддерживают только обычную игру 8×8, по одному движку на каждую одновременную партию. --max-think-seconds — защитный лимит на ход (по умолчанию 10 секунд). Если Egaroucid достигает его, поиск останавливается, делается допустимый запасной ход, а на следующем ходу движок перезапускается.

Самописный движок: подключение через JSON

Читайте эту часть, только если хотите подключить движок, который написали сами. Если вы используете Edax, Egaroucid или другой движок NBoard, у вас уже всё готово выше, и эту часть можно пропустить.

Каждый раз, когда наступает ход вашего бота, программа подключения один раз запускает my_adapter.py. Она передаёт текущую позицию одной строкой (где стоят фишки, чей ход, какие клетки допустимы, сколько осталось времени), my_adapter.py отвечает одной клеткой в одной строке, и программа подключения делает этот ход на сервере. my_adapter.py представляет собой небольшой файл на Python, который стоит посередине и запрашивает ход у вашего движка. Соединением с сервером, токеном и отправкой ходов занимается программа подключения, поэтому этот файл с ними никогда не работает.

FlipSight→ bot_match_client.py→ my_adapter.py
FlipSight← bot_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.py

Windows PowerShell

'{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | py my_adapter.py

2. Запуск программы подключения с адаптером

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 1777

my_engine → my_adapter.py

D3
import json
import subprocess
import sys
from pathlib import Path

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


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


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


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

Время на обдумывание: в партиях ботов каждому боту даётся 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}
ДвижокПрограмма, которая смотрит на позицию и выбирает ход.
АдаптерНебольшая программа, которая преобразует данные между вашим движком и программой подключения.
NBoardNBoard — графическая программа для Othello, которую можно использовать с NTest. Протокол NBoard — её открытый формат связи с движками. Он работает с поддерживающими его движками, например NTest и Edax.
Допустимый ходКлетка, на которую можно сходить сейчас. Сервер перечисляет такие клетки в legal_moves.

Загрузки

Скачать примеры Python

OpenAPI 3.1 JSON

README.md

Войдите для загрузки. .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 PATHEdax также можно подключить, указав только путь к программе через --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 чёрных
Стандартstd8×8Случайные 12 фишек60,000 msЗависит от позиции
Antianti8×8Случайные 12 фишек60,000 msЗависит от позиции
Grand (10x10)grand10×10Случайные 12 фишек60,000 msЗависит от позиции
Octagonoctagon10×10 (88)Случайные 12 фишек60,000 msЗависит от позиции
Классическое реверсиreversi8×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_modestringОдно из std, anti, grand, octagon, reversi. XOT в играх с людьми приходит как std.
board_sizeinteger 8 | 108, или 10 для grand и octagon.
boardinteger[board_size²]Массив длиной board_size². Читается от a1 вправо, затем следующий ряд. 0=пусто (включая заблокированные клетки Octagon), 1=чёрные, 2=белые.
blocked_squaresinteger[]Неиспользуемые клетки Octagon. В других играх пустой массив.
placed{"1": integer, "2": integer}Сколько фишек уже поставил каждый цвет, включая случайные начальные ходы; ключами служат строки "1" и "2". Используется для лимита в 32 фишки в классическом Reversi.
legal_movesinteger[]Клетки, на которые можно сходить сейчас. Если список пуст, ответьте move=-1.
colorinteger 1 | 2Цвет, который ходит. 1=чёрные, 2=белые.
budget_msintegerВремя на этот ход в миллисекундах: меньшее из --max-think-seconds и оставшегося времени на часах минус 0,75 секунды. При превышении программа подключения останавливает движок.
remaining_msintegerОставшееся время вашего цвета в этой партии в миллисекундах, за вычетом времени, уже потраченного на этом ходу.
match_idinteger ≠ 0ID матча. Для игр с людьми отрицательный (-номер комнаты).
leginteger 0 | 1Номер партии 0 или 1. В играх с людьми всегда 0.
revisionintegerНомер состояния этой партии. Отправляйте его без изменений вместе с agent_move.
historyarrayХоды, сыгранные в этой партии. Решение принимайте по board.
round_nointeger | nullНомер раунда игры с человеком. null для пар ботов.
room_created_atstring | 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 99

Octagon (octagon)

     a  b  c  d  e  f  g  h  i  j
  1  ## ##  2  3  4  5  6  7 ## ##
  2  ## 11 12 13 14 15 16 17 18 ##
  3  20 21 22 23 24 25 26 27 28 29
  4  30 31 32 33 34 35 36 37 38 39
  5  40 41 42 43 44 45 46 47 48 49
  6  50 51 52 53 54 55 56 57 58 59
  7  60 61 62 63 64 65 66 67 68 69
  8  70 71 72 73 74 75 76 77 78 79
  9  ## 81 82 83 84 85 86 87 88 ##
 10  ## ## 92 93 94 95 96 97 ## ##

Поток событий

Подключитесь с тем же токеном 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

Получить состояние

ПараметрыТипОбязательноОписание
actionstring "agent_poll"ОбязательноИмя вызываемого действия
environmentobjectНеобязательно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[])

ПолеТипОписание
idintegerID матча (отрицательный для игр с человеком)
kindstringhuman для игр с человеком
bot_aintegerID бота участника 0 (0 для человека)
bot_bintegerID бота участника 1 (0 для человека)
game_modestringИгра (std, anti, grand, octagon, reversi)
time_limit_secintegerСекунды на партию для каждого цвета
statusstringpending, running, finished, aborted, rejected, expired
expires_msintegerСрок принятия (миллисекунды)
rankedbooleanУчитывается ли матч в рейтинге
state.statusstringactive, finished или aborted
state.game_modestringИгра (std, anti, grand, octagon, reversi)
state.board_sizeinteger 8 | 10Клеток на сторону, 8 или 10
state.blocked_squaresinteger[]Неиспользуемые клетки (только Octagon, не отсортированы)
state.legsarrayПартии (2 между ботами, 1 с человеком)
state.totals[integer, integer]Сумма фишек за обе партии по участникам
state.winnerinteger | nullПобедивший участник 0 или 1, null при ничьей
state.finish_reasonstring | nulldiscs, timeout, double_timeout, disconnect, no_first_move

Объект партии (state.legs[])

ПолеТипОписание
boardinteger[64 | 100]64 / 100 клетки, 0=пусто 1=чёрные 2=белые
turnintegerЦвет, который ходит: 1 (чёрные) или 2 (белые)
legal_movesinteger[]Допустимые клетки для цвета, который ходит. Пусто для завершённых партий
placed{"1": integer, "2": integer}Поставленные фишки по цветам, ключи "1" (чёрные) и "2" (белые)
black_participantintegerУчастник, играющий чёрными в этой партии
clocks_ms[number, number]Базовые значения оставшегося времени в порядке цветов (чёрные, белые)
turn_started_msintegerВремя сервера на начало текущего хода
revisionintegerУвеличивается на 1 при каждом изменении этой партии
statusstringactive, finished, timeout, stopped
historyarrayСделанные ходы (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.

Вызов со страницы ботов

ПараметрыТипОбязательноОписание
actionstring "agent_challenge"ОбязательноИмя вызываемого действия
target_idinteger ≥ 1НеобязательноID вызываемого бота
target_namestringНеобязательноИмя вызываемого бота (вместо target_id)
game_modestring 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

Принять

ПараметрыТипОбязательноОписание
actionstring "agent_accept"ОбязательноИмя вызываемого действия
match_idinteger ≥ 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

Отклонить

ПараметрыТипОбязательноОписание
actionstring "agent_reject"ОбязательноИмя вызываемого действия
match_idinteger ≥ 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

Отправить ход

ПараметрыТипОбязательноОписание
actionstring "agent_move"ОбязательноИмя вызываемого действия
match_idinteger ≠ 0ОбязательноID матча (отрицательный для игр с человеком)
round_nointeger ≥ 0НеобязательноТолько для игр с человеком: round_no из объекта матча
room_created_atstringНеобязательноТолько для игр с человеком: room_created_at из объекта матча
leginteger 0 | 1ОбязательноПартия 0 или 1 (0 для игр с человеком)
revisioninteger ≥ 0ОбязательноRevision партии, для которой рассчитан ход; 409 stale_revision, если партия изменилась
moveinteger -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}
HTTPerror
400JSON object required / unknown action
401invalid bot credential
403access denied / invalid csrf
405POST required
409stale_revision / not_your_turn / illegal_move / leg_finished / match not running / match timed out / stale room revision / invalid move coordinate / move outside board / unsupported game mode / both bots must be connected / bot already has an active match / bot busy / challenge expired or answered / site bot variant matches are full / target bot not found
413request too large
429too many streams (bot-stream)
503bot match storage unavailable
Copyright © Othelloclubthe.othelloclub.com@gmail.com All rights reserved.