vino2241.html.7z- (21.11 KiB) 已下载 38 次
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
今日創作了一款弈棋《維諾廣場》,竟然非常好玩!
維諾廣場是一款抽象策略遊戲。在我創作弈棋時,一般都不會考慮用正方形格子,因為總覺得不會再有什麼創新的玩法了,沒什麼新鮮的了。
但是這次在用V形棋子進行研究以及修改規則時,我都忍不住要玩下去,沒想到會這麼好玩!
規則很簡單:只要把棋子放上去就可得分,如果構成2×2的棋型則可加4分,如果構成3×3可以再加9分。得分棋型可疊加。
遊戲流程是,先放法諾(五個正方形構成的v形棋子),再放米諾(一個小正方形),最後再盡量把維諾(三個正方形構成v字形)放進棋盤中。維諾階段是最關鍵的得分環節,每次落下一個維諾之前,都可以移動一格米諾,是的,米諾是可以移動的,如果有棋子阻擋還可以跳過這些棋子。
遊戲目標就是盡可能多的形成2×2的棋型,如果你能用高超的手法(或趁對手不注意)實現3×3,那基本上這局你已經獲勝啦!
維諾廣場是一款抽象策略遊戲。在我創作弈棋時,一般都不會考慮用正方形格子,因為總覺得不會再有什麼創新的玩法了,沒什麼新鮮的了。
但是這次在用V形棋子進行研究以及修改規則時,我都忍不住要玩下去,沒想到會這麼好玩!
規則很簡單:只要把棋子放上去就可得分,如果構成2×2的棋型則可加4分,如果構成3×3可以再加9分。得分棋型可疊加。
遊戲流程是,先放法諾(五個正方形構成的v形棋子),再放米諾(一個小正方形),最後再盡量把維諾(三個正方形構成v字形)放進棋盤中。維諾階段是最關鍵的得分環節,每次落下一個維諾之前,都可以移動一格米諾,是的,米諾是可以移動的,如果有棋子阻擋還可以跳過這些棋子。
遊戲目標就是盡可能多的形成2×2的棋型,如果你能用高超的手法(或趁對手不注意)實現3×3,那基本上這局你已經獲勝啦!
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
更改規則:
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則去掉,改為:
「每方在遊戲中有三個階段,法諾、米諾、維諾,當處於前面的階段,玩家可以自由選擇跳到後面的階段,但不能再跳回前面的階段。
比如當一個玩家手上還有法諾棋子沒有落完,如果他願意,或者確實無法再落下一個法諾,則他可以跳到米諾階段,甚至維諾階段。」
程式邏輯實現:
當輪到一方行動時如果處於法諾階段,他可以選擇米諾或維諾棋子(當然可以再選回法諾),當米諾或維諾棋子落下,則等於他選擇跳過了法諾階段,此後他將不能再落下一個法諾。
當一方處於米諾落子階段,他如果選擇一個維諾並落下,或者他移動了一個場上的米諾,則等同於他跳過了米諾階段,直接到維諾階段。此後他將不能再落下一個米諾。
https://gpt.quanquan.space/share/L0SCU5 ... _sUo8WNcjg
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則去掉,改為:
「每方在遊戲中有三個階段,法諾、米諾、維諾,當處於前面的階段,玩家可以自由選擇跳到後面的階段,但不能再跳回前面的階段。
比如當一個玩家手上還有法諾棋子沒有落完,如果他願意,或者確實無法再落下一個法諾,則他可以跳到米諾階段,甚至維諾階段。」
程式邏輯實現:
當輪到一方行動時如果處於法諾階段,他可以選擇米諾或維諾棋子(當然可以再選回法諾),當米諾或維諾棋子落下,則等於他選擇跳過了法諾階段,此後他將不能再落下一個法諾。
當一方處於米諾落子階段,他如果選擇一個維諾並落下,或者他移動了一個場上的米諾,則等同於他跳過了米諾階段,直接到維諾階段。此後他將不能再落下一個米諾。
代码: 全选
更改規則:
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則去掉,改為:
「每方在遊戲中有三個階段,法諾、米諾、維諾,當處於前面的階段,玩家可以自由選擇跳到後面的階段,但不能再跳回前面的階段。
比如當一個玩家手上還有法諾棋子沒有落完,如果他願意,或者確實無法再落下一個法諾,則他可以跳到米諾階段,甚至維諾階段。」
程式邏輯實現:
當輪到一方行動時如果處於法諾階段,他可以選擇米諾或維諾棋子(當然可以再選回法諾),當米諾或維諾棋子落下,則等於他選擇跳過了法諾階段,此後他將不能再落下一個法諾。
當一方處於米諾落子階段,他如果選擇一個維諾並落下,或者他移動了一個場上的米諾,則等同於他跳過了米諾階段,直接到維諾階段。此後他將不能再落下一個米諾。- 附件
-
vino2242.html.7z- (21.11 KiB) 已下载 23 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
更改規則及邏輯實現
更改規則
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則改為「如果一方無法再合規放置一個法諾,該方將直接開始放置米諾。」
之前的規則是「結束本方遊戲:進入維諾階段後,輪到的一方可以按下『結束本方遊戲』。玩家可在按下前最後移動一次米諾。結束後該方不能再行動,另一方仍可自行繼續, 直到他也結束或放完所有維諾。」現在改為「當一位玩家想要結束遊戲,則他將飛走一個米諾,即拿起其中一個米諾放到任意一個它當前走不到的空位。之後他將不能再行動。另一方仍可自行繼續行動,直到他也作出飛子結束本局。」
程式邏輯實現
當檢查出一方無法再放置一個法諾,則自動切換到米諾放置階段,並給出1s消息提示。當檢查出一方無法再放置一個米諾,則自動切換到維諾放置階段,並給出1s消息提示。
當檢查出一方無法再放置一個維諾,包括移走一個米諾也無法放置,則自動切換到米諾飛行階段,維諾棋子的「選中」將消失,並給出1s消息提示。這時點擊選中一個米諾時,將不會再有移動箭頭,也不能放到能走到的空位上。
當在維諾放置階段,如果點擊選取了一個米諾,則他將不能再放置一個維諾(除非米諾走完或再次點擊取消選擇)。這時若點擊棋盤空位使米諾落下(不是點擊行走箭頭),如果這個空位是當前位置所能走到的地方,則算它是一次行走,否則算是飛子。
當一方的米諾在移走之後,他將繼續放置一個維諾。這時這個米諾要用一個藍點或紅點作標記,它原來的地方也用相同的點作標記。這時如果再次點擊這個米諾,則它將退回原來的地方。
當一方飛子時,如果檢查出他仍能放置一個維諾,則彈出「確認或取消提示窗口」,詢問是否執行飛子以使本局結束,如果點否,則退回這次飛子。
當一方因為不能再落下一個維諾而自動進入子階段時,如果他點擊錯誤的棋子,或者沒選取米諾就點擊棋盤空位,或米諾在選取後所點擊的空位是它能走到的地方,都將彈出1s消息提示出錯。但可以在已經選取一個米諾時,點擊另一個米諾,以切換所選取的米諾。
去掉「結束本方遊戲」按鈕,以及相關的代碼。
明確給出所要修改的地方和所要替換的代碼。
https://gpt.quanquan.space/share/nfv0CU ... M6JBewp3E0
更改規則
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則改為「如果一方無法再合規放置一個法諾,該方將直接開始放置米諾。」
之前的規則是「結束本方遊戲:進入維諾階段後,輪到的一方可以按下『結束本方遊戲』。玩家可在按下前最後移動一次米諾。結束後該方不能再行動,另一方仍可自行繼續, 直到他也結束或放完所有維諾。」現在改為「當一位玩家想要結束遊戲,則他將飛走一個米諾,即拿起其中一個米諾放到任意一個它當前走不到的空位。之後他將不能再行動。另一方仍可自行繼續行動,直到他也作出飛子結束本局。」
程式邏輯實現
當檢查出一方無法再放置一個法諾,則自動切換到米諾放置階段,並給出1s消息提示。當檢查出一方無法再放置一個米諾,則自動切換到維諾放置階段,並給出1s消息提示。
當檢查出一方無法再放置一個維諾,包括移走一個米諾也無法放置,則自動切換到米諾飛行階段,維諾棋子的「選中」將消失,並給出1s消息提示。這時點擊選中一個米諾時,將不會再有移動箭頭,也不能放到能走到的空位上。
當在維諾放置階段,如果點擊選取了一個米諾,則他將不能再放置一個維諾(除非米諾走完或再次點擊取消選擇)。這時若點擊棋盤空位使米諾落下(不是點擊行走箭頭),如果這個空位是當前位置所能走到的地方,則算它是一次行走,否則算是飛子。
當一方的米諾在移走之後,他將繼續放置一個維諾。這時這個米諾要用一個藍點或紅點作標記,它原來的地方也用相同的點作標記。這時如果再次點擊這個米諾,則它將退回原來的地方。
當一方飛子時,如果檢查出他仍能放置一個維諾,則彈出「確認或取消提示窗口」,詢問是否執行飛子以使本局結束,如果點否,則退回這次飛子。
當一方因為不能再落下一個維諾而自動進入子階段時,如果他點擊錯誤的棋子,或者沒選取米諾就點擊棋盤空位,或米諾在選取後所點擊的空位是它能走到的地方,都將彈出1s消息提示出錯。但可以在已經選取一個米諾時,點擊另一個米諾,以切換所選取的米諾。
去掉「結束本方遊戲」按鈕,以及相關的代碼。
明確給出所要修改的地方和所要替換的代碼。
代码: 全选
更改規則及邏輯實現
更改規則
之前的規則是「如果一方無法合規放完五個法諾,該方立即判負。」現在把這個規則改為「如果一方無法再合規放置一個法諾,該方將直接開始放置米諾。」
之前的規則是「結束本方遊戲:進入維諾階段後,輪到的一方可以按下『結束本方遊戲』。玩家可在按下前最後移動一次米諾。結束後該方不能再行動,另一方仍可自行繼續, 直到他也結束或放完所有維諾。」現在改為「當一位玩家想要結束遊戲,則他將飛走一個米諾,即拿起其中一個米諾放到任意一個它當前走不到的空位。之後他將不能再行動。另一方仍可自行繼續行動,直到他也作出飛子結束本局。」
程式邏輯實現
當檢查出一方無法再放置一個法諾,則自動切換到米諾放置階段,並給出1s消息提示。當檢查出一方無法再放置一個米諾,則自動切換到維諾放置階段,並給出1s消息提示。
當檢查出一方無法再放置一個維諾,包括移走一個米諾也無法放置,則自動切換到米諾飛行階段,維諾棋子的「選中」將消失,並給出1s消息提示。這時點擊選中一個米諾時,將不會再有移動箭頭,也不能放到能走到的空位上。
當在維諾放置階段,如果點擊選取了一個米諾,則他將不能再放置一個維諾(除非米諾走完或再次點擊取消選擇)。這時若點擊棋盤空位使米諾落下(不是點擊行走箭頭),如果這個空位是當前位置所能走到的地方,則算它是一次行走,否則算是飛子。
當一方的米諾在移走之後,他將繼續放置一個維諾。這時這個米諾要用一個藍點或紅點作標記,它原來的地方也用相同的點作標記。這時如果再次點擊這個米諾,則它將退回原來的地方。
當一方飛子時,如果檢查出他仍能放置一個維諾,則彈出「確認或取消提示窗口」,詢問是否執行飛子以使本局結束,如果點否,則退回這次飛子。
當一方因為不能再落下一個維諾而自動進入子階段時,如果他點擊錯誤的棋子,或者沒選取米諾就點擊棋盤空位,或米諾在選取後所點擊的空位是它能走到的地方,都將彈出1s消息提示出錯。但可以在已經選取一個米諾時,點擊另一個米諾,以切換所選取的米諾。
去掉「結束本方遊戲」按鈕,以及相關的代碼。
明確給出所要修改的地方和所要替換的代碼。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
vino2244.html
- 附件
-
vino2244.html.7z- (23.49 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
當一方的法諾棋子沒有放完就跳到米諾階段,它的棋子數量不用變為零。
claude sonnet 5
claude sonnet 5
代码: 全选
Now update `getRequiredPiece` to respect the skip flags instead of relying on a zeroed count:Now update `preparePlayerPhase` so it no longer zeroes out the remaining piece count:Now reset `phaseSkipped` at the start of each round:Good, only one runtime reset site (in `startRound`), and it's already updated. Let me verify the diff and do a quick syntax sanity check.Syntax checks out. Let me diff against the original to confirm the changes are exactly what's intended:The diff is clean and matches the fix exactly. Now let me save it as the output file.已修好。原本 `preparePlayerPhase()` 在跳過法諾(或米諾)階段時,是直接把 `state.inventory[player].fino = 0`(或 `.mino = 0`)來強制切換階段,導致剩餘數量被清零。
現在改用獨立的 `state.phaseSkipped[player].fino` / `.mino` 旗標來標記「此階段已跳過」,`getRequiredPiece()` 會依此旗標略過已跳過的階段,而不再依賴把庫存數量歸零來判斷。這樣一來:
- 法諾(或米諾)尚未放完就進入下一階段時,畫面上仍會顯示真實剩餘數量,不會變成 0。
- 該棋子仍會正確標示為鎖定(locked)狀態,不能再放置。
- 這個旗標也同步加進了 undo/redo 的歷史快照與每回合重置邏輯,確保悔棋、重來時狀態正確。- 附件
-
vino2245.html.7z- (23.24 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
增加規則
當一方在場上存在至少一個法諾時,他可以選擇跳到米諾放置階段。
當一方在場上存在至少一個米諾時,他可以選擇跳到維諾放置階段。
當一方在場上存在至少一個維諾時,他可以選擇飛走一個米諾以結束本局遊戲。
本遊戲在機制上能夠保證在每個階段每種棋子都至少能放置一個,但為了規則上的完整,在此補充:當在放置其中一種棋的階段時,如果連一個這種棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
claude sonnet 5
當一方在場上存在至少一個法諾時,他可以選擇跳到米諾放置階段。
當一方在場上存在至少一個米諾時,他可以選擇跳到維諾放置階段。
當一方在場上存在至少一個維諾時,他可以選擇飛走一個米諾以結束本局遊戲。
本遊戲在機制上能夠保證在每個階段每種棋子都至少能放置一個,但為了規則上的完整,在此補充:當在放置其中一種棋的階段時,如果連一個這種棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
代码: 全选
增加規則
當一方在場上存在至少一個法諾時,他可以選擇跳到米諾放置階段。
當一方在場上存在至少一個米諾時,他可以選擇跳到維諾放置階段。
當一方在場上存在至少一個維諾時,他可以選擇飛走一個米諾以結束本局遊戲。
本遊戲在機制上能夠保證在每個階段每種棋子都至少能放置一個,但為了規則上的完整,在此補充:當在放置其中一種棋的階段時,如果連一個這種棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
增加規則
當一方處於法諾放置階段,如果場上存在至少一個法諾,他就可以選擇跳到米諾放置階段。
當一方處於米諾放置階段,如果場上存在至少一個米諾,他就可以選擇跳到維諾放置階段。
當一方處於維諾放置階段,如果場上存在至少一個維諾,他就可以選擇飛走一個米諾以結束本局遊戲。
當一方在某階段,如果連一個棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
明確給出所要修改的地方和所要替換的代碼。
https://gpt.quanquan.space/share/94kYwS ... P13RDCPkWs
當一方處於法諾放置階段,如果場上存在至少一個法諾,他就可以選擇跳到米諾放置階段。
當一方處於米諾放置階段,如果場上存在至少一個米諾,他就可以選擇跳到維諾放置階段。
當一方處於維諾放置階段,如果場上存在至少一個維諾,他就可以選擇飛走一個米諾以結束本局遊戲。
當一方在某階段,如果連一個棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
明確給出所要修改的地方和所要替換的代碼。
代码: 全选
增加規則
當一方處於法諾放置階段,如果場上存在至少一個法諾,他就可以選擇跳到米諾放置階段。
當一方處於米諾放置階段,如果場上存在至少一個米諾,他就可以選擇跳到維諾放置階段。
當一方處於維諾放置階段,如果場上存在至少一個維諾,他就可以選擇飛走一個米諾以結束本局遊戲。
當一方在某階段,如果連一個棋子都無法放置,則直接判負(兩局的總勝負)。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
明確給出所要修改的地方和所要替換的代碼。代码: 全选
以下修改以這個判定為準:
- 「無法放置」指:**該階段仍有該類棋子庫存,但棋盤上沒有任何合法落點**。
- 如果維諾已全部成功放完,仍可依「場上已有自己的維諾」規則飛走米諾,不會因庫存為 0 自動判負。
- 因無合法落點而判負時,直接決定**兩局總勝負**,不再進入下一局,也不比較分數。
---
# 一、增加新的翻譯文字
在 `I18N.en` 中,找到:
```js
phaseVinoToFlight:
'{player} cannot place another Vino, even after a normal Mino move. Mino flight phase begins.',
```
在它後面加入:
```js
turnVinoOnlyFlight: '{player} · select a Mino to fly and finish',
flightNeedsVino:
'Flying a Mino is allowed only during the Vino phase and after you have placed at least one Vino.',
noPlacementDefeat:
'{loser} has no legal {piece} placement and immediately loses the match. {winner} wins.',
noPlacementResultSummary:
'{loser} had at least one {piece} remaining but no legal placement. Under the no-placement rule, {winner} wins the entire two-round match immediately.',
decidedByRule: 'Match decided by the no-placement rule',
```
在 `I18N.zh` 中,找到:
```js
phaseVinoToFlight: '{player}即使正常移動一個米諾也無法再放置維諾,自動進入米諾飛行階段。',
```
在它後面加入:
```js
turnVinoOnlyFlight: '輪到{player}・請選取一個米諾飛走並結束本方遊戲',
flightNeedsVino: '只有進入維諾放置階段,而且場上已有至少一個本方維諾時,才能飛走米諾。',
noPlacementDefeat: '{loser}已無法合法放置任何一個{piece},直接判負;{winner}贏得兩局總勝負。',
noPlacementResultSummary:
'{loser}仍有{piece}尚未放置,但棋盤上已經沒有任何合法落點。依照無法放置直接判負的規則,{winner}立即贏得整場兩局比賽。',
decidedByRule: '本場由「無法放置直接判負」規則決定',
```
舊的:
```js
phaseFinoToMino
phaseMinoToVino
phaseVinoToFlight
```
可以保留,不會再被新階段判定使用。
---
# 二、增加狀態欄位
在 `state` 裡找到:
```js
selectedMinoId: null,
movedThisTurn: false,
pendingMinoMove: null,
pendingFlight: null,
```
替換成:
```js
selectedMinoId: null,
// 玩家目前在控制區選中的「待放置棋子」。
// null 表示自動使用該玩家目前正式所處的放置階段。
selectedPlacementType: null,
movedThisTurn: false,
pendingMinoMove: null,
pendingFlight: null,
```
再找到:
```js
roundEnded: false,
endInfo: null,
roundResults: [],
```
替換成:
```js
roundEnded: false,
endInfo: null,
roundResults: [],
// 因某階段沒有合法落點而直接輸掉兩局總勝負。
matchForfeit: null,
```
---
# 三、替換階段與棋子選擇函式
找到從:
```js
function isMinoFlightPhase(player) {
```
開始,一直到 `getActivePiece()` 結束的整段程式:
```js
function getActivePiece() {
...
}
```
全部替換成:
```js
function isMinoFlightPhase(player) {
return Boolean(player && state.minoFlightMode[player] && !state.playerEnded[player]);
}
function hasPlacedPiece(player, type) {
return state.pieces.some(piece => piece.owner === player && piece.type === type);
}
/*
* 回傳玩家目前正式所處的放置階段。
*
* phaseSkipped.fino === true:
* 玩家已經實際放下一個米諾,因此不能再選法諾。
*
* phaseSkipped.mino === true:
* 玩家已經實際放下一個維諾,因此不能再選米諾。
*
* 維諾即使庫存為 0,仍維持在維諾階段,
* 以便玩家飛走米諾結束本方遊戲。
*/
function getRequiredPiece(player) {
if (!player || !state.inventory[player] || state.playerEnded[player]) {
return null;
}
if (!state.phaseSkipped[player].fino && state.inventory[player].fino > 0) {
return 'fino';
}
if (!state.phaseSkipped[player].mino && state.inventory[player].mino > 0) {
return 'mino';
}
return 'vino';
}
/*
* 判斷控制區中的某種棋子目前是否可以被選中。
*/
function canSelectPlacementType(player, type) {
if (
!player ||
!state.inventory[player] ||
state.playerEnded[player] ||
state.roundEnded ||
state.gameOver ||
state.inventory[player][type] <= 0
) {
return false;
}
const phase = getRequiredPiece(player);
if (!phase) {
return false;
}
// 正式階段本身永遠可以選回。
if (type === phase) {
return true;
}
/*
* 法諾階段:
* 場上至少已有一個自己的法諾時,可暫時選擇米諾。
*/
if (phase === 'fino' && type === 'mino') {
return hasPlacedPiece(player, 'fino');
}
/*
* 米諾階段:
* 場上至少已有一個自己的米諾時,可暫時選擇維諾。
*/
if (phase === 'mino' && type === 'vino') {
return hasPlacedPiece(player, 'mino');
}
return false;
}
function getSelectedPlacementType(player) {
const phase = getRequiredPiece(player);
if (!phase) {
return null;
}
if (
state.selectedPlacementType &&
canSelectPlacementType(player, state.selectedPlacementType)
) {
return state.selectedPlacementType;
}
return phase;
}
/*
* 主動飛走米諾必須同時符合:
* 1. 正式處於維諾階段;
* 2. 場上至少有一個自己的維諾。
*/
function canVoluntarilyFlyMino(player) {
return (
getRequiredPiece(player) === 'vino' &&
hasPlacedPiece(player, 'vino')
);
}
function getActivePiece() {
if (
!state.gameStarted ||
state.roundEnded ||
state.gameOver ||
!state.currentPlayer ||
isMinoFlightPhase(state.currentPlayer)
) {
return null;
}
const type = getSelectedPlacementType(state.currentPlayer);
if (!type || state.inventory[state.currentPlayer][type] <= 0) {
return null;
}
return {
player: state.currentPlayer,
type,
orientation:
state.orientationMemory[state.currentPlayer][type] ||
(type === 'mino' ? 'single' : 'tl')
};
}
```
---
# 四、刪除自動跳階段,改成無法放置直接判負
找到從:
```js
function canContinueVinoPhase(player) {
```
開始,一直到:
```js
function preparePlayerPhase(player) {
...
}
```
結束的整段,包括:
```js
function enterMinoFlightPhase(player) {
```
全部替換成:
```js
function finishMatchByNoPlacement(loser, requiredType) {
const winner = otherPlayer(loser);
state.matchForfeit = {
winner,
loser,
requiredType,
round: state.roundNumber
};
state.gameOver = true;
state.roundEnded = true;
state.currentPlayer = 0;
state.selectedPlacementType = null;
state.selectedMinoId = null;
state.pendingMinoMove = null;
state.pendingFlight = null;
state.movedThisTurn = false;
state.hoverCell = null;
state.endInfo = {
reason: 'no-placement',
winner,
loser,
requiredType
};
hideFlightConfirmDialog();
showToast(
'noPlacementDefeat',
{
loser: playerToken(loser),
winner: playerToken(winner),
piece: pieceToken(requiredType)
},
1800
);
window.setTimeout(() => {
if (state.gameOver && state.matchForfeit) {
showFinalModal();
}
}, 260);
}
function preparePlayerPhase(player) {
if (
!player ||
state.playerEnded[player] ||
state.roundEnded ||
state.gameOver
) {
return;
}
const required = getRequiredPiece(player);
if (!required) {
return;
}
/*
* 維諾已全部成功放完時,不再檢查維諾落點;
* 玩家留在維諾階段,並可飛走一個米諾結束本方遊戲。
*
* 正常情況下,只要維諾全部放完,場上必然已有自己的維諾。
* 如果資料異常,沒有維諾或沒有米諾,則無法完成飛子,直接判負。
*/
if (required === 'vino' && state.inventory[player].vino <= 0) {
if (hasPlacedPiece(player, 'vino') && hasPlacedPiece(player, 'mino')) {
return;
}
finishMatchByNoPlacement(player, 'vino');
return;
}
/*
* 該階段仍有棋子,但整個棋盤沒有任何合法落點:
* 不再自動跳到下一階段,而是直接輸掉兩局總勝負。
*/
if (!hasAnyLegalPlacement(player, required)) {
finishMatchByNoPlacement(player, required);
}
}
```
這一段會完全取消原本的:
- 無法放法諾時自動進入米諾;
- 無法放米諾時自動進入維諾;
- 無法放維諾時自動進入米諾飛行階段。
---
# 五、替換棋子控制區的顯示與按鈕啟用邏輯
找到完整的:
```js
function renderPieces() {
...
}
```
替換成:
```js
function renderPieces() {
document.querySelectorAll('.piece-item').forEach(item => {
const player = Number(item.dataset.player);
const type = item.dataset.type;
const count = state.inventory[player][type];
const frame = item.querySelector('.piece-frame');
const nameElement = item.querySelector('.piece-name');
const countElement = item.querySelector('.piece-count');
const orientation =
state.orientationMemory[player][type] ||
(type === 'mino' ? 'single' : 'tl');
const activePlayer =
state.gameStarted &&
!state.roundEnded &&
!state.gameOver &&
state.currentPlayer === player &&
!state.playerEnded[player];
const inFlightPhase = isMinoFlightPhase(player);
const selectedType = activePlayer
? getSelectedPlacementType(player)
: null;
const selectable =
activePlayer &&
!inFlightPhase &&
canSelectPlacementType(player, type);
/*
* 選取場上米諾準備移動時,暫時不把維諾控制框畫成選中,
* 直到米諾移動完成或取消選取。
*/
const blockedByBoardMinoSelection =
activePlayer &&
type === 'vino' &&
state.selectedMinoId !== null;
const selected =
selectable &&
selectedType === type &&
!blockedByBoardMinoSelection;
const locked = count > 0 && !selectable;
nameElement.textContent = pieceName(type);
countElement.textContent = `×${count}`;
item.classList.toggle('selected', selected);
item.classList.toggle('locked', locked);
item.classList.toggle('not-current', !activePlayer);
item.classList.toggle('ended', state.playerEnded[player]);
item.classList.toggle('depleted', count === 0);
/*
* 米諾按鈕現在也必須可以點擊,
* 因為法諾階段可點擊米諾進行暫時切換。
*/
frame.disabled = !selectable || blockedByBoardMinoSelection;
frame.setAttribute('aria-pressed', String(selected));
frame.setAttribute(
'aria-label',
translate('pieceCountAria', {
player: playerToken(player),
piece: pieceToken(type),
count
})
);
frame.title = frame.getAttribute('aria-label');
renderMiniPiece(frame, type, orientation, player);
});
}
```
---
# 六、替換棋子控制區的點擊函式
找到完整的:
```js
function handlePieceFrameClick(event, frame) {
...
}
```
替換成:
```js
function handlePieceFrameClick(event, frame) {
if (!state.gameStarted || state.roundEnded || state.gameOver) {
return;
}
const player = Number(frame.dataset.player);
const type = frame.dataset.type;
if (player !== state.currentPlayer) {
showToast('notTurn', {
player: playerToken(state.currentPlayer)
});
return;
}
if (!canSelectPlacementType(player, type)) {
return;
}
/*
* 在法諾與米諾之間,以及米諾與維諾之間,
* 只改變暫時選中的棋子,不立即提交階段跳轉。
*/
state.selectedPlacementType = type;
state.hoverCell = null;
/*
* 法諾和維諾仍然可以透過點擊控制框的四個象限改變方向。
* 米諾沒有方向,因此只切換選中狀態。
*/
if (type !== 'mino') {
const orientation = getOrientationFromFrameClick(event, frame);
state.orientationMemory[player][type] = orientation;
}
renderPieces();
renderPreview();
renderMoveArrows();
renderStatus();
}
```
---
# 七、落下米諾或維諾時,正式提交階段跳轉
找到完整的:
```js
function placePiece(validation, active) {
...
}
```
替換成:
```js
function placePiece(validation, active) {
/*
* active.type 是目前暫時選中的棋子;
* committedPhase 是落子前正式所處的階段。
*/
const committedPhase = getRequiredPiece(active.player);
const piece = {
id: state.nextPieceId,
owner: active.player,
type: active.type,
orientation: active.orientation,
cells: validation.cells.map(cell => ({ ...cell }))
};
state.nextPieceId += 1;
state.pieces.push(piece);
state.inventory[active.player][active.type] -= 1;
/*
* 法諾階段實際落下一個米諾:
* 從此正式進入米諾階段,不能再選回法諾。
*/
if (committedPhase === 'fino' && active.type === 'mino') {
state.phaseSkipped[active.player].fino = true;
}
/*
* 米諾階段實際落下一個維諾:
* 從此正式進入維諾階段,不能再選回米諾。
*/
if (committedPhase === 'mino' && active.type === 'vino') {
state.phaseSkipped[active.player].mino = true;
}
rebuildBoard();
recalculateScores();
state.hoverCell = null;
state.selectedMinoId = null;
state.selectedPlacementType = null;
state.pendingMinoMove = null;
state.movedThisTurn = false;
resolveNextTurn(active.player);
pushHistorySnapshot();
renderAll();
if (state.gameOver) {
return;
}
if (state.roundEnded) {
window.setTimeout(() => {
if (state.roundEnded && !state.gameOver) {
showRoundModal();
}
}, 120);
}
}
```
---
# 八、換手時清除暫時選中的放置棋子
在 `resolveNextTurn()` 中找到:
```js
state.movedThisTurn = false;
state.selectedMinoId = null;
state.pendingMinoMove = null;
state.hoverCell = null;
preparePlayerPhase(state.currentPlayer);
```
替換成:
```js
state.movedThisTurn = false;
state.selectedMinoId = null;
state.selectedPlacementType = null;
state.pendingMinoMove = null;
state.hoverCell = null;
preparePlayerPhase(state.currentPlayer);
```
---
# 九、移動場上的米諾後,自動選中維諾
在 `performNormalMinoMove()` 中找到:
```js
state.movedThisTurn = true;
state.selectedMinoId = null;
state.hoverCell = null;
```
替換成:
```js
state.movedThisTurn = true;
state.selectedMinoId = null;
/*
* 不論原本是從米諾階段暫時選中維諾,
* 還是已正式進入維諾階段,
* 移動場上的米諾後都自動切換為維諾選中。
*/
state.selectedPlacementType = 'vino';
state.hoverCell = null;
```
在 `revertPendingMinoMove()` 中找到:
```js
state.pendingMinoMove = null;
state.movedThisTurn = false;
state.selectedMinoId = null;
state.hoverCell = null;
```
替換成:
```js
state.pendingMinoMove = null;
state.movedThisTurn = false;
state.selectedMinoId = null;
state.selectedPlacementType = 'vino';
state.hoverCell = null;
```
---
# 十、飛子必須處於維諾階段,而且場上已有自己的維諾
在 `attemptMinoFlight()` 開頭找到:
```js
function attemptMinoFlight(pieceId, destination) {
const player = state.currentPlayer;
const piece = findPiece(pieceId);
if (
```
替換成:
```js
function attemptMinoFlight(pieceId, destination) {
const player = state.currentPlayer;
const piece = findPiece(pieceId);
if (!canVoluntarilyFlyMino(player)) {
showToast('flightNeedsVino', {}, 1400);
return;
}
if (
```
接著,在同一個 `attemptMinoFlight()` 中找到:
```js
const wasAutomaticFlightPhase = isMinoFlightPhase(player);
```
替換成:
```js
const wasAutomaticFlightPhase = false;
```
雖然舊的自動飛行階段已經不會再啟動,但保留後面的確認窗口邏輯。
---
# 十一、取消飛子後保持維諾選中
在 `cancelPendingFlight()` 中找到:
```js
state.pendingFlight = null;
state.movedThisTurn = false;
state.pendingMinoMove = null;
state.hoverCell = null;
```
替換成:
```js
state.pendingFlight = null;
state.movedThisTurn = false;
state.pendingMinoMove = null;
state.selectedPlacementType = 'vino';
state.hoverCell = null;
```
在 `finishPlayerByFlight()` 中找到:
```js
state.pendingFlight = null;
state.playerEnded[player] = true;
state.selectedMinoId = null;
state.pendingMinoMove = null;
```
替換成:
```js
state.pendingFlight = null;
state.playerEnded[player] = true;
state.selectedMinoId = null;
state.selectedPlacementType = null;
state.pendingMinoMove = null;
```
---
# 十二、沒有維諾庫存時不要顯示普通移動箭頭
在 `renderMoveArrows()` 中找到:
```js
// 飛行階段只顯示選中框,不顯示任何移動箭頭。
if (isMinoFlightPhase(state.currentPlayer) || state.movedThisTurn) {
return;
}
```
替換成:
```js
/*
* 沒有待放置維諾時,只能把米諾飛走結束,
* 不能再進行「先正常移動米諾、再放維諾」的操作。
*/
const active = getActivePiece();
if (
isMinoFlightPhase(state.currentPlayer) ||
state.movedThisTurn ||
!active ||
active.type !== 'vino'
) {
return;
}
```
---
# 十三、完整替換棋盤點擊函式
原本的 `handleBoardClick()` 依賴 `active.type === 'vino'`。當維諾庫存為 0 時,`active` 會是 `null`,因此必須改用正式階段判斷。
找到完整的:
```js
function handleBoardClick(event) {
...
}
```
替換成:
```js
function handleBoardClick(event) {
if (!state.gameStarted) {
return;
}
if (state.roundEnded || state.gameOver) {
openCurrentResult();
return;
}
const arrow = event.target.closest('[data-move-dir]');
if (arrow) {
executeMinoMove(arrow.dataset.moveDir);
return;
}
const cell = getBoardCellFromPointer(event);
if (!cell) {
return;
}
const player = state.currentPlayer;
const occupied = state.board[cell.row][cell.col];
const placementPhase = getRequiredPiece(player);
const active = getActivePiece();
if (isMinoFlightPhase(player)) {
handleMinoFlightBoardClick(cell, occupied);
return;
}
/*
* 正常移動後再次點擊被移動的米諾,
* 立即使它退回原來的位置。
*/
if (
placementPhase === 'vino' &&
occupied &&
occupied.owner === player &&
occupied.type === 'mino' &&
state.pendingMinoMove &&
occupied.pieceId === state.pendingMinoMove.pieceId
) {
revertPendingMinoMove();
return;
}
/*
* 只要目前選中的是維諾,或者正式處於維諾階段,
* 就可以點擊本方場上米諾進入米諾移動選取。
*
* 因此也支援:
* 米諾階段 → 點擊控制區維諾 → 點擊場上米諾移動。
*/
const canUseBoardMino =
placementPhase === 'vino' ||
(active && active.type === 'vino');
if (
canUseBoardMino &&
occupied &&
occupied.owner === player &&
occupied.type === 'mino'
) {
selectMino(occupied.pieceId);
return;
}
/*
* 已選中場上米諾時,空格點擊被解釋為米諾目的地。
*/
if (canUseBoardMino && state.selectedMinoId) {
if (occupied) {
showToast('overlap');
return;
}
const legalDestinations =
getLegalMinoDestinations(state.selectedMinoId);
const normalDestination =
legalDestinations.get(cellKey(cell.row, cell.col));
if (normalDestination) {
/*
* 只有仍有維諾可放時,才允許普通移動米諾。
* 普通移動完成後會自動選中維諾。
*/
if (active && active.type === 'vino') {
performNormalMinoMove(
state.selectedMinoId,
normalDestination
);
} else {
/*
* 維諾已用完時,能正常到達的位置不能作為飛子落點。
*/
showToast('flightReachableCell', {}, 1000);
}
} else if (canVoluntarilyFlyMino(player)) {
attemptMinoFlight(state.selectedMinoId, cell);
} else {
showToast('flightNeedsVino', {}, 1400);
}
return;
}
/*
* 沒有可放置的 active 棋子時,表示維諾庫存已用完;
* 此時只能選取米諾進行飛子,不能普通落子。
*/
if (!active) {
return;
}
const validation = validatePlacement(
active.player,
active.type,
active.orientation,
cell.row,
cell.col
);
if (!validation.ok) {
const toastKey =
{
bounds: 'outOfBounds',
overlap: 'overlap',
contact: 'ownContact'
}[validation.reason] || 'overlap';
showToast(toastKey);
renderMoveArrows();
return;
}
placePiece(validation, active);
}
```
---
# 十四、維諾用完時顯示飛子提示
## 修改 `renderBoard()`
在 `renderBoard()` 中找到:
```js
const active = getActivePiece();
status = active
? translate('turnAuto', {
player: playerToken(active.player),
piece: pieceToken(active.type)
})
: '';
```
替換成:
```js
const active = getActivePiece();
const phase = getRequiredPiece(state.currentPlayer);
if (active) {
status = translate('turnAuto', {
player: playerToken(active.player),
piece: pieceToken(active.type)
});
} else if (
phase === 'vino' &&
canVoluntarilyFlyMino(state.currentPlayer)
) {
status = translate('turnVinoOnlyFlight', {
player: playerToken(state.currentPlayer)
});
} else {
status = '';
}
```
## 修改 `renderStatus()`
在 `renderStatus()` 中找到最後這一段:
```js
} else if (active) {
els.turnText.textContent = translate('turnAuto', {
player: playerToken(active.player),
piece: pieceToken(active.type)
});
} else {
els.turnText.textContent = '';
}
```
替換成:
```js
} else if (active) {
els.turnText.textContent = translate('turnAuto', {
player: playerToken(active.player),
piece: pieceToken(active.type)
});
} else if (
getRequiredPiece(state.currentPlayer) === 'vino' &&
canVoluntarilyFlyMino(state.currentPlayer)
) {
els.turnText.textContent = translate('turnVinoOnlyFlight', {
player: playerToken(state.currentPlayer)
});
} else {
els.turnText.textContent = '';
}
```
---
# 十五、初始化與重新開局時清除新增狀態
在 `startRound()` 中找到:
```js
state.hoverCell = null;
state.selectedMinoId = null;
state.movedThisTurn = false;
state.pendingMinoMove = null;
state.pendingFlight = null;
```
替換成:
```js
state.hoverCell = null;
state.selectedMinoId = null;
state.selectedPlacementType = null;
state.movedThisTurn = false;
state.pendingMinoMove = null;
state.pendingFlight = null;
state.matchForfeit = null;
```
在 `startGame()` 中找到:
```js
state.gameStarted = true;
state.gameOver = false;
state.roundResults = [];
```
替換成:
```js
state.gameStarted = true;
state.gameOver = false;
state.roundResults = [];
state.matchForfeit = null;
```
在 `restoreSnapshot()` 中找到:
```js
state.selectedMinoId = null;
state.hoverCell = null;
```
替換成:
```js
state.selectedMinoId = null;
state.selectedPlacementType = null;
state.hoverCell = null;
```
在 `returnToTitle()` 中找到:
```js
state.pendingFlight = null;
state.pendingMinoMove = null;
state.gameStarted = false;
state.gameOver = false;
```
替換成:
```js
state.pendingFlight = null;
state.pendingMinoMove = null;
state.selectedPlacementType = null;
state.matchForfeit = null;
state.gameStarted = false;
state.gameOver = false;
```
在正常的 `finishMatch()` 開頭加入:
```js
state.matchForfeit = null;
```
修改後為:
```js
function finishMatch() {
state.matchForfeit = null;
commitPendingRoundResult();
state.gameOver = true;
state.roundEnded = true;
renderAll();
showFinalModal();
}
```
---
# 十六、增加「無法放置直接輸掉總比賽」的結果窗口
在 `showFinalModal()` 前面加入:
```js
function showNoPlacementFinalModal(focusPrimary = true) {
const forfeit = state.matchForfeit;
if (!forfeit) {
return;
}
state.resultMode = 'final';
els.resultEyebrow.textContent = translate('finalEyebrow');
els.resultTitle.textContent = translate('winsMatch', {
player: playerToken(forfeit.winner)
});
els.resultSummary.textContent = translate(
'noPlacementResultSummary',
{
loser: playerToken(forfeit.loser),
winner: playerToken(forfeit.winner),
piece: pieceToken(forfeit.requiredType)
}
);
/*
* 這次勝負由直接判負規則決定,
* 分數不參與總勝負,所以隱藏分數區。
*/
els.resultSectionLabel.textContent = translate('decidedByRule');
els.resultSectionLabel.hidden = false;
els.resultScores.hidden = true;
els.resultScores.replaceChildren();
els.resultBreakdown.hidden = true;
els.resultBreakdown.replaceChildren();
els.resultPrimary.textContent = translate('playAgain');
els.resultSecondary.textContent = translate('backToTitle');
els.resultSecondary.hidden = false;
els.resultPrimary.onclick = () => {
state.roundResults = [];
state.matchForfeit = null;
state.gameOver = false;
state.gameStarted = true;
startRound(1);
};
els.resultSecondary.onclick = returnToTitle;
els.resultBackdrop.hidden = false;
if (focusPrimary) {
requestAnimationFrame(() => {
els.resultPrimary.focus();
});
}
}
```
然後找到 `showFinalModal()` 開頭:
```js
function showFinalModal(focusPrimary = true) {
const results = [...state.roundResults].sort((a, b) => a.round - b.round);
if (!results.length) {
return;
}
```
替換成:
```js
function showFinalModal(focusPrimary = true) {
if (state.matchForfeit) {
showNoPlacementFinalModal(focusPrimary);
return;
}
const results = [...state.roundResults].sort(
(a, b) => a.round - b.round
);
if (!results.length) {
return;
}
els.resultSectionLabel.hidden = false;
els.resultScores.hidden = false;
```
注意:只替換函式開頭,`showFinalModal()` 後面的原有內容繼續保留。
在 `showRoundModal()` 中,找到:
```js
els.resultSectionLabel.textContent = translate('roundScores');
els.resultScores.innerHTML = scoreCardsHTML(result.stats[1], result.stats[2]);
```
替換成:
```js
els.resultSectionLabel.hidden = false;
els.resultSectionLabel.textContent = translate('roundScores');
els.resultScores.hidden = false;
els.resultScores.innerHTML = scoreCardsHTML(
result.stats[1],
result.stats[2]
);
```
---
# 十七、更新規則窗口
## 中文 `rulesHtml`
將 `I18N.zh.rulesHtml` 完整替換成:
```js
rulesHtml: `
<div class="rules-grid">
<article class="rule-block">
<span class="rule-index">01</span>
<h3>棋子與放置階段</h3>
<p>
每方擁有法諾 5 個、米諾 3 個、維諾 7 個。
每位玩家分別依照
<strong>法諾 → 米諾 → 維諾</strong>
的順序進行自己的放置階段。
</p>
</article>
<article class="rule-block">
<span class="rule-index">02</span>
<h3>法諾階段</h3>
<p>
新放置的法諾不能與本方任何既有棋子接觸,包括共用邊和角對角接觸。
場上已有至少一個本方法諾時,可以點擊米諾暫時選中米諾;
在實際落子前仍可選回法諾。一旦實際放下一個米諾,
就正式進入米諾階段,不能再選法諾。
</p>
</article>
<article class="rule-block">
<span class="rule-index">03</span>
<h3>米諾階段</h3>
<p>
新放置的米諾不能與本方任何既有棋子接觸,包括角對角接觸。
場上已有至少一個本方米諾時,可以點擊維諾暫時選中維諾;
在實際落子前仍可選回米諾。一旦實際放下一個維諾,
就正式進入維諾階段,不能再選米諾。
</p>
</article>
<article class="rule-block">
<span class="rule-index">04</span>
<h3>維諾與米諾移動</h3>
<p>
選中維諾時,可以先點擊一個本方場上米諾並按照正常移動規則移動。
米諾移動完成後,控制區會自動選中維諾。
點擊移動後的米諾,可以在放置棋子前把它退回原位。
</p>
</article>
<article class="rule-block">
<span class="rule-index">05</span>
<h3>飛走米諾結束本方遊戲</h3>
<p>
正式處於維諾階段,而且場上已有至少一個本方維諾時,
可以選擇把一個本方米諾飛到它目前按照正常移動規則走不到的空位。
確認飛子後,該玩家立即結束本方遊戲,不能再進行行動。
另一方繼續遊戲,直到他也結束本方遊戲。
</p>
</article>
<article class="rule-block">
<span class="rule-index">06</span>
<h3>無法放置直接判負</h3>
<p>
如果輪到一方時,該方在目前正式所處的放置階段仍有棋子,
但整個棋盤上連一個合法落點都不存在,該方立即判負。
這不是單局判負,而是直接輸掉兩局的總勝負;
比賽立即結束,不再比較分數,也不再進行下一局。
</p>
</article>
<article class="rule-block wide">
<span class="rule-index">07</span>
<h3>計分</h3>
<p>
每個由同一玩家完整佔用的正方形,都會按照面積計分:
1 × 1 計 1 分、2 × 2 計 4 分、3 × 3 計 9 分,依此類推。
不同位置及互相重疊的正方形會分別計算。
如果比賽因「無法放置直接判負」而結束,分數不影響總勝負。
</p>
</article>
</div>
`
```
## 英文 `rulesHtml`
將 `I18N.en.rulesHtml` 完整替換成:
```js
rulesHtml: `
<div class="rules-grid">
<article class="rule-block">
<span class="rule-index">01</span>
<h3>Pieces and placement phases</h3>
<p>
Each player has Fino × 5, Mino × 3, and Vino × 7.
Each player advances independently through
<strong>Fino → Mino → Vino</strong>.
</p>
</article>
<article class="rule-block">
<span class="rule-index">02</span>
<h3>Fino phase</h3>
<p>
A newly placed Fino may not touch another piece owned by the same
player, including diagonally. After placing at least one Fino,
the player may temporarily select Mino. The player may select Fino
again before placing, but once a Mino is placed, Fino can no longer
be selected.
</p>
</article>
<article class="rule-block">
<span class="rule-index">03</span>
<h3>Mino phase</h3>
<p>
A newly placed Mino may not touch another piece owned by the same
player, including diagonally. After placing at least one Mino,
the player may temporarily select Vino. The player may select Mino
again before placing, but once a Vino is placed, Mino can no longer
be selected.
</p>
</article>
<article class="rule-block">
<span class="rule-index">04</span>
<h3>Vino and normal Mino movement</h3>
<p>
While Vino is selected, the player may first select and normally move
one of their Mino pieces. After that Mino moves, Vino is selected
automatically. Clicking the moved Mino before placement returns it
to its original cell.
</p>
</article>
<article class="rule-block">
<span class="rule-index">05</span>
<h3>Flying a Mino to finish</h3>
<p>
During the Vino phase, after at least one of the player's Vino pieces
is on the board, the player may fly one of their Mino pieces to an
empty cell that it cannot reach by a normal move. Confirming the
flight immediately ends that player's game.
</p>
</article>
<article class="rule-block">
<span class="rule-index">06</span>
<h3>No legal placement means defeat</h3>
<p>
If a player still has pieces for their current placement phase but
there is no legal placement anywhere on the board, that player
immediately loses the entire two-round match. The match ends without
comparing scores and no further round is played.
</p>
</article>
<article class="rule-block wide">
<span class="rule-index">07</span>
<h3>Scoring</h3>
<p>
Every fully occupied square owned by a player scores its area:
a 1 × 1 scores 1 point, a 2 × 2 scores 4 points, a 3 × 3
scores 9 points, and so on. Overlapping positions count separately.
Scores do not affect the result when the match ends under the
no-legal-placement defeat rule.
</p>
</article>
</div>
`
```
---
# 修改後的核心行為
1. **法諾階段**
- 場上已有自己的法諾後,法諾和米諾按鈕都可以點。
- 放米諾前可以選回法諾。
- 實際放下米諾後永久鎖定法諾。
2. **米諾階段**
- 場上已有自己的米諾後,米諾和維諾按鈕都可以點。
- 放維諾前可以選回米諾。
- 實際放下維諾後永久鎖定米諾。
- 移動場上的米諾後自動選中維諾。
3. **維諾階段**
- 場上已有自己的維諾後,可以飛走自己的米諾。
- 飛子後結束該玩家本局行動。
- 維諾全部放完後仍可飛子。
4. **無合法落點**
- 不再自動跳到下一階段。
- 直接輸掉整場兩局比賽。
- 結果窗口顯示規則判負,不比較總分。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
更改規則
落子階段有「法諾、米諾、維諾」。在每一個落子階段,都必須至少放置一個本階段的棋子,否則直接判負(兩局的總勝負)。當場上已經存在一個本階段的棋子,則玩家可以選擇跳到下一個階段。當在維諾階段,場上已經存在至少一個維諾時,玩家可以選擇飛子以結束本局。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
之前的「當處於法諾階段,不能再放置一個法諾,就自動跳到米諾階段;當處於米諾階段,不能再放置一個米諾,就自動跳到米諾階段;當處於維諾階段,不能再放置一個維諾,就自動跳到飛子階段」仍然保留。
明確給出所要修改的地方和所要替換的代碼。
落子階段有「法諾、米諾、維諾」。在每一個落子階段,都必須至少放置一個本階段的棋子,否則直接判負(兩局的總勝負)。當場上已經存在一個本階段的棋子,則玩家可以選擇跳到下一個階段。當在維諾階段,場上已經存在至少一個維諾時,玩家可以選擇飛子以結束本局。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
之前的「當處於法諾階段,不能再放置一個法諾,就自動跳到米諾階段;當處於米諾階段,不能再放置一個米諾,就自動跳到米諾階段;當處於維諾階段,不能再放置一個維諾,就自動跳到飛子階段」仍然保留。
明確給出所要修改的地方和所要替換的代碼。
代码: 全选
更改規則
落子階段有「法諾、米諾、維諾」。在每一個落子階段,都必須至少放置一個本階段的棋子,否則直接判負(兩局的總勝負)。當場上已經存在一個本階段的棋子,則玩家可以選擇跳到下一個階段。當在維諾階段,場上已經存在至少一個維諾時,玩家可以選擇飛子以結束本局。
程式實現:
當一方在法諾放置階段,如果場上已經至少有一個他的法諾,則他可以點擊米諾棋子使之選中,在落子前他可以再選回法諾,但當他落下一個米諾時,他將無法再選中法諾。
當一方在米諾放置階段,如果場上已經至少有一個他的米諾,則他可以點擊維諾棋子使之選中,在落子前他可以再選回米諾,但當他落下一個維諾時,他將無法再選中米諾。當他移動一個場上的米諾時,棋子選中將自動切換到維諾。
之前的「當處於法諾階段,不能再放置一個法諾,就自動跳到米諾階段;當處於米諾階段,不能再放置一個米諾,就自動跳到米諾階段;當處於維諾階段,不能再放置一個維諾,就自動跳到飛子階段」仍然保留。
明確給出所要修改的地方和所要替換的代碼。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
chatgpt sol high
https://gpt.quanquan.space/share/pg687v ... JYHGnBdb98
claude sonnet 5 medium
一個都沒放置就判負——這個是程式自動判定,或說是被動判定。當一方在某階段還沒落一個棋子時,他不能選取下一個階段的棋子。
規則中英說明是否已經同步更新?
https://gpt.quanquan.space/share/pg687v ... JYHGnBdb98
claude sonnet 5 medium
代码: 全选
Now let's apply the edits one by one.Now add the English translation keys:Now add the helper functions (`piecesPlacedCount`, `getManualPhaseAction`) right after `getRequiredPiece`:Now add the `forfeitMatch` function and wire it into `preparePlayerPhase`:Now update `handlePieceFrameClick` to handle the manual skip/revert action:Now add the guard so voluntary flight during the Vino phase requires at least one Vino already placed:Now update `showFinalModal` to branch on a forfeit result, and reset `matchForfeit` on the "play again" / "back to title" paths:Also reset it in `returnToTitle`:Syntax checks out. Now copy to outputs and present:## 修改摘要
我已經直接在檔案裡完成修改(並用 `node --check` 驗證語法),以下是每一處修改的**確切位置**與**取代後的程式碼**,方便你比對或手動套用到別的版本。
### 1. `state` 物件新增欄位(約在 `roundResults: []` 之後)
```js
roundEnded: false,
endInfo: null,
roundResults: [],
matchForfeit: null,
```
### 2. `getRequiredPiece` 之後新增四個輔助函式
```js
// 統計某玩家目前已經放置在場上的某種棋子數量。
function piecesPlacedCount(player, type) {
return state.pieces.filter(piece => piece.owner === player && piece.type === type).length;
}
const NEXT_PIECE_TYPE = { fino: 'mino', mino: 'vino' };
// 場上已經有一個本階段棋子時,玩家可以主動點擊下一階段的棋子提前切換。
function canManualSkip(player, fromType) {
if (!player || state.playerEnded[player] || isMinoFlightPhase(player)) return false;
if (getRequiredPiece(player) !== fromType) return false;
return piecesPlacedCount(player, fromType) >= 1;
}
// 只要還沒有落下下一階段的棋子,玩家可以點回上一階段的棋子取消切換。
function canManualRevert(player, backToType) {
if (!player || state.playerEnded[player] || isMinoFlightPhase(player)) return false;
if (!state.phaseSkipped[player][backToType]) return false;
return piecesPlacedCount(player, NEXT_PIECE_TYPE[backToType]) === 0;
}
// 點擊某個棋子圖示時應該觸發哪個動作,皆非則回傳 null。
function getManualPhaseAction(player, type) {
if (type === 'mino' && canManualSkip(player, 'fino')) return 'skip';
if (type === 'vino' && canManualSkip(player, 'mino')) return 'skip';
if (type === 'fino' && canManualRevert(player, 'fino')) return 'revert';
if (type === 'mino' && canManualRevert(player, 'mino')) return 'revert';
return null;
}
```
**原理**:既有的 `phaseSkipped[player].fino / .mino` 本來只用於「無合法落點→自動跳階段」。我讓「主動點擊切換」直接操作同一個旗標,這樣 `getRequiredPiece`/`getActivePiece` 完全不用改,選中米諾後棋盤上「選子移動米諾、飛子」等所有原本綁在 `active.type==='vino'` 的邏輯,會自動一起生效——這正好對應你說的「移動一個場上的米諾時自動切換到維諾」。
### 3. `renderPieces()`:讓下一階段/上一階段的圖示可以被點擊
把
```js
const locked = count > 0 && (inFlightPhase || (requiredPiece !== null && requiredPiece !== type));
...
frame.disabled = !(selected && type !== 'mino');
```
改成
```js
const manualAction = activePlayer ? getManualPhaseAction(player, type) : null;
const locked =
count > 0 && (inFlightPhase || (requiredPiece !== null && requiredPiece !== type && !manualAction));
...
item.classList.toggle('manual-next', manualAction === 'skip');
item.classList.toggle('manual-back', manualAction === 'revert');
frame.disabled = !((selected && type !== 'mino') || manualAction);
```
### 4. `handlePieceFrameClick`:處理切換點擊
整個函式取代為(前段判斷、方向鍵旋轉邏輯不變,新增 skip/revert 分支):
```js
function handlePieceFrameClick(event, frame) {
if (!state.gameStarted || state.roundEnded || state.gameOver) return;
const player = Number(frame.dataset.player);
const type = frame.dataset.type;
if (player !== state.currentPlayer) {
showToast('notTurn', { player: playerToken(state.currentPlayer) });
return;
}
const manualAction = getManualPhaseAction(player, type);
if (manualAction === 'skip') {
const fromType = type === 'mino' ? 'fino' : 'mino';
state.phaseSkipped[player][fromType] = true;
state.hoverCell = null;
state.selectedMinoId = null;
refreshMinoSelectionUI();
return;
}
if (manualAction === 'revert') {
state.phaseSkipped[player][type] = false;
state.hoverCell = null;
state.selectedMinoId = null;
refreshMinoSelectionUI();
return;
}
const active = getActivePiece();
if (!active) return;
if (active.player !== player || active.type !== type || type === 'mino') return;
const orientation = getOrientationFromFrameClick(event, frame);
state.orientationMemory[player][type] = orientation;
renderPieces();
renderPreview();
}
```
### 5. 「一個都沒放置就判負」:`forfeitMatch` + `preparePlayerPhase`
在 `preparePlayerPhase` 前新增:
```js
function forfeitMatch(player, phaseType) {
const winner = otherPlayer(player);
state.matchForfeit = { loser: player, winner, phase: phaseType };
state.roundEnded = true;
state.gameOver = true;
state.currentPlayer = 0;
state.selectedMinoId = null;
state.hoverCell = null;
state.pendingMinoMove = null;
state.pendingFlight = null;
state.endInfo = { reason: 'forfeit', winner, loser: player, requiredType: phaseType };
commitPendingRoundResult();
renderAll();
window.setTimeout(() => { if (state.gameOver) showFinalModal(); }, 120);
}
```
`preparePlayerPhase` 三處自動跳階段前各加一段零棋子檢查(法諾/米諾/維諾),例如:
```js
if (required === 'fino') {
if (hasAnyLegalPlacement(player, 'fino')) return;
if (piecesPlacedCount(player, 'fino') === 0) { forfeitMatch(player, 'fino'); return; }
state.phaseSkipped[player].fino = true;
showToast('phaseFinoToMino', { player: playerToken(player) }, 1000);
continue;
}
```
(`mino`、`vino` 兩段同理,`vino` 的檢查放在 `canContinueVinoPhase` 失敗、進入 `enterMinoFlightPhase` 之前。)
### 6. 飛子需要先放過維諾(規則 4)
`attemptMinoFlight` 中,在 `wasAutomaticFlightPhase` 判斷後加:
```js
if (!wasAutomaticFlightPhase && piecesPlacedCount(player, 'vino') === 0) {
showToast('flightNeedsVino', { player: playerToken(player) }, 1200);
return;
}
```
### 7. 判負結果視窗:`showFinalModal` 新增分支 + `renderForfeitFinalModal`
在 `showFinalModal` 開頭加:
```js
if (state.matchForfeit) { renderForfeitFinalModal(focusPrimary); return; }
```
並新增 `renderForfeitFinalModal`(使用新翻譯字串 `forfeitSummary`),同時在所有「再玩一次 / 回標題」的地方補上 `state.matchForfeit = null;`(`startGame`、`showFinalModal` 的 primary 按鈕、`returnToTitle`)。
### 8. 新增翻譯字串(中/英各一組)
- `flightNeedsVino`:尚未放置維諾時嘗試飛子的提示。
- `forfeitSummary`:判負結果視窗的說明文字。
### 9. CSS:可點擊的「下一階段/上一階段」圖示樣式
在 `.piece-item.locked .piece-frame` 規則後新增 `.manual-next` / `.manual-back` 樣式(虛線邊框+可點擊游標),與原本「鎖定變半透明」區隔開來。
---
保留不變的部分:原本「無合法落點就自動跳階段」的三段 toast 邏輯完全保留,只是各自前面多了一個「零棋子判負」的檢查。
我已經把改好的檔案存成新版並附上,可以直接下載測試。代码: 全选
沒錯,目前的實作正是這個設計,兩件事分開處理:
**1. 判負(forfeitMatch)— 完全被動、程式自動判定**
只有一個觸發點:`preparePlayerPhase` 裡,當某階段**已經沒有合法落點**(`hasAnyLegalPlacement` 回傳 false)、系統準備自動跳到下一階段時,才會先檢查 `piecesPlacedCount(player, type) === 0`,是的話呼叫 `forfeitMatch`。這段完全在玩家行動之外自動觸發,玩家不能主動叫出判負,也不能主動迴避——只要真的無棋可下又一子未落,就一定會被判負。
```js
if (required === 'fino') {
if (hasAnyLegalPlacement(player, 'fino')) return;
if (piecesPlacedCount(player, 'fino') === 0) {
forfeitMatch(player, 'fino'); // 被動判定,非玩家操作觸發
return;
}
state.phaseSkipped[player].fino = true;
...
}
```
**2. 主動選取下一階段棋子 — 被 `canManualSkip` 擋住**
```js
function canManualSkip(player, fromType) {
if (!player || state.playerEnded[player] || isMinoFlightPhase(player)) return false;
if (getRequiredPiece(player) !== fromType) return false;
return piecesPlacedCount(player, fromType) >= 1; // 一子未落 → 回傳 false
}
```
`getManualPhaseAction` 靠這個函式決定要不要回傳 `'skip'`。只要本階段還一子未落,`piecesPlacedCount(...) >= 1` 就是 `false`,`manualAction` 就是 `null`;再往下看 `renderPieces()`:
```js
const manualAction = activePlayer ? getManualPhaseAction(player, type) : null;
const locked = count > 0 && (inFlightPhase || (requiredPiece !== null && requiredPiece !== type && !manualAction));
...
frame.disabled = !((selected && type !== 'mino') || manualAction);
```
`manualAction` 為 `null` 時,下一階段那顆棋子圖示會維持 `locked`、`frame.disabled = true`,玩家在畫面上根本點不動它,`handlePieceFrameClick` 也不會有效果。
所以兩條路徑是分開且一致的:
- 只要一子未落,**畫面上就選不到下一階段的棋子**(主動路徑被鎖死);
- 真的走到「無棋可下」那一步時,**程式自動判負**(被動路徑),不需要、也不可能由玩家的點擊觸發。
這部分不需要再改程式碼,目前的行為已經符合你描述的規則。如果你想要的話,我可以另外在畫面上加一個小提示(例如把「一子未落」的圖示 hover 時顯示 tooltip,說明「必須先放置本階段棋子才能提前切換」),需要的話告訴我。代码: 全选
沒有——我之前只加了 toast 文案和結果視窗的翻譯字串,**沒有更新「玩法說明」彈窗(規則彈窗)裡的中英文說明內容**。讓我檢查一下現況,再補上。確實還沒同步——我之前只更新了 toast 提示與結算彈窗的翻譯字串,**玩法說明(規則)彈窗**裡的中英文條文都還是舊版,沒有提到「強制放置/判負」和「提前切換階段」規則,「飛子」條文也沒提到「需先放過一個維諾」的新限制。我現在補上。16 個 `rule-index` = 中英各 8 條,語法也通過檢查。## 已補上的修改
規則彈窗(中英文都有)原本只到「07 自動飛行階段」「08 計分」(原本編號是 06/07),我新增了一條規則並全部重新編號:
| 編號 | 英文標題 | 中文標題 | 內容 |
|---|---|---|---|
| 01 | Pieces and order | 棋子與放置順序 | 不變 |
| 02 | Fino placement | 法諾放置規則 | 不變 |
| 03 | Mino placement | 米諾放置規則 | 不變 |
| **04(新增)** | **Mandatory placement and early phase switch** | **強制放置與提前切換階段** | 說明「每階段至少放一子否則判負(兩局總勝負)」,以及「已放置一子後可點下一階段圖示提前切換,落下前可點回,落下後永久生效」 |
| 05 | Vino and normal Mino movement | 維諾放置與米諾正常移動 | 不變 |
| 06 | Flying a Mino to finish | 飛子結束本方遊戲 | **加了一句**:「只有在已放置至少一個維諾後才能主動飛子結束」 |
| 07 | Automatic flight phase and move markers | 自動飛行階段與移動標記 | 不變 |
| 08 | Scoring | 計分 | 不變 |
已重新驗證整份檔案的 JS 語法(`node --check`)沒有問題,附上的檔案已是最新版。- 附件
-
vino2247claude.html.7z- (24.81 KiB) 已下载 2 次
-
vino2246claude.html.7z- (24.35 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
當處於米諾階段,如果點擊了維諾,然後移動了場上的一個米諾,這時控制區中就不能再切換到米諾了。只有再次點擊剛移動的米諾,使其退回原來的位置,這時才可以切換回米諾。
claude sonnet 5 medium
claude sonnet 5 medium
代码: 全选
Good — `executeMinoMove` routes through `performNormalMinoMove`, which sets `state.pendingMinoMove = { player: state.currentPlayer, ... }`, so this covers both click-to-move and arrow-key moves. Let's syntax-check and re-deliver:## 修改內容
問題出在 `canManualRevert`:原本只檢查「維諾還沒落下」(`piecesPlacedCount(vino) === 0`),沒考慮到「米諾已經被移動、尚未收回」這個中間狀態。移動米諾雖然不消耗庫存,但代表玩家已經實質進入維諾階段的操作,這時候仍然允許切回米諾是錯的。
### 修改的地方:`canManualRevert` 函式
取代為:
```js
// 只要還沒有落下下一階段的棋子,玩家可以點回上一階段的棋子取消切換。
// 但如果米諾已經被移動且尚未收回,代表已進入維諾階段的操作,不能再切回米諾。
function canManualRevert(player, backToType) {
if (!player || state.playerEnded[player] || isMinoFlightPhase(player)) {
return false;
}
if (!state.phaseSkipped[player][backToType]) {
return false;
}
if (piecesPlacedCount(player, NEXT_PIECE_TYPE[backToType]) !== 0) {
return false;
}
if (
backToType === 'mino' &&
state.pendingMinoMove &&
state.pendingMinoMove.player === player
) {
return false;
}
return true;
}
```
### 為什麼不用改別的地方
- `performNormalMinoMove`(無論是拖曳點擊移動,還是方向鍵 `executeMinoMove` 觸發)都會設定 `state.pendingMinoMove = { player: state.currentPlayer, pieceId, from, to }`,所以新增的判斷能同時攔住兩種移動方式。
- `revertPendingMinoMove`(玩家再次點擊剛移動的那顆米諾,退回原位)會把 `state.pendingMinoMove` 設回 `null`,並呼叫 `renderAll()` 觸發重新渲染。這時 `canManualRevert` 重新求值,`pendingMinoMove` 已經是 `null`,只要維諾也還沒落下,米諾圖示就會自動恢復成可點擊,不需要額外程式碼。
行為結果:
- 米諾階段點擊維諾圖示 → 切到維諾(`phaseSkipped.mino = true`)。
- 移動場上一顆米諾 → `pendingMinoMove` 被設定 → 這時控制區的米諾圖示鎖死,點不回去。
- 再點一次那顆剛移動的米諾,讓它退回原位(`revertPendingMinoMove`)→ `pendingMinoMove` 清空 → 米諾圖示恢復可點擊,可以切回米諾。- 附件
-
vino2248.html.7z- (24.87 KiB) 已下载 1 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 寡人新創作的弈棋「維諾廣場」竟然十分好玩!
代码: 全选
1. 記憶體與物件重用(Memory Reuse)
物件池(Object Pooling):不要在每一次搜尋時 new Node(),預先分配一個 Tree Node Pool,搜尋結束後重置指標(Pointer),徹底避免 GC。
扁平化樹狀結構(Flat Array Tree):將 MCTS 的節點直接儲存在一個大陣列中,用索引(Index)代替物件引用(Reference/Pointer)。
2. Rollout / Playout(模擬階段)優化
MCTS 最耗時的地方通常是模擬到局末。
輕量化模擬(Lightweight Rollout):只維護必要的棋盤狀態,不觸發任何完整遊戲邏輯(如 UI 更新、複雜規則檢查)。
啟發式剪枝(Heuristic / Heavy Playout):完全隨機的落子效率很低。引入簡單的領域知識(例如:不走明顯的死棋、優先走連子點),可以用極少的計算量換取更精確的勝率估算。
RAVE / Rapid Action Value Estimation:利用「全局移動代換」(All-Moves-As-First)假設,讓更新資訊共享給其他相關節點,大幅加快收斂速度。
重用搜尋樹(Tree Reuse):對手落子後,保留原搜尋樹中對應的子樹作為下一輪搜尋的根節點,不重新從頭建立。
這樣會否因為上一次的搜尋已經決定了主流方向,而使本次搜尋沒有意義?
動態調整探索常數 $C$(Optional):如果你發現舊樹佔用的訪問次數過高(例如根節點已有 10 萬次訪問),可能導致新展開的節點 UCT 權重被稀釋,可以適度在換根時對舊數據進行衰減(Discounting/Decay),例如將所有舊節點的訪問數與勝場數同比例乘以 $0.5$ 或 $0.8$,讓新搜尋更容易改變舊觀點。
記憶體回收(Garbage Collection):重用子樹時,記得要將未被選中的其他兄弟分支(Sibling Nodes) 的記憶體正確釋放回你的「扁平化陣列(Node Pool)」中,否則搜尋幾十步後池子就會被廢棄節點塞爆。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
-
- 相似主题
- 回复总数
- 阅读次数
- 最新帖子
在线用户
正浏览此版面之用户: 没有注册用户 和 96 访客