「真想看看大海。」
「看大海做什麼?」
「唔,我不知道看海要做什麼,我只是想看看不同的東西罷了。這裡永遠一成不變,什麼事也不會發生。」
「每個地方發生的事,都由這裡開始。」
「噢,我曉得......我只是想看看一兩件正在發生的事!」-- <地海古墓>,娥蘇拉.勒瑰恩著,蔡美鈴譯
狀態碼是遊戲系統定義的內建資訊,用來知會代理人,它的每一個選擇的結果為何,全部都被定義在 src/core/status_code.rs
裡面。
當初在定的時候沒有特別參考什麼範本。最接近榜樣的,應該是在寫 Rust 的過程當中,每次編譯錯誤時,編譯器回報出來的錯誤,如E0507
Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of dereference of `Ref<'_, TheDarkKnight>`
--> src/main.rs:12:5
|
12 | x.borrow().nothing_is_true(); // error: cannot move out of borrowed content
| ^^^^^^^^^^ ----------------- value moved due to this method call
| |
| move occurs because value has type `TheDarkKnight`, which does not implement the `Copy` trait
|
note: `TheDarkKnight::nothing_is_true` takes ownership of the receiver `self`, which moves value
--> src/main.rs:6:24
|
6 | fn nothing_is_true(self) {}
| ^^^^
note: if `TheDarkKnight` implemented `Clone`, you could clone the value
...
難怪整個社群的人對於自己的工具鏈如此自豪!在官網可以看到錯誤代碼像字典一樣建立了清晰的索引,其中的內容還有範例。
和這樣的偉大工程品相比有點往臉上貼金了。PathogenEngine 的場合,有兩股驅力促使我將錯誤的種類分門別類:
add_*
系列函數)構成一個龐大的狀態機(state machine)。過去粗糙的分類很難繼續沿用。所謂粗糙的分類,其實一直殘留在 examples/coord_server/README.md
裡面的描述。也就是大致上有 TURN
, OK
和 DONE
三種順利的狀態,但是不順利的狀態則是非常多種的。與其走過整個列表,我這裡挑兩個範例來呈現。
add_map_step
錯誤、或說失敗的狀態,在這個函數之中有四個。
開場的是,判斷這個羅盤階段的座標選點 c
,是不是故意選在對手(g.opposite(g.turn)
)的標記所在的位置。這是一個。
if *g.map.get(&g.opposite(g.turn)).unwrap() == c {
return Err("Ex00");
}
再來是關於邊界合法性判定,這裡有兩個不同的錯誤碼,前幾天介紹過就不重複解釋了,
if !c.is_valid() {
return Err("Ex25");
}
if (g.lockdown() && g.turn == Camp::Plague) || g.turn == Camp::Doctor {
// Plague cannot outbreak when lockdown
if c.x < -1 || c.x > 1 || c.y < -1 || c.y > 1 {
return Err("Ex01");
}
}
最後還有機會有誤。就是通過了前述三種檢查之後,如果沒有辦法以這個羅盤座標起始走出一個合法的標準回合(也就是昨天探討過的候選回合機制),那就表示這個選點不能選擇,
if self.candidate.len() == 0 {
// Cleanup self. It is recommanded that the application should
// clean up the state after seeing this error. Anyway we also
// wipe out the three attributes we have set above.
self.map = None;
self.restriction = HashMap::new();
self.steps = 0;
return Err("Ex20");
} else {
...
這裡還有一些殘留的註解,我自己完全沒有印象了。似乎當初有打算讓 application,也就是客戶程式端針對這些錯誤去做一些處理,但是後來我的路線就直接不讓代理人走錯誤步了。
這裡做些清除的動作,否則恐怕這個 action
會殘留一些髒值。在 status_code.rs
裡面定義了這些錯誤代碼的意義。我是用一個 str_to_full_msg
,也就是字串轉換成完整訊息的的函數,兼作解釋的用途,
pub fn str_to_full_msg(s: &'static str) -> &'static str {
let r = u32::from_str_radix(&s[2..], 16);
let mut index = u32::default();
...
match s.chars().next() {
Some('E') => index = index + 512,
Some('W') => index = index + 256,
Some('I') => index = index + 0,
_ => panic!("Invalid status code encountered. Wrong Type."),
}
match index {
...
549 => return "Invalid map position: Out of the possible 21 grids",
...
544 => return "Invalid map position: No possible route",
...
513 => return "Invalid map position: Lockdown now",
512 => return "Invalid map position: Collide with opponent",
...
中間一段可以看出,我想要三種不同的狀態代碼,錯誤(Error)、警告(Warning)、和資訊(Info)。那麼為什麼都有那些 x
呢?呃,只是想要提醒那些數字是十六進位的表示法。一般來說,可以用 0x
開頭或是 x
開頭來表示十六進位數字。但是也像函數最開始的兩行,其實還是把前兩個字元都跳掉,只讀後面的數字部份。所以... x
就是表示用,沒有什麼功能。
Ix00
)在所有狀態碼中,Ix00
,或俗稱原地踏步的這個碼,可說是最特殊的一個。
原地踏步,依規則書是禁止的。在羅盤階段第一條明訂,將羅盤上的己方標記移到任一其他空格,必須移動標記,不能將它留在原本位置。然後又在最後的注意事項當中補述:如果玩家於回合中沒有任何角色可以進行移動,則跳過該玩家的回合。也就是說,雖然遊戲規則明確禁止這種原地踏步的行為,然而如果一個玩家當前的確無任何路線可動,那麼他是被允許原地踏步的。
規則還有遊戲配件當中,有一個 token 叫做路標。在一個玩家的羅盤階段開始時,他移動(或放置,如果是疫病方玩家的第一回合的話)標記到某一格,然後接下來必須使用路標 token(跟其他角色棋一樣是個立體的遊戲配件),從對方玩家的羅盤標記開始,依最短路徑,走到自己剛移動的羅盤標記上。特殊情況發生在上述原地踏步的場合,必須將路標瞬移到對手的羅盤標記上。無論如何,但是這個元件在遊戲系統當中完全被省略掉了,之後也不會再提及。
這讓我想到翻轉大稻埕的處理方式。該作的核心機制是有一個 4x4 的共用貨品盤,上面的 token 是正反兩面有不同貨物的圓片。由於它定義了連鎖效果(爽快的 combo),所以規則當中也是將這種特殊例子列在注意事項中,以代表遊戲設計師有想到要幫玩家解決這些零星的遊玩干擾。規則提到,**在特定的圓片組合下,貨物圓片會出現迴圈,...當出現迴圈時,玩家必須重新設置...**一堆東西,而且有一些再重設盤面的動作。
對於遊戲沒有太多涉獵的讀者可能想要反問,**不然呢?**如果不能移動,那就跳過回合,一切維持原樣,不是嗎?不,不是的。遊戲中出現不能夠移動的情境並不罕見,而除了跳過一手之外,也還有更極端的情況,如
當初在啟動這個專案、實作到相關功能時(應該就是放隨機猴子 free run,結果某次發現卡死的錯愕之後),還直接私訊設計師,後來才發現規則書寫得很清楚。這是個很有趣的主題,因為直覺上我會認為,切合疫途主題的方式的話,這種情況也是很有判負的空間。畢竟這是一個嚴肅且有時效性、你死我活的主題。不過規則訂了就訂了。
回歸到程式設計師的角色,規格長成那樣,該怎麼做?重新解譯規格(遊戲規則),可以發現,雖然必須移動、不能留在原本位置,但倘若嚴格地如此執行了,那就要嘛無法兼容注意事項當中讓遊戲續行的手段,要嘛必須要在後續補上條件判斷特定盤面是否確實沒有可以考慮的著手可以成立。到頭來,我當初選擇了更省力的路線:不特別判斷,令原地踏步是個正常的著手。
這原本也不太令我擔心,我想,畢竟,一旦開始了模型訓練,原地踏步多了的總會比較容易輸;既然比較容易輸,那就應該讓它自己學會把這樣妨礙獲勝的著手從考慮的策略當中慢慢降低比重乃至於濾除。豈料,這還是在後續引爆了系統改版的契機。
Well,我這裡描述的改版並不是指 github 上面打的 tag,而是某種大規模的、實質上支援的系統或系統行為大幅改變的,更迭的過程。
所以,好一段時間裡,就時常看得到這種棋譜,
...
B[gj][fc][cc][fc][fc][fc][fc]
W[hi][cd][fd][fb][cd][cd][cd][cd][cd]
B[gj]
W[hj][da][fa][da][da][da][da][da]
B[hi][ec][ea][ec][ec][ec][ec]
W[ii][ji][fb][cb][fb][fb][fb][fb][fb]
B[hj][cc][bc][be][bc][cc][bc][cc]
W[ij][cb][fb][cb][cb][cb][cb][cb]
B[jh][be][de][dd][db][dd][be][de][dd]
W[hj][fb][cb][cd][bd][bf][fb][cb][cd][bd][bd]
B[ik][db][dd][ed][dd][db][db][dd]
W[ij][bf][bd][bf][bf][bf][bf][bf]
B[hj][ea][aa][ea][ea][ea][ea]
W[ij]
...
擷取自以 76a59cb92746401c9e133b46249c176a
為亂數種子生成的設置盤面中,可行的前 15 手。通常第一格是羅盤階段,如疫病方(B
)的第一格就是走在 gj
相當於 (-2, 1)
的位置的羅盤上;它在第三手只有一個簡短的屬性 B[gj]
,這就是停留在前一個階段的羅盤標記上的狀態。我在後來的演進過程中也逐步蒐集著資料,其實真正的情況下,因為不存在任何合法行動以至於必須引用原地踏步來解套的情況,至多只有 1%。雖然沒有嚴格檢驗,但這裡的第 3 手與第 15 手,恐怕都是不合乎規則的吧。
但是合乎我當初實作的系統,因此也留下了這樣的對局。
續前日,結果目前為止表現最好的模型是殘局訓練,一般驗證。是說,如果一般對局紀錄只是驗證用,那麼這個模型怎麼學習一般對局紀錄裡面的知識呢?的確沒辦法,但是可以取驗證曲線的極小值處,當作是下一個世代的基礎。
另一邊廂,有了殘局譜製造機之後,可以大量大量的生成,生成效率非常之高。
但我不確定這樣接下來到底要怎麼樣迭代下去?試圖用一般對局資料(沒有可靠的價值和策略的感覺)和可以大量生成的殘局混成訓練集,效果也並不好。得再做些實驗才行。
在西洋棋裡面,如果王的當前格子不是被叫將的狀況,而且它下一回合可以走的格子全部都在敵方攻擊範圍,那這是和棋。
第一次聽到
長知識了