汗顏,比賽近了尾聲,才發現步伐越是顢頇。
昨天發文之後,從空棋盤的狀態,先在 main.rs
和 web_server.rs
之間做一些調整,如這個 patch,然後有些本地端的調整還沒完善。
(繼續進行中...)
一邊煩惱著到底該怎麼最大限度的利用打譜程式打下的基礎,一邊先做了狀態碼的傳遞。我預期玩家在網頁模式點擊的時候,也是有可能會點錯。雖然那種反白當作可走步數的提示的功能是來不及做了,但至少點了之後是成功或失敗,應該還是可以顯示一下的。
相關功能剛剛推上去了。
我最煩惱的地方在於,到底該怎麼設計一個狀態窗格?
我還是比較習慣類似 coord_server
裡面的那種狀態機設計,如同先前介紹過的。但是,ChatGPT 老師很理所當然的認為,網頁前端傳送了著點之後,從伺服器傳回來的 HTTP 回應,就應該用來顯示在這個狀態窗格裡面。所謂的狀態機是指,由諸多狀態碼包裝而成的複雜迴圈結構:
'game: loop {
read_exact_bytes(&mut stream, 4, &mut status)?;
// XXX: 所有的行動都是由這裡取得的 status 來判斷的!!!
if status == "Ix0d".as_bytes() {
// 接收到的對手玩家的回合做了什麼事情,然後
// 使用疫途遊戲引擎,更新接收到的對手玩家的回合
...
};
continue 'game;
}
- if status == "Ix02".as_bytes() || status == "Ix00".as_bytes() {
- continue 'game;
- } else if status == "Ix04".as_bytes() {
- println!("win!");
- break 'game;
- } else if status == "Ix05".as_bytes() {
- println!("lose!");
- break 'game;
- } else {
- if status == "Ix01".as_bytes() || status == "Ix0b".as_bytes() {
- // the normal middle moves
- } else if status == "Ix03".as_bytes() {
- // the main play of this round
+
+ 'turn: loop {
+ if status == "Ix02".as_bytes() || status == "Ix00".as_bytes() {
+ continue 'game;
+ } else if status == "Ix04".as_bytes() {
+ println!("win!");
+ break 'game;
+ } else if status == "Ix05".as_bytes() {
+ println!("lose!");
+ break 'game;
} else {
- // Error?
- }
+ if status == "Ix01".as_bytes() || status == "Ix0b".as_bytes() {
+ // the normal middle moves
+ } else if status == "Ix03".as_bytes() {
+ // the main play of this round
+ } else {
+ // Error?
+ }
- if let Ok(c) = click_rx.recv() {
- println!("Received {} from web interface.", c);
- stream.write(&[c])?; // XXX:這是 patch 前的內容,也就是我們可以從 click_rx 接收到網頁前端選擇的著點,但是,狀態碼卻需要到觸及迴圈底部之後,從頭開始(參照前面的 XXX 註解)!!!
+ if let Ok((c, status_tx)) = click_rx.recv() {
// 所以 patch 的策略就是附上回郵信封,不是只傳回著點,而是在負責處理前端事件的非同步函數 receive_click 當中,連接一個傳送門,讓這裡可以經由網頁代理人傳送資料給遊戲伺服器(stream.write),然後由遊戲伺服器根據著點更新遊戲狀態...
+ println!("Received {} from web interface.", c);
+ stream.write(&[c])?;
+ read_exact_bytes(&mut stream, 4, &mut status)?; // ...並傳送狀態碼回來,然後我們再將這個狀態碼...
+ let s = format!("{:?}", status);
+ status_tx.send(StatusCode { status: s }); // ... 傳回給 receive_click 的傳送門。
+ }
+ continue 'turn; // 為了這個調度多塞了一層迴圈,但講下去頭腦打結,就先這樣吧。
Rust 對於型別是非常嚴謹的,但依我的感覺,這個傳送門功能,實在不得不令人想起 go channel,而且也很意外沒有什麼 Copy/Clone 特徵有無實作的問題,也沒有參照可不可變的問題,很乾脆地讓我擴充了。
所以看看 receive_click
那邊的變動,
async fn receive_click(
data: web::Json<ClickData>,
- click_tx: web::Data<Sender<u8>>,
+ click_tx: web::Data<Sender<(u8, Sender<StatusCode>)>>,
) -> impl Responder {
let c = data.coord;
+ let (status_tx, mut status_rx): (Sender<StatusCode>, Receiver<StatusCode>) = mpsc::channel(); // 傳送門
// Send the coordinates back to main.rs
- if let Err(e) = click_tx.send(c) {
+ if let Err(e) = click_tx.send((c, status_tx)) { // 很輕鬆的擴充了
eprintln!("Failed to send click data to main: {}", e);
return HttpResponse::InternalServerError().body("Failed to process click");
}
- HttpResponse::Ok().body("Click received")
+
+ if let Ok(game_response) = status_rx.recv() { // 附上回郵信封之後,立刻等信來!哈。
+ return HttpResponse::Ok().json(game_response);
+ }
+
+ HttpResponse::InternalServerError().body("Error processing the move")
}
接下來仍然要解決盤面上其他物件顯示的問題,不然就像閉著眼睛在玩。理論上昨天也是這樣。
但是為了避免意外,先上圖結束今天。
自首:雖然我有
str_to_full_msg
函數,但是我現在有點卡在 Rust 的嚴厲型別審查。所以也不確定怎麼轉,因此只能看到窗格中只有 ASCII。熟練如實作者的我當然可以看出這是Ex24
。但當務之急果然還是,趕快把圖形顯示搞定啊。
明天的話,按照一開始的規劃,邀請了兩位朋友(友人 Y 與友人 T)首次來遊玩疫途,並且預計讓他們與訓練了好幾個世代但仍然不怎麼樣的代理人對局。兩位都是比我更身經百戰的桌遊咖。本來是很浪漫的預想要準備一些問題,端出神擋殺神的 AI 代理人殺爆他們之後,再好好的對話一下,像是 DeepMind 對李世石做的那樣,但現在,Well,就簡單聊聊天吧。