iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0

驚險地趕在友人來訪之前搞定對局介面!以下描述幾個關鍵

字串解析,超瞎

從昨天的截圖(唉,不過數小時前而已,實在沒怎麼睡)顯示,狀態碼從以 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。

本來只想錄兩場,但是第二場本來是要和代理人對戰卻錄錯了,只好加碼這場。

三場都是現場進行,我也沒有預先計算設置的優劣,但不得不說疫途的隨機棋盤初始狀態,真的非常有趣。第一局真的很卡,怎麼走都走不到想要的地方,而它竟然插堡那麼快?都沒發現,不太懂,唉,總之,可以先收工了。

與友人關於疫途與抽象棋的雜談

https://ithelp.ithome.com.tw/upload/images/20240928/201035243kNuvjnaiq.jpg

https://ithelp.ithome.com.tw/upload/images/20240928/20103524vCord66SWy.jpg

也該介紹一下背景:友人 Y:浸淫圍棋多年,對於多種桌遊都有涉獵;友人 T:對於桌遊深有興趣,啟發我們這團的桌遊大使,自己也有頗豐富的收藏。這次讓兩人對戰,我在旁邊看著也是很有趣。

加上公司桌遊社的經驗,饒是我已經教過很多次,還是會在講解的時候漏規則。比方說像據點升級之後會有路障效果、或者敵方英雄踩著你的標記的時候你也不能再新增之類。看著剛學會的玩家互相阻撓,實在比隨機猴子緊湊得多。

不過應該是因為對局經驗還不多,所以兩人對於棋盤中的特定空間(比方說中央、或是角隅)的偏好看起來還是比較強烈,而往往要到中期搶佔路線的時候才發現,原以為有些格子很近,但實際上卻是很難抵達的邊陲。要認真計算的話可以很滿足地燒腦的遊戲。

原本計畫下午時間玩完之後,難得聚首,開個蓋亞計畫;但後來因為最近都因為公務操勞的緣故,還是玩了比較輕鬆的 Fujiyama星際探險隊:深海任務。原本計畫能夠讓兩人互相對戰之後,與我訓練出來的 AI 對戰,但對抗程度大有不如,就不必了。

友人們的一些心得感想

  • 「一般來說,規則如果不太直覺的話就不會太好玩,但這款不直覺的地方也還是很有趣。」
  • 「很特殊的機制。而且在 AI 時代的確會讓人很好奇,比方說封城這個動作的關鍵程度如何?它在一般遊戲所佔的著手多少?僅限制疫病活動範圍、但不真正扭轉羅盤的佔比又多少?遊戲結束的兩個條件中,四象限據點勝利的比例有多少?怎樣的條件適合以據點作為主要策略?」
  • 「抽象遊戲,真的很不吃香。不說抽象遊戲,就說現代桌遊,很多桌遊出版商也都知道,投入了大量資源與心力,但現代桌遊的壽命是如此短暫。」

友人的碎嘴盤外招

  • 「看來這第一手就是敗著。」

上一篇
對戰網頁實作:狀態碼嵌入
下一篇
這個代理人到底學會了什麼?
系列文
DeltaPathogen:國產雙人不對稱抽象棋「疫途」之桌遊 AI 實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言