驚險地趕在友人來訪之前搞定對局介面!以下描述幾個關鍵
從昨天的截圖(唉,不過數小時前而已,實在沒怎麼睡)顯示,狀態碼從以 JSON 格式傳遞,
{"status":"[69, 120, 50, 54]"}
幸好有 Mozilla 大神的輔助,終於是搞懂怎麼好好解析 JSON 內的字串了。經實測,後面真正的 status
內容,"[69, 120, 50, 54]"
和 ["69", "120", "50", "54"]
的處理手法不同,前者還要再多做一次。所以最後這個部份的程式碼如下:
const statusBlock = document.getElementById("status-block"); // 在整個 `document.addEventListener("DOMContentLoaded"` 的視野裡面
...
// Convert the array of ASCII codes to a string
const statusArray = JSON.parse(data);
const statusString = JSON.parse(statusArray.status); // This is tricky,真的需要特別強調!
const asciiString = String.fromCharCode(...statusString); // 以 ASCII 碼轉換
statusBlock.textContent = `Status: ${asciiString}`; // 更新到 `statusBlock`,也就是狀態窗格
根據 commit 時間,
commit f94497e1deafe9bdeea0dcdbcc7c809c9758ae98
Author: Quey-Liang Kao <s101062801@m101.nthu.edu.tw>
Date: Sat Sep 28 04:12:36 2024 +0800
[action_client] Refine status code
嗯,這個蠢問題剛好是用來醒腦的。
又奮鬥了一個多小時之後,得到
commit b3d5f16b804d0d828af407cb16a29c458ed39125
Author: Quey-Liang Kao <s101062801@m101.nthu.edu.tw>
Date: Sat Sep 28 05:23:44 2024 +0800
[action_client] First victory!
這是第一個成功顯示棋盤狀態在網頁上面的開發成果。因為沒有時間開發其他的顯示機制,所以一開始就打算完全利用打譜程式裡面的顯示功能。但是,打譜程式接收靜態的 SGF 棋譜,將它解析成前端 UI 需要的資訊;相較之下,對局的網頁則會在後端更新遊戲盤面狀態,而目前為止的網頁畫面都停滯在設置階段。這個更新的重點就是讓更新標準回合的功能上線。
這裡需要的思考是,如何繞過 Rust 的所有權概念。因為遊戲資訊(gs
)已經被包到可共享物件裡面(amgs: Arc<Mutex<GameState>>
),而且又被包到網頁資料形式(web::Data<AppState {amgs: Arc<Mutex<GameState>>} >
)裡面了。所以我在主程式的後續行動中,也沒辦法直接更新遊戲資訊,因為它已經是屬於別的物件了。但就是運用可共享物件的特性,將它們都複製來用。
再來就是前端了。有了這些更新的資訊之後,還需要予以顯示:
if (asciiString == "Ix02" || asciiString == "Ix00") {
fetch('/update_state')
...
data.steps.forEach(s => {
if (s.id > 9 + current_id) {
steps.push(s);
if (current_id == -1) {
const [_, [tcol, trow]] = convertPosition(steps[0].pos);
drawMap(trow, tcol, 'P');
} else {
applyStepForward();
}
current_id++;
}
在這個標準回合結束後觸發的 POST 方法當中,重新解析每一筆(data.steps.forEach(s =>
)遊戲資訊,同時以原先就有維護的流水號 current_id
過濾舊資訊(if (s.id > 9 + current_id)
,9
是跳過設置)。然後這裡的邏輯和原本打譜程式的 PageUp 觸發下一手是相同的。
事實上,這麼做,應該是不夠了解 javascript 的作法,因為這個 POST 方法藏在 /click
POST 事件當中。而且,它沒有考量到伺服器端的狀況。後面也的確發生了狀況。這一套如果和隨機猴子對戰,可以看到即時的更新;但是和代理人對戰的時候,由於模型推論需要時間,而這個 /update_state
的 POST 方法一發就過去,所以是更新不到的。但我也沒有力氣再修,所以還是每一手下完之後需要重新整理網頁。
之後又有一些零星的修改,比方說確保醫療方也可以用這套來玩。於是,終於可以和自己訓練的代理人對戰了!
應該要直接寫在 README 裡面的,但直接紀錄一下吧。有環境的讀者請也務必試試。因為是 Rust 專案,所以使用 cargo
開啟的這些指令都需要在 PathogenEngine 專案目錄之下的地方執行,有額外指定的另計。
首先生成幾份設置(對,我們只能在特定的設置上遊玩,所以還無法享受布局的樂趣...):
$ mkdir setup
$ for _ in $(seq 1 50); do
id=$(uuidgen);
cargo run --example setup_generator -- --mode sgf --seed "$(echo $id | sed -e 's/-//g')" --save "./setups/$id.sgf";
done
然後啟動伺服器,它會在 3698 聽疫病方、6241 聽醫療方:
$ cargo run --release --example coord_server -- --load-dir setups --save-dir records
然後,網頁玩家登入的方式是
$ cd examples/action_client
$ cargo run --release --example action_client 127.0.0.1 3698
指定訓練好的代理人,並且採取 100 次的蒙地卡羅模擬搜尋。這裡的模型我並沒有釋出,因為也沒有什麼強度可言。
$ cd examples/coord_clients
$ python main.py -t ReinforcementSimulate -s Doctor -m ../../share/20240923_gen23/train-trial6/game-trial6.pth.4 -b 1 --trial-unit 100
隨機猴子
$ python main.py -t Query -s Doctor --batch 1 --seed "333"
以下就以影片代替說明了。
我就這樣輸給了隨機。非常好笑。腦衝了沒在防守。
在錄的時候才想起來,我上面都沒有提到,現在的 UI 非常的差,點擊之後的回饋感只有依賴狀態碼,事實上這也是為什麼我會先打通狀態碼的緣故,因為至少有了它就可以玩了。簡單的原則是:
Ex
開頭,表示下錯了。Ready
,那就是要嘛現在可以下,要嘛等一下子重新整理之後取得新的盤面就可以下。Ix01
,表示羅盤階段和棋盤階段的著手有效。需要注意這個需要遵守嚴格的 SGF 格式,所以像是封城操作的時候,需要點擊兩次羅盤,一次代表自己的移動,一次代表疫病方更改後的移動。又,影片沒有音效,就更增加了閱讀的難度;我自己看影片都覺得蠻困難的...Ix0b
的時候,表示進入標記階段。需注意這是第二版規則,所以醫療方仍然需要先清除、疫病方也需要平衡(我系統實作上強制連順序都需要平衡)。是一場毫無壓力的快勝。呃......
這個代理人經歷了過去兩週,應該可以算是八個世代的訓練,每次訓練平均以十萬步著手訓練,ResNet 結構。對局時的模擬深度達到 40 次。
也是無壓力樂勝。
有點瞎,過程中帶到 nvidia-smi
畫面,但其實對戰時是用 CPU 跑的。但是也順便感謝一下兩年來不離不棄、熱當無數的筆電,還有裡面的 RTX 3080。
本來只想錄兩場,但是第二場本來是要和代理人對戰卻錄錯了,只好加碼這場。
三場都是現場進行,我也沒有預先計算設置的優劣,但不得不說疫途的隨機棋盤初始狀態,真的非常有趣。第一局真的很卡,怎麼走都走不到想要的地方,而它竟然插堡那麼快?都沒發現,不太懂,唉,總之,可以先收工了。
也該介紹一下背景:友人 Y:浸淫圍棋多年,對於多種桌遊都有涉獵;友人 T:對於桌遊深有興趣,啟發我們這團的桌遊大使,自己也有頗豐富的收藏。這次讓兩人對戰,我在旁邊看著也是很有趣。
加上公司桌遊社的經驗,饒是我已經教過很多次,還是會在講解的時候漏規則。比方說像據點升級之後會有路障效果、或者敵方英雄踩著你的標記的時候你也不能再新增之類。看著剛學會的玩家互相阻撓,實在比隨機猴子緊湊得多。
不過應該是因為對局經驗還不多,所以兩人對於棋盤中的特定空間(比方說中央、或是角隅)的偏好看起來還是比較強烈,而往往要到中期搶佔路線的時候才發現,原以為有些格子很近,但實際上卻是很難抵達的邊陲。要認真計算的話可以很滿足地燒腦的遊戲。
原本計畫下午時間玩完之後,難得聚首,開個蓋亞計畫;但後來因為最近都因為公務操勞的緣故,還是玩了比較輕鬆的 Fujiyama 和星際探險隊:深海任務。原本計畫能夠讓兩人互相對戰之後,與我訓練出來的 AI 對戰,但對抗程度大有不如,就不必了。