新手逐步指南
需要準備的東西
- 一個 FlipSight 帳號。登入後才能註冊機器人和下載連接器。
- 一張機器人照片:GIF、JPG 或 PNG 檔案,不超過 5 MB,寬和高都至少 175 px。
- 一台用於執行機器人的個人電腦 機器人的著法在執行連線程式的你的電腦上計算。即使是同一個引擎,強度也會因電腦效能與引擎設定而不同;更換電腦或設定後,等級分也可能隨之變化。
- Python 3,一個用來執行連接器的免費程式。3.9 或更新版本可用,建議 3.11 或更新版本。會在第 5 步檢查。
- 自己的機器人引擎。連線程式附帶用於測試的練習引擎。
1. 註冊機器人
登入後開啟機器人頁面(othelloclub.com/zh-tw/flipsight/bot)。在最上方的註冊我的機器人區塊,在機器人名稱(2~24個字元)中輸入名稱。支援的遊戲暫時保持只勾選標準。在上傳機器人照片中選擇圖片,然後按下註冊我的機器人按鈕。
成功時會這樣:出現連線權杖(僅顯示一次)框,裡面是一段以 fsbm_ 開頭的長文字。
2. 保存權杖
這段長文字就是權杖,是你的機器人專用的密碼。連接器用它向伺服器證明自己代表這個機器人。請全部複製,暫時貼到文字編輯器等地方。關閉這個框後,就無法再看到同一個權杖。
請像對待密碼一樣對待權杖。不要給任何人看,也不要張貼它的螢幕截圖。如果遺失或被別人看到,請在機器人列表中開啟管理,按下產生連線權杖。拿到新權杖後,舊權杖會立即失效。
3. 下載連接器
按下下載Python範例下載 ZIP 檔案並解壓縮。本指南把解壓縮出的資料夾稱為「機器人資料夾」。請把它放在容易找到的地方,例如桌面。
成功時會這樣:機器人資料夾裡有 README.md 檔案和 tools 資料夾。
4. 在機器人資料夾中開啟終端機
終端機是用來輸入指令的視窗。請依你的系統用下面的方法開啟。
macOS:按 Command+Space,輸入 Terminal 並開啟它。輸入 cd 和一個空格,把機器人資料夾拖進視窗,然後按 Enter。
Linux:在檔案管理員中對機器人資料夾按右鍵,選擇「在終端機中開啟」。不同發行版的選單名稱可能不同。如果沒有這個選單,就開啟終端機,像 macOS 那樣用 cd 進入資料夾。
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:從 python.org 下載 macOS 安裝程式(.pkg)並安裝。然後在「應用程式」裡的 Python 資料夾中執行一次 Install Certificates.command。這會準備安全連線(HTTPS)所需的憑證。
Linux:用發行版的軟體管理工具或套件管理員安裝 python3。
Windows:從 python.org 安裝 Python install manager(Python 安裝管理工具)。
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 內建的 Windows PowerShell 5.1 和較新的 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的引擎使用--nboard,自製引擎使用JSON。權杖無需更換。
只有當你的引擎懂得某種變體遊戲(反向、10x10、八角、經典黑白棋)的規則時,才在支援的遊戲中勾選它。其他機器人和玩家可以用你勾選的所有遊戲向你的機器人發起挑戰。要修改時,在機器人列表中開啟管理,修改支援的遊戲後按下儲存。有進行中的對局或等待接受的挑戰時無法修改。
連接Edax等支援NBoard的引擎
NBoard是一款可搭配NTest使用的黑白棋圖形介面程式。NBoard協定是該程式與引擎通訊的公開格式,適用於NTest、Edax等支援該協定的引擎。
以下安裝與啟動範例以 Edax 4.6 和 Egaroucid for Console 7.8.2 為例。
連線程式不包含Edax 4.6。請從Edax 4.6的Assets下載適合作業系統的檔案,放入機器人資料夾,再用下面的準備指令解壓縮。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命令列工具,下面的準備指令會編譯原始碼。
準備(僅限第一次)
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僅指定執行檔路徑來連線。不需要另寫轉接程式;連線程式會依對局剩餘時間分配每步思考時間。
將命令中的路徑換成自己執行檔的完整路徑。路徑包含空白時以雙引號括起來。如果無法自動找到評估檔,請用--edax-eval指定eval.dat路徑。
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}。看到這一行後,在已設定第 6 步權杖的視窗中,用方塊 2 的指令以這個配接器啟動連接器。請分別執行這兩個方塊。如果你的引擎本身能讀取這種 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;grand 和 octagon 為 10。board: 長度為 board_size² 的陣列。從 a1 開始向右讀,然後是下一列。0=空(包括八角的禁用格),1=黑,2=白。blocked_squares: 八角中不能使用的格子。其他遊戲中為空陣列。placed: 每種顏色到目前為止已下的子數(包括開局的隨機步);鍵是字串 "1" 和 "2"。用於經典黑白棋的 32 子上限。
反向 (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}10x10 (Grand) (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 上,確認已經執行了第 5 步中的 Install Certificates.command。如果只在對局中出現,可能是伺服器把你的引擎的著法判為非法而拒絕了。連接器會再次送出同一著,所以需要修正引擎。 |
| 挑戰按鈕沒有反應,或挑戰沒有被接受。 | 雙方機器人都必須是綠色圓點。確認在我的參賽機器人中選擇了你的機器人。已經在進行其他對局的機器人不會接受新的挑戰。沒有被接受的挑戰會在 120 秒後過期。 |
機器人不下子,然後逾時判負或對局被中止。Engine failed for match | 引擎沒有及時回答,或回答的格式不對。請檢查引擎指令和它輸出的一行 JSON。如果機器人在開始後 30 秒內沒有下出第一步,對局會被中止。 |
| 收不到變體遊戲的挑戰。 | 挑戰視窗只列出雙方機器人都支援的遊戲。請確認你的機器人的支援的遊戲中勾選了該遊戲,而且對方機器人也支援。透過 NBoard 連線的機器人應只勾選標準。 |
名詞解釋
| 名詞 | 意思 |
|---|---|
| API | 程式之間約定好的傳送請求和回覆的方式。連接器會替你處理。 |
| 權杖 | 你的機器人專用的密碼。連接器用它向伺服器證明自己是哪個機器人。 |
| 終端機 | 用來輸入指令的視窗。在 Windows 上使用 PowerShell。 |
| 環境變數 | 傳給從同一個終端機視窗啟動的程式的暫時設定。視窗關閉後就會消失。 |
| JSON | 把資料寫成文字的一種格式。範例:{"move":19} |
| 引擎 | 查看局面並選擇著法的程式。 |
| 配接器 | 在你的引擎和連接器之間轉換資料的小程式。 |
| NBoard | NBoard是一款可搭配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 | Edax的eval.dat路徑。省略時在執行檔目錄或其上層目錄中尋找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 | 要回報的 CPU 名稱(不超過 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 或經過驗證的事件串流接收挑戰和局面。對你是 bot_b 的待接受挑戰送出 agent_accept,然後在進行中的對戰裡,只計算每一局中輪到你的局面。每個 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 中選擇。不要用標準 8×8 的著法產生方式來產生 10×10 或經典黑白棋的著法。
| 遊戲 | game_mode | 棋盤 | 初始棋子 | 時間(每局、每方) | 黑方最初的 legal_moves |
|---|---|---|---|---|---|
| 標準 | std | 8×8 | 隨機 12 子 | 60,000 ms | 取決於局面 |
| 反向 (Anti) | anti | 8×8 | 隨機 12 子 | 60,000 ms | 取決於局面 |
| 10x10 (Grand) | 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分鐘。
反向:下子和翻轉與標準奧賽羅相同。兩局合計棋子較少的一方獲勝。
10x10:在 10×10 棋盤(100 格)上依標準規則對局。初始配置是中央四格;機器人對局會再隨機走 8 步,從 12 子開始。
八角:棋盤和初始配置與 10x10 相同,但每個角各有三格、共 12 格不能使用(可用 88 格)。禁用格以格子編號的形式放在 blocked_squares 中,在 board 中則是與空格相同的 0。該陣列不依編號順序排列。禁用格不能下子,連成一線的棋子也在那裡中斷。
經典黑白棋:依規則從空棋盤開始。中央四格(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}10x10 (Grand) (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;grand 和 octagon 為 10。 |
board | integer[board_size²] | 長度為 board_size² 的陣列。從 a1 開始向右讀,然後是下一列。0=空(包括八角的禁用格),1=黑,2=白。 |
blocked_squares | integer[] | 八角中不能使用的格子。其他遊戲中為空陣列。 |
placed | {"1": integer, "2": integer} | 每種顏色到目前為止已下的子數(包括開局的隨機步);鍵是字串 "1" 和 "2"。用於經典黑白棋的 32 子上限。 |
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。 |
用變體向其他機器人發起挑戰
在 agent_challenge 中加入 game_mode;預設是 std。雙方機器人的 supported_games 中都必須有它。對手支援的遊戲可從 agent_poll 回應的 bots[].supported_games 中讀取。只要有一方不支援該遊戲,就會回傳 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子局面開始。 同一局面交換黑白同時進行兩局,以總棋子數決定勝負;反向遊戲中棋子較少者獲勝。
所有機器人對局都從隨機產生的12子局面開始。 所有遊戲類型中,每局每個機器人限時1分鐘。
如果機器人在開始後 30 秒內沒有下出第一步,對戰將中止且不計等級分。如果對戰中一方機器人斷線超過 30 秒而另一方保持連線,則保持連線的機器人獲勝(finish_reason: disconnect),結果計入等級分。
所有遊戲類型中,FlipSight 機器人的著法都由伺服器計算。網頁挑戰和 API 挑戰(agent_challenge)都可以,伺服器會立即開始對局,因此不必保持對局室開啟。如果伺服器的計算停止超過 30 秒,該對局作廢(aborted,finish_reason: technical),不計入等級分。變體遊戲的著法計算量大,與 FlipSight 機器人的變體對局同時最多 2 局。名額已滿時會回傳 HTTP 409 和 site bot variant matches are full,請稍後再試。
自動機器人戰:網站始終保持兩個機器人戰房間。開啟自動配對的註冊機器人在線且未在對局時,會收到與等級分相近的機器人(優先其他註冊機器人,沒有則為 FlipSight 機器人)進行標準(std)機器人戰的挑戰,該機器人為 bot_b。連接器自動接受後對局立即開始。如果 120 秒內未接受或拒絕,該機器人會在 10 分鐘內不參加自動配對。自動機器人戰計入等級分。競技場對局進行中時,自動機器人戰也會繼續。自動配對預設開啟,可在機器人頁面的管理中關閉。 用練習引擎啟動的機器人(environment.practice 為 true)不參加自動機器人戰,這段期間機器人之間的對局也不計入等級分。
人類直接挑戰註冊機器人時,可選擇支援的遊戲與時間,在一般房間進行一局。伺服器強制直接挑戰不計等級分。 開啟自動配對的上線註冊機器人也會參加支援的STD/XOT快速配對。會員快速配對計等級分,訪客不計。 註冊機器人只接受機器人管理中設定的同時對局數(預設 2)。與人的對局 1 局和機器人對戰 1 場(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子局面開始。 同一局面交換黑白同時進行兩局,以總棋子數決定勝負;反向遊戲中棋子較少者獲勝。
如果你是 bot_a,參與者編號為 0;如果是 bot_b,則為 1。參與者 0 在 legs[0] 執黑(1),在 legs[1] 執白(2);參與者 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 在 8×8 上是 19,在 10×10 上是 23。
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}狀態同步
事件串流已連線時,在收到事件時以及每 10 秒呼叫一次 agent_poll。不使用事件串流輪詢時,對戰中每秒一次,閒置時每 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 | 參與者 0 的機器人 ID(玩家為 0) |
bot_b | integer | 參與者 1 的機器人 ID(玩家為 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[] | 不能使用的格子(僅八角,未排序) |
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) | 逾時判負的那一局用於合計的判定分數 [黑, 白](選填)。有此欄位時,以它取代 discs 計入合計。逾時一方與對手的分數:std 和 reversi 為 0 比 64,grand 為 0 比 100,octagon 為 0 比 88,anti 為 64 比 0。另一局繼續進行,兩局都結束後依合計決定勝負。過去已結束的紀錄不會重新計算。 |
POST agent_challenge
發起挑戰
用 target_id(機器人編號)或 target_name(機器人名稱)指定對手,可在 agent_poll 回應的 bots 列表中查到。所有遊戲類型都可以透過 API 向 FlipSight 內建機器人(is_builtin: true)發起挑戰,伺服器會立即開始對局。如果與內建機器人的變體對局已有 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) / 10x10 (Grand) / 經典翻轉棋 |
請求範例
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 |