共享会话
🛠️ 程式修改與勝負邏輯
分享于 2026年8月20日 23:35改進程式 檢查html結構:
檢查程式代碼中的html結構是否正確,如不正確,請給出修復方案。
增加勝負消息框:
之前在判勝負時會把文字直接寫到棋盤上,現在要新增「勝負消息框」,其內容下方將出現「重新開始」,點擊則進入新的一局。其右上角有一個svg最小化按鈕,點擊之後此消息框將消失,而左上角將出現「恢復窗口svg按鈕」,其css位置參考#fullscreen(注意#fullscreen是在右邊)。
更改設置窗口:
把設置窗口(將不再只是AI設置窗口,而是遊戲設置窗口)的寬度改為更寬一些(電腦端比300px更寬一倍左右,手機端要改為90%),增加大標題「哥尼斯堡——路與橋」,英文是「Königsberg - Path & Bridge」。
增加「語言選擇」欄目,下方是「English|中文」語言切換按鈕(水平居中),「English」和「中文」是分開的,所選中的將高亮。一開始默認是中文。同時在script中增加多國語言,包括消息框,以及設置窗口中的文字。
新增一個「教學模式」欄目在「語言選擇」下方,其內容是一個「開始教學模式」按鈕(水平居中)。如果遊戲開始,則這個欄目將消失。「開始教學模式」按鈕的功能將在以後說明。
把原來的「AI搜尋設定」欄目放在「教學模式」欄目的下方。
更改控制行動邏輯:
當輪到一方行動時,不需要再點擊一次本方人物,也不會再出現紅色折線,將自動使其進入行動狀態。他所能走到的circle的stroke將變為淺紅色,stroke-width將變為4。每走一步更新。當他沒有走至少一步時,「打勾」按鈕將不會出現。
當一方不能走至少一步時,程式將直接判其「困斃」輸棋,並彈出勝負消息框。當一方會被對方射殺時,將直接顯示紅色折線並判負,同時彈出勝負消息框。當一方在仍可放木橋時沒放至少一個木橋,則直接判負並彈出勝負消息框。如果判為和棋也要彈出勝負消息框。
注意將AI控制一並修改。AI思考消息框不必改動。
回答要求:
明確給出所要修改的地方和所要替換的代碼。
QuanAI以下修改以你上傳的 konigsberg20260606cannotmove7954.html 為基礎。為避免貼出整個 10 萬多字元檔案,下面按「搜尋原代碼 → 替換代碼」給出可直接套用的修改。
一、HTML 結構檢查結果
目前 HTML 的開始、結束標籤基本配對正確,沒有明顯缺失的 </div>、</svg>、</script> 或 </body>。
但有以下問題需要修正:
- 兩個
<style>都放在<body>內。瀏覽器通常能執行,但不符合較嚴格的 HTML 結構規範,應移入<head>。 - 預設語言是中文,但目前是:
<html lang="en">應改成:
<html lang="zh-Hant">- SVG 的:
viewbox="0 0 720 480"大小寫錯誤,應改成:
viewBox="0 0 720 480"- 文件標題應改為遊戲正式名稱。
- 原始
judgeResult()中的current_history、current_history_str、lose_num沒有用let/const宣告,會建立隱式全域變數。下方替換後的judgeResult()已修正。 - 設定窗口原本使用大量沒有語義的
<div>,建議改為h1、section、button等結構;下方已提供完整替換碼。
修改 <head>
將開頭改為:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>哥尼斯堡——路與橋</title>
<style>
/*
* 把目前位於 body 內的兩個 <style>...</style>
* 中的 CSS 全部移到這裡。
*
* 然後再加入本文下一節提供的新增/替換 CSS。
*/
</style>
</head>
<body>移動完成後,刪除 <body> 內原本的兩組 <style>...</style> 標籤,避免重複。
二、替換設定窗口並新增勝負消息框
搜尋從:
<div id="thinking_ui">🤔 思考中...</div>
<div id="mcts_modal">開始,到原本 #mcts_modal 的結束 </div> 為止,整段替換成:
<div id="thinking_ui">🤔 思考中...</div>
<div id="mcts_modal"
role="dialog"
aria-modal="true"
aria-labelledby="game_settings_title">
<button type="button"
id="mcts_close_btn"
class="mcts_close"
aria-label="關閉"
title="關閉">×</button>
<h1 id="game_settings_title" data-i18n="gameTitle">
哥尼斯堡——路與橋
</h1>
<!-- 語言選擇 -->
<section class="settings_section">
<h2 data-i18n="languageSelection">語言選擇</h2>
<div class="language_switch" role="group" aria-label="語言選擇">
<button type="button"
id="lang_en"
class="language_btn"
data-lang="en">English</button>
<span class="language_separator" aria-hidden="true">|</span>
<button type="button"
id="lang_zh"
class="language_btn active"
data-lang="zh">中文</button>
</div>
</section>
<!-- 教學模式 -->
<section id="tutorial_section" class="settings_section">
<h2 data-i18n="tutorialMode">教學模式</h2>
<div class="center_button_row">
<button type="button"
id="tutorial_start_btn"
class="primary_setting_button"
data-i18n="startTutorial">
開始教學模式
</button>
</div>
</section>
<!-- AI 搜尋設定 -->
<section class="settings_section">
<h2 data-i18n="aiSearchSettings">AI 搜尋設定</h2>
<div class="settings_toolbar">
<button type="button"
id="toggle_params"
class="toggle_params_button"
data-expanded="false">
▸ 展開
</button>
<div id="mode_buttons" class="mode_buttons">
<button type="button"
class="mode_btn"
data-mode="easy"
data-i18n="easy">簡單</button>
<button type="button"
class="mode_btn"
data-mode="hard"
data-i18n="hard">困難</button>
<button type="button"
class="mode_btn"
data-mode="expert"
data-i18n="expert">專家</button>
<button type="button"
class="mode_btn"
data-mode="custom"
data-i18n="custom">自定</button>
</div>
</div>
<div id="params_container" style="display:none;">
<div class="mcts_row">
<label for="mcts_sims" data-i18n="simulations">模擬次數:</label>
<input type="number"
id="mcts_sims"
class="mcts_param_input"
step="1"
min="1">
</div>
<div class="mcts_row">
<label for="mcts_depth" data-i18n="searchDepth">搜尋深度:</label>
<input type="number"
id="mcts_depth"
class="mcts_param_input"
step="1"
min="1">
</div>
<div class="mcts_row">
<label for="mcts_max_path" data-i18n="maxSteps">最大步數:</label>
<input type="number"
id="mcts_max_path"
class="mcts_param_input"
max="9"
step="1"
min="1">
</div>
<div class="mcts_row">
<label for="mcts_first_path" data-i18n="firstTurnLimit">首回限步:</label>
<input type="number"
id="mcts_first_path"
class="mcts_param_input"
max="9"
step="1"
min="1">
</div>
<div class="forced_search_group">
<div class="forced_search_title"
data-i18n="forcedSearch">
必勝搜尋(橋數 ➔ 深度):
</div>
<div class="mcts_row depth_row">
<label for="fcd_depth1">7</label>
<input type="number"
id="fcd_depth1"
class="mcts_param_input depth_input"
step="1"
min="1">
</div>
<div class="mcts_row depth_row">
<label for="fcd_depth2">12</label>
<input type="number"
id="fcd_depth2"
class="mcts_param_input depth_input"
step="1"
min="1">
</div>
<div class="mcts_row depth_row">
<label for="fcd_depth3">16</label>
<input type="number"
id="fcd_depth3"
class="mcts_param_input depth_input"
step="1"
min="1">
</div>
<div class="mcts_row depth_row">
<label for="fcd_depth4">19</label>
<input type="number"
id="fcd_depth4"
class="mcts_param_input depth_input"
step="1"
min="1">
</div>
</div>
</div>
<hr>
<div class="mcts_row ai_player_row">
<label>
<input type="checkbox" id="mcts_auto_blue">
<span data-i18n="aiBlue">AI 藍方</span>
</label>
<label>
<input type="checkbox" checked id="mcts_auto_green">
<span data-i18n="aiGreen">AI 綠方</span>
</label>
</div>
<button type="button"
id="mcts_play_btn"
class="full_width_primary"
style="display:none;"
data-i18n="startGame">
開始遊戲
</button>
<button type="button"
id="mcts_start_btn"
class="full_width_button"
data-i18n="startSearch">
開始搜尋
</button>
<div id="mcts_results"
class="mcts_results"
aria-live="polite"></div>
</section>
</div>然後在 #setting_div 結束標籤之後、#fullscreen 之前,加入勝負消息框和恢復按鈕:
<!-- 勝負消息框 -->
<div id="result_modal"
class="hide"
role="dialog"
aria-modal="true"
aria-labelledby="result_title">
<button type="button"
id="result_minimize"
class="result_minimize"
aria-label="最小化"
title="最小化">
<svg viewBox="0 0 24 24"
width="24"
height="24"
fill="currentColor"
aria-hidden="true">
<path d="M5 11h14v2H5z"></path>
</svg>
</button>
<h2 id="result_title">遊戲結果</h2>
<div id="result_message" aria-live="assertive"></div>
<button type="button"
id="result_restart"
class="result_restart">
重新開始
</button>
</div>
<!-- 最小化後顯示在左上角 -->
<div id="result_restore"
class="trbtn hide"
role="button"
tabindex="0"
aria-label="恢復勝負窗口"
title="恢復勝負窗口">
<svg viewBox="0 0 24 24"
fill="white"
width="24"
height="24"
aria-hidden="true">
<path d="M5 5h14v14H5V5zm2 2v10h10V7H7zm2 2h6v2H9V9zm0 4h6v2H9v-2z"></path>
</svg>
</div>同時把 SVG 棋盤的:
viewbox="0 0 720 480"替換成:
viewBox="0 0 720 480"三、CSS 修改
3.1 替換原本的 #mcts_modal
刪除原本的:
#mcts_modal {
position: absolute;
top: 10%;
left: 50%;
transform: translateX(-50%);
background: white;
border: 2px solid #333;
padding: 15px;
z-index: 20;
display: none;
width: 300px;
max-height: 80%;
overflow-y: auto;
font-size: 16px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
#mcts_modal h3 {
margin-top: 0;
margin-bottom: 10px;
}替換為:
#mcts_modal {
position: absolute;
top: 5%;
left: 50%;
transform: translateX(-50%);
display: none;
width: 620px;
max-width: calc(100% - 32px);
max-height: 90%;
padding: 22px 24px;
overflow-y: auto;
color: #333;
background: #fff;
border: 2px solid #333;
border-radius: 12px;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
z-index: 20;
font-size: 16px;
}
#mcts_modal h1 {
margin: 0 36px 22px;
text-align: center;
font-size: 30px;
line-height: 1.25;
color: #24214f;
}
#mcts_modal h2 {
margin: 0 0 12px;
text-align: center;
font-size: 20px;
line-height: 1.3;
}
.settings_section {
margin: 0 0 20px;
padding: 16px;
border: 1px solid #d7d7df;
border-radius: 10px;
background: #fafaff;
}
.settings_section:last-child {
margin-bottom: 0;
}
.language_switch {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.language_btn {
min-width: 92px;
padding: 7px 14px;
color: #333;
background: #fff;
border: 1px solid #aaa;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
}
.language_btn:hover {
background: #f0efff;
}
.language_btn.active {
color: #fff;
background: #8278f5;
border-color: #8278f5;
font-weight: bold;
box-shadow: 0 0 0 2px rgba(130, 120, 245, 0.2);
}
.language_separator {
color: #777;
font-size: 20px;
}
.center_button_row {
display: flex;
justify-content: center;
}
.primary_setting_button,
.full_width_primary,
.result_restart {
color: #fff;
background: #8278f5;
border: 0;
border-radius: 6px;
cursor: pointer;
}
.primary_setting_button {
padding: 9px 22px;
font-size: 16px;
}
.full_width_primary,
.full_width_button {
width: 100%;
margin-top: 7px;
padding: 9px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.full_width_button {
color: #333;
background: #f4f4f4;
border: 1px solid #aaa;
}
.settings_toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.toggle_params_button {
flex: 0 0 auto;
padding: 5px 8px;
color: #333;
background: transparent;
border: 0;
cursor: pointer;
font-weight: bold;
font-size: 15px;
}
.mode_buttons {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 5px;
}
.forced_search_group {
margin: 12px 0 5px;
}
.forced_search_title {
margin-bottom: 7px;
}
.depth_row {
padding-left: 12px;
}
.depth_input {
width: 48px !important;
}
.ai_player_row {
align-items: center;
}
.ai_player_row label {
display: flex;
align-items: center;
gap: 5px;
}
.mcts_results {
margin-top: 10px;
font-size: 14px;
}
.mcts_close {
position: absolute;
top: 7px;
right: 10px;
width: 34px;
height: 34px;
padding: 0;
color: #333;
background: transparent;
border: 0;
border-radius: 50%;
cursor: pointer;
font-weight: bold;
font-size: 26px;
line-height: 34px;
}
.mcts_close:hover {
color: #fff;
background: #8f2929;
}3.2 加入勝負框及可到達點樣式
在 CSS 最後加入:
/* 當前位置能走到的節點 */
.reachable-dot {
stroke: #f0a0a0 !important;
stroke-width: 4 !important;
}
/* 勝負消息框 */
#result_modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 420px;
max-width: 88%;
min-height: 190px;
padding: 30px 28px 24px;
color: #2d2d2d;
background: rgba(255, 255, 255, 0.97);
border: 3px solid #8278f5;
border-radius: 14px;
box-shadow: 0 10px 36px rgba(0, 0, 0, 0.42);
z-index: 30;
text-align: center;
}
#result_modal h2 {
margin: 0 34px 18px;
color: #3e377f;
font-size: 27px;
}
#result_message {
min-height: 54px;
margin-bottom: 20px;
font-size: 21px;
line-height: 1.55;
}
.result_minimize {
position: absolute;
top: 8px;
right: 8px;
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
padding: 0;
color: #333;
background: #eee;
border: 1px solid #aaa;
border-radius: 6px;
cursor: pointer;
}
.result_minimize:hover {
color: #fff;
background: #8278f5;
border-color: #8278f5;
}
.result_restart {
min-width: 150px;
padding: 10px 20px;
font-size: 18px;
}
.result_restart:hover,
.primary_setting_button:hover,
.full_width_primary:hover {
background: #655bdc;
}
/*
* 位置與 #fullscreen 同樣是 top:7px,
* 但 #fullscreen 在右邊,恢復按鈕在左邊。
*/
#result_restore {
top: 7px;
left: 7px;
right: auto;
background: #8278f5;
z-index: 29;
}
/* 手機端設定窗口為 90% */
@media (max-width: 600px) {
#mcts_modal {
top: 3%;
width: 90%;
max-width: 90%;
max-height: 94%;
padding: 16px 14px;
font-size: 15px;
}
#mcts_modal h1 {
margin: 4px 32px 18px;
font-size: 23px;
}
#mcts_modal h2 {
font-size: 18px;
}
.settings_section {
padding: 13px 10px;
}
.settings_toolbar {
flex-direction: column;
align-items: stretch;
}
.mode_buttons {
justify-content: center;
}
.mcts_row {
gap: 10px;
}
#result_modal {
width: 88%;
padding: 28px 18px 22px;
}
#result_modal h2 {
font-size: 23px;
}
#result_message {
font-size: 18px;
}
}注意不要在 #result_modal 中寫:
display: block;否則可能蓋過 .hide { display:none; }。
四、加入多國語言程式
在:
let lastSearchTime = 0;之後加入:
let currentLanguage = "zh";
const I18N = {
zh: {
gameTitle: "哥尼斯堡——路與橋",
languageSelection: "語言選擇",
tutorialMode: "教學模式",
startTutorial: "開始教學模式",
aiSearchSettings: "AI 搜尋設定",
expand: "展開",
collapse: "收回",
easy: "簡單",
hard: "困難",
expert: "專家",
custom: "自定",
simulations: "模擬次數:",
searchDepth: "搜尋深度:",
maxSteps: "最大步數:",
firstTurnLimit: "首回限步:",
forcedSearch: "必勝搜尋(橋數 ➔ 深度):",
aiBlue: "AI 藍方",
aiGreen: "AI 綠方",
startGame: "開始遊戲",
startSearch: "開始搜尋",
stopSearch: "停止搜尋",
close: "關閉",
resultTitle: "遊戲結果",
restart: "重新開始",
minimize: "最小化",
restoreResult: "恢復勝負窗口",
blue: "藍方",
green: "綠方",
shotResult: "{winner}勝利!{loser}被射殺。",
trappedResult: "{loser}無路可走而困斃,{winner}勝利!",
noBridgeResult: "{loser}沒有放置至少一座木橋,{winner}勝利!",
drawResult: "本局和棋!",
searchComplete: "搜尋完成!共 {count} 條路徑:",
totalTime: "總耗時:{time} 秒",
previousPage: "上一頁",
nextPage: "下一頁",
page: "頁數:{current} / {total}",
fatalMove: "💀 致命步",
forcedLoss: "⚠️ 必敗步",
forcedWin: "🎯 必殺走法",
winRate: "勝率:{rate}%",
searchOnlyAtTurnStart: "請在輪到玩家且尚未移動時搜尋。",
searchingPleaseWait: "搜尋中,請稍候……",
searchAborted: "搜尋已中止。",
noLegalRoute: "沒有能完成合法回合的路徑。"
},
en: {
gameTitle: "Königsberg - Path & Bridge",
languageSelection: "Language",
tutorialMode: "Tutorial Mode",
startTutorial: "Start Tutorial Mode",
aiSearchSettings: "AI Search Settings",
expand: "Expand",
collapse: "Collapse",
easy: "Easy",
hard: "Hard",
expert: "Expert",
custom: "Custom",
simulations: "Simulations:",
searchDepth: "Search depth:",
maxSteps: "Maximum steps:",
firstTurnLimit: "First-turn limit:",
forcedSearch: "Forced-win search (bridges ➔ depth):",
aiBlue: "AI Blue",
aiGreen: "AI Green",
startGame: "Start Game",
startSearch: "Start Search",
stopSearch: "Stop Search",
close: "Close",
resultTitle: "Game Result",
restart: "Restart",
minimize: "Minimize",
restoreResult: "Restore result window",
blue: "Blue",
green: "Green",
shotResult: "{winner} wins! {loser} was shot.",
trappedResult: "{loser} is trapped with no available move. {winner} wins!",
noBridgeResult: "{loser} did not place at least one wooden bridge. {winner} wins!",
drawResult: "The game is a draw!",
searchComplete: "Search complete! {count} paths found:",
totalTime: "Total time: {time} seconds",
previousPage: "Previous",
nextPage: "Next",
page: "Page: {current} / {total}",
fatalMove: "💀 Fatal move",
forcedLoss: "⚠️ Forced loss",
forcedWin: "🎯 Forced win",
winRate: "Win rate: {rate}%",
searchOnlyAtTurnStart: "Search only at the beginning of the player's turn.",
searchingPleaseWait: "Searching, please wait...",
searchAborted: "Search aborted.",
noLegalRoute: "No route can complete a legal turn."
}
};
function t(key, variables = {}) {
const languageTable = I18N[currentLanguage] || I18N.zh;
let text = languageTable[key] ?? I18N.zh[key] ?? key;
for (const [name, value] of Object.entries(variables)) {
text = text.replaceAll(`{${name}}`, String(value));
}
return text;
}
function playerName(playerIndex) {
return t(playerIndex === 0 ? "blue" : "green");
}
function applyLanguage(language) {
currentLanguage = language === "en" ? "en" : "zh";
document.documentElement.lang =
currentLanguage === "en" ? "en" : "zh-Hant";
document.title = t("gameTitle");
document.querySelectorAll("[data-i18n]").forEach(element => {
const key = element.getAttribute("data-i18n");
element.textContent = t(key);
});
document.querySelectorAll(".language_btn").forEach(button => {
const selected = button.dataset.lang === currentLanguage;
button.classList.toggle("active", selected);
button.setAttribute("aria-pressed", selected ? "true" : "false");
});
const toggleButton = document.getElementById("toggle_params");
if (toggleButton) {
const expanded = toggleButton.dataset.expanded === "true";
toggleButton.textContent =
expanded ? `▾ ${t("collapse")}` : `▸ ${t("expand")}`;
}
const closeButton = document.getElementById("mcts_close_btn");
if (closeButton) {
closeButton.title = t("close");
closeButton.setAttribute("aria-label", t("close"));
}
const minimizeButton = document.getElementById("result_minimize");
if (minimizeButton) {
minimizeButton.title = t("minimize");
minimizeButton.setAttribute("aria-label", t("minimize"));
}
const restoreButton = document.getElementById("result_restore");
if (restoreButton) {
restoreButton.title = t("restoreResult");
restoreButton.setAttribute("aria-label", t("restoreResult"));
}
const resultTitle = document.getElementById("result_title");
const restartButton = document.getElementById("result_restart");
if (resultTitle) resultTitle.textContent = t("resultTitle");
if (restartButton) restartButton.textContent = t("restart");
const startSearchButton = document.getElementById("mcts_start_btn");
if (startSearchButton) {
startSearchButton.textContent =
isMctsRunning ? t("stopSearch") : t("startSearch");
}
if (typeof lastGameResult !== "undefined" && lastGameResult) {
renderGameResult();
}
if (
typeof currentMctsResults !== "undefined" &&
currentMctsResults.length > 0
) {
renderMctsResults();
}
}五、修改 window.onload
在 window.onload 中,將模式按鈕事件:
document.querySelectorAll('.mode_btn').forEach(btn => {
btn.addEventListener('click', (e) => setMode(e.target.getAttribute('data-mode')));
});替換成:
document.querySelectorAll(".mode_btn").forEach(button => {
button.addEventListener("click", event => {
setMode(event.currentTarget.dataset.mode);
});
});將原本「展開/收回」事件整段替換成:
document.getElementById("toggle_params").addEventListener("click", function() {
const container = document.getElementById("params_container");
const expanding = container.style.display === "none";
container.style.display = expanding ? "block" : "none";
this.dataset.expanded = expanding ? "true" : "false";
this.textContent =
expanding ? `▾ ${t("collapse")}` : `▸ ${t("expand")}`;
});在 window.onload 的結尾、openMctsModal(); 之前加入:
document.querySelectorAll(".language_btn").forEach(button => {
button.addEventListener("click", () => {
applyLanguage(button.dataset.lang);
});
});
document.getElementById("mcts_close_btn").addEventListener("click", () => {
closeMctsModal();
});
/*
* 教學功能預留。
* 目前按鈕可點擊,但不改變遊戲狀態。
*/
document.getElementById("tutorial_start_btn").addEventListener("click", () => {
// 教學模式功能將在之後加入。
});
applyLanguage("zh");保留:
openMctsModal();因此初次開啟時預設顯示中文。
六、新增勝負窗口控制
在元素變數區,搜尋:
const exportsvgbtn = document.getElementById("exportsvg");在其後加入:
const resultModal = document.getElementById("result_modal");
const resultTitle = document.getElementById("result_title");
const resultMessage = document.getElementById("result_message");
const resultRestart = document.getElementById("result_restart");
const resultMinimize = document.getElementById("result_minimize");
const resultRestore = document.getElementById("result_restore");
const tutorialSection = document.getElementById("tutorial_section");
let lastGameResult = null;在變數宣告區後加入:
function updateTutorialVisibility() {
if (!tutorialSection) return;
/*
* 只有尚未開始任何一局,即 game_status === 0 時顯示。
* 遊戲開始、結束或進入歷史模式後都隱藏。
*/
tutorialSection.classList.toggle("hide", game_status !== 0);
}
function renderGameResult() {
if (!lastGameResult) return;
resultTitle.textContent = t("resultTitle");
if (lastGameResult.kind === "draw") {
resultMessage.textContent = t("drawResult");
return;
}
const variables = {
winner: playerName(lastGameResult.winner),
loser: playerName(lastGameResult.loser)
};
if (lastGameResult.kind === "shot") {
resultMessage.textContent = t("shotResult", variables);
} else if (lastGameResult.kind === "trapped") {
resultMessage.textContent = t("trappedResult", variables);
} else if (lastGameResult.kind === "noBridge") {
resultMessage.textContent = t("noBridgeResult", variables);
}
}
function showGameResult(kind, winner = null, loser = null) {
lastGameResult = {
kind,
winner,
loser
};
renderGameResult();
resultModal.classList.remove("hide");
resultRestore.classList.add("hide");
}
function hideGameResult() {
lastGameResult = null;
resultModal.classList.add("hide");
resultRestore.classList.add("hide");
}
function clearReachableDots() {
dots.forEach(dot => {
dot.classList.remove("reachable-dot");
});
}
function endGame(kind, winner = null, loser = null) {
game_status = 7;
abortMcts = true;
isMctsRunning = false;
clearTimeout(autoPlayTimer);
clearReachableDots();
route.classList.add("hide");
btn_finish.classList.add("hide");
btn_back.classList.add("hide");
btn_cancel.classList.add("hide");
btn_play.classList.remove("hide");
document.getElementById("thinking_ui").style.display = "none";
document.getElementById("mcts_start_btn").textContent = t("startSearch");
men_top.forEach(man => man.classList.add("hide"));
men_bottom.forEach(man => man.classList.remove("hide"));
men_bottom[0].setAttribute("stroke", "none");
men_bottom[1].setAttribute("stroke", "none");
if (kind === "draw") {
men_bottom[0].setAttribute("stroke", "#e77");
men_bottom[1].setAttribute("stroke", "#e77");
} else if (loser !== null) {
men_bottom[loser].setAttribute("stroke", "#e77");
}
detail_div.textContent = "";
showGameResult(kind, winner, loser);
updateTutorialVisibility();
displayGame();
}
resultMinimize.addEventListener("click", () => {
resultModal.classList.add("hide");
resultRestore.classList.remove("hide");
});
function restoreResultWindow() {
if (!lastGameResult) return;
resultRestore.classList.add("hide");
resultModal.classList.remove("hide");
}
resultRestore.addEventListener("click", restoreResultWindow);
resultRestore.addEventListener("keydown", event => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
restoreResultWindow();
}
});
resultRestart.addEventListener("click", () => {
hideGameResult();
btn_play.onclick();
});七、自動進入行動狀態及標示可走節點
在 addShotLine() 後面加入以下函式:
function getAvailableNextDots(lastDot) {
if (lastDot === undefined || lastDot === null) return [];
const visited = new Set(route_tmp);
const result = new Set();
for (const nextDot of empty_map[lastDot] || []) {
if (!visited.has(nextDot)) result.add(nextDot);
}
for (const nextDot of arrow_map[lastDot] || []) {
if (!visited.has(nextDot)) result.add(nextDot);
}
return Array.from(result);
}
function updateReachableDots() {
clearReachableDots();
if (![2, 4].includes(game_status) || route_tmp.length === 0) {
return;
}
const lastDot = route_tmp[route_tmp.length - 1];
for (const dotIndex of getAvailableNextDots(lastDot)) {
dots[dotIndex].classList.add("reachable-dot");
}
}
/*
* 找出射手沿有向橋到達目標的實際路線。
* 回傳格式為 [起點, 下一點, 下一點, ...]。
*/
function findShotRoute(shooterDot, targetDot) {
const queue = [shooterDot];
const parent = new Map();
parent.set(shooterDot, null);
while (queue.length > 0) {
const currentDot = queue.shift();
for (const nextDot of arrow_map[currentDot] || []) {
if (parent.has(nextDot)) continue;
parent.set(nextDot, currentDot);
if (nextDot === targetDot) {
const nodeRoute = [];
let traceDot = targetDot;
while (traceDot !== null) {
nodeRoute.push(traceDot);
traceDot = parent.get(traceDot);
}
nodeRoute.reverse();
const lineArray = [];
for (let index = 0; index < nodeRoute.length - 1; index++) {
lineArray.push(nodeRoute[index], nodeRoute[index + 1]);
}
return lineArray;
}
queue.push(nextDot);
}
}
return null;
}
function scheduleAutoTurn() {
clearTimeout(autoPlayTimer);
if (
![2, 4].includes(game_status) ||
route_tmp.length !== 1 ||
document.getElementById("mcts_modal").style.display === "block"
) {
return;
}
const autoBlue =
document.getElementById("mcts_auto_blue")?.checked === true;
const autoGreen =
document.getElementById("mcts_auto_green")?.checked === true;
if (
(current_man === 0 && autoBlue) ||
(current_man === 1 && autoGreen)
) {
autoPlayTimer = setTimeout(() => {
updateMctsParams();
runMCTS(true);
}, 500);
}
}
/*
* 每一回合開始時自動執行,不再要求玩家點擊本方人物。
*/
function beginTurn() {
if (![1, 3].includes(game_status)) return;
current_man = game_status === 1 ? 0 : 1;
const startDot = parseInt(
men_bottom[current_man].getAttribute("i"),
10
);
const opponent = 1 - current_man;
const opponentDot = parseInt(
men_bottom[opponent].getAttribute("i"),
10
);
route_tmp = [startDot];
new_arrow = [];
clearReachableDots();
route.classList.add("hide");
btn_finish.classList.add("hide");
btn_back.classList.add("hide");
btn_cancel.classList.add("hide");
/*
* 先判斷當前玩家是否可以沿有向橋射殺對方。
* 如果可以,直接顯示紅線並結束。
*/
const shotLines = findShotRoute(startDot, opponentDot);
if (shotLines !== null) {
addShotLine(shotLines, false);
game_history.push(
JSON.stringify([-1].concat(shotLines))
);
endGame("shot", current_man, opponent);
return;
}
/*
* 完全沒有第一步可走,直接判困斃。
*/
if (getAvailableNextDots(startDot).length === 0) {
game_history.push(
JSON.stringify([-2, startDot])
);
endGame("trapped", opponent, current_man);
return;
}
game_status = game_status === 1 ? 2 : 4;
men_top.forEach(man => man.classList.add("hide"));
men_bottom.forEach(man => man.classList.remove("hide"));
men_bottom[current_man].classList.add("hide");
men_top[current_man].classList.remove("hide");
updateReachableDots();
scheduleAutoTurn();
}八、修改 displayGame()
在原本 displayGame() 底部,刪除這一整段:
// 自動下棋
clearTimeout(autoPlayTimer);
if ((game_status == 1 || game_status == 3) && document.getElementById('mcts_modal').style.display !== 'block') {
let autoBlue = document.getElementById('mcts_auto_blue') && document.getElementById('mcts_auto_blue').checked;
let autoGreen = document.getElementById('mcts_auto_green') && document.getElementById('mcts_auto_green').checked;
if ((current_man === 0 && autoBlue) || (current_man === 1 && autoGreen)) {
autoPlayTimer = setTimeout(() => {
updateMctsParams();
runMCTS(true);
}, 500);
}
}替換為:
clearTimeout(autoPlayTimer);
updateTutorialVisibility();
/*
* displayGame 完成棋盤顯示後,自動令當前玩家進入行動狀態。
*/
if (game_status === 1 || game_status === 3) {
setTimeout(beginTurn, 0);
}這樣原本所有:
displayGame();在切換到 game_status === 1 或 game_status === 3 後,都會自動開始行動,不需要逐一追加 beginTurn()。
九、替換人物及節點點擊邏輯
9.1 替換 dotOnClick()
將完整的原 dotOnClick() 替換成:
function dotOnClick() {
if (isMctsRunning) return;
if (![2, 4].includes(game_status)) return;
const dotIndex = parseInt(this.getAttribute("i"), 10);
const opponentDot = parseInt(
men_bottom[1 - current_man].getAttribute("i"),
10
);
if (route_tmp.length === 0) return;
if (route_tmp.includes(dotIndex)) return;
const lastDot = route_tmp[route_tmp.length - 1];
const availableDots = getAvailableNextDots(lastDot);
if (!availableDots.includes(dotIndex)) return;
/*
* 不再繪製規劃折線,只記錄路徑。
*/
route_tmp.push(dotIndex);
addBridge();
/*
* 至少走一步後才顯示打勾。
* 不能停在對方人物所在節點。
*/
if (dotIndex === opponentDot) {
btn_finish.classList.add("hide");
} else {
btn_finish.classList.remove("hide");
}
btn_cancel.classList.remove("hide");
btn_back.classList.remove("hide");
updateReachableDots();
}9.2 替換 manOnClick()
完整替換成:
function manOnClick() {
if (isMctsRunning) return;
/*
* 不再用點擊本方人物開始行動。
* 行動期間點擊另一方人物,仍視為點擊其所在節點。
*/
if (game_status === 2 || game_status === 4) {
dotOnClick.call(this);
}
}這樣在 game_status === 1/3 時點人物不會再開始行動;行動由 beginTurn() 自動開始。
十、替換打勾、返回、取消功能
10.1 替換 btn_finish.onclick
完整替換成:
btn_finish.onclick = function() {
if (![2, 4].includes(game_status)) return;
if (route_tmp.length <= 1) return;
const routeEnd = route_tmp[route_tmp.length - 1];
const opponentDot = parseInt(
men_bottom[1 - current_man].getAttribute("i"),
10
);
/*
* 不能在對方人物所在節點完成。
*/
if (routeEnd === opponentDot) return;
btn_finish.classList.add("hide");
btn_back.classList.add("hide");
btn_cancel.classList.add("hide");
clearReachableDots();
route.classList.add("hide");
men_bottom[current_man].setAttribute("i", routeEnd);
men_top[current_man].setAttribute("i", routeEnd);
createAnimateMotion();
};10.2 替換 btn_back.onclick
完整替換成:
btn_back.onclick = function() {
if (![2, 4].includes(game_status)) return;
if (route_tmp.length <= 1) return;
const removedDot = route_tmp.pop();
const previousDot = route_tmp[route_tmp.length - 1];
/*
* 如果退回的那一步建立了最新的一座橋,
* 同時撤銷該橋。
*/
if (
new_arrow.length >= 2 &&
new_arrow[new_arrow.length - 2] === previousDot &&
new_arrow[new_arrow.length - 1] === removedDot
) {
new_arrow.splice(new_arrow.length - 2, 2);
empty_map[previousDot].push(removedDot);
empty_map[removedDot].push(previousDot);
const arrowIndex = arrow_map[previousDot].indexOf(removedDot);
if (arrowIndex !== -1) {
arrow_map[previousDot].splice(arrowIndex, 1);
}
const removedArrow = arrows.pop();
if (removedArrow) removedArrow.remove();
}
const opponentDot = parseInt(
men_bottom[1 - current_man].getAttribute("i"),
10
);
if (route_tmp.length <= 1) {
btn_finish.classList.add("hide");
btn_back.classList.add("hide");
btn_cancel.classList.add("hide");
} else {
btn_back.classList.remove("hide");
btn_cancel.classList.remove("hide");
if (previousDot === opponentDot) {
btn_finish.classList.add("hide");
} else {
btn_finish.classList.remove("hide");
}
}
route.classList.add("hide");
updateReachableDots();
};10.3 替換 btn_cancel.onclick
完整替換成:
btn_cancel.onclick = function() {
if (![2, 4].includes(game_status)) return;
if (route_tmp.length <= 1) return;
const startDot = route_tmp[0];
for (let index = 0; index < new_arrow.length; index += 2) {
const bridgeStart = new_arrow[index];
const bridgeEnd = new_arrow[index + 1];
empty_map[bridgeStart].push(bridgeEnd);
empty_map[bridgeEnd].push(bridgeStart);
const arrowIndex = arrow_map[bridgeStart].indexOf(bridgeEnd);
if (arrowIndex !== -1) {
arrow_map[bridgeStart].splice(arrowIndex, 1);
}
const removedArrow = arrows.pop();
if (removedArrow) removedArrow.remove();
}
route_tmp = [startDot];
new_arrow = [];
route.classList.add("hide");
btn_finish.classList.add("hide");
btn_back.classList.add("hide");
btn_cancel.classList.add("hide");
updateReachableDots();
};十一、替換判勝負邏輯
將原本完整的:
function judgeResult() {
...
}替換成:
function judgeResult() {
if (![2, 4].includes(game_status)) {
return true;
}
const mover = current_man;
const opponent = 1 - mover;
/*
* 防禦性檢查。
* 正常情況下打勾按鈕在未移動時不會顯示。
*/
if (route_tmp.length <= 1) {
const startDot = parseInt(
men_bottom[mover].getAttribute("i"),
10
);
game_history.push(
JSON.stringify([-2, startDot])
);
route_tmp = [];
new_arrow = [];
endGame("trapped", opponent, mover);
return false;
}
/*
* 尚未放滿十二座可翻轉木橋時,
* 本回合必須至少新增一座木橋。
*/
if (new_arrow.length === 0 && arrows.length < 12) {
game_history.push(
JSON.stringify([-3].concat(route_tmp))
);
route_tmp = [];
new_arrow = [];
endGame("noBridge", opponent, mover);
return false;
}
/*
* 合法完成本回合。
*/
for (let index = 0; index < new_arrow.length; index += 2) {
const arrowNumber =
(stack_arrow.length - new_arrow.length) / 2 +
index / 2;
if (arrows[arrowNumber]) {
arrows[arrowNumber].setAttribute("stroke", "none");
}
}
const man0Dot = parseInt(
men_bottom[0].getAttribute("i"),
10
);
const man1Dot = parseInt(
men_bottom[1].getAttribute("i"),
10
);
const currentHistory =
[man0Dot, man1Dot].concat(stack_arrow);
const currentHistoryString =
JSON.stringify(currentHistory);
const repeated =
game_history.includes(currentHistoryString);
const historyStatus = repeated ? -4 : -5;
game_history.push(
JSON.stringify([historyStatus].concat(route_tmp))
);
game_history.push(currentHistoryString);
game_hp++;
route_tmp = [];
new_arrow = [];
clearReachableDots();
current_man = opponent;
if (repeated) {
endGame("draw");
return false;
}
game_status = current_man === 0 ? 1 : 3;
displayGame();
return true;
}這段新邏輯的結果是:
- 回合開始時不能走:立即困斃。
- 可以射殺:立即紅線並勝出。
- 沒走一步:打勾根本不顯示;防禦性判定仍會判困斃。
- 還需要木橋但沒放木橋:完成動畫後立即判負並彈框。
- 重複局面:立即和棋並彈框。
- 不再把結果文字寫到
detail_div。
十二、修改重新開始
在原本:
btn_play.onclick = function() {開頭加入:
hideGameResult();
clearReachableDots();
abortMcts = true;
isMctsRunning = false;
clearTimeout(autoPlayTimer);
document.getElementById("thinking_ui").style.display = "none";
document.getElementById("mcts_start_btn").textContent = t("startSearch");所以開頭應變成:
btn_play.onclick = function() {
hideGameResult();
clearReachableDots();
abortMcts = true;
isMctsRunning = false;
clearTimeout(autoPlayTimer);
document.getElementById("thinking_ui").style.display = "none";
document.getElementById("mcts_start_btn").textContent = t("startSearch");
men_bottom[0].setAttribute("stroke", "none");
men_bottom[1].setAttribute("stroke", "none");
detail_div.innerHTML = "";
// 後面保留原有程式……歷史模式中的和棋處理
在 btn_play.onclick 的 game_status == 8 分支中,搜尋:
if (game_history.length > 2 && -4 == JSON.parse(game_history[game_history.length - 2])[0]) {
game_status = 7;
detail_div.innerHTML = "Draw already";
setTimeout(() => detail_div.innerHTML = "Draw", 3000);
btn_play.classList.remove("hide");
men_bottom[0].setAttribute("stroke", "#e77");
men_bottom[1].setAttribute("stroke", "#e77");
} else {
game_status = game_hp & 1 ? 3 : 1;
}
displayGame();替換成:
if (
game_history.length > 2 &&
JSON.parse(game_history[game_history.length - 2])[0] === -4
) {
endGame("draw");
return;
} else {
game_status = game_hp & 1 ? 3 : 1;
}
displayGame();十三、修改設定窗口開關及 AI 自動控制
13.1 替換 openMctsModal()
function openMctsModal() {
abortMcts = true;
document.getElementById("mcts_results").innerHTML = "";
currentMctsResults = [];
currentMctsPage = 0;
updateTutorialVisibility();
if ([0, 7, 8].includes(game_status)) {
document.getElementById("mcts_start_btn").style.display = "none";
document.getElementById("mcts_results").style.display = "none";
document.getElementById("mcts_play_btn").style.display = "block";
} else {
document.getElementById("mcts_start_btn").style.display = "block";
document.getElementById("mcts_results").style.display = "block";
document.getElementById("mcts_play_btn").style.display = "none";
}
document.getElementById("mcts_modal").style.display = "block";
}13.2 替換 closeMctsModal()
function closeMctsModal() {
abortMcts = true;
document.getElementById("mcts_start_btn").textContent =
t("startSearch");
document.getElementById("mcts_modal").style.display = "none";
/*
* 新邏輯中 2、4 才是尚未移動或正在移動的行動狀態。
* 只有 route_tmp.length === 1 才代表回合剛開始。
*/
if (
[2, 4].includes(game_status) &&
route_tmp.length === 1
) {
setTimeout(scheduleAutoTurn, 500);
}
}13.3 修改手動搜尋按鈕
搜尋:
if (![1, 3].includes(game_status)) {
document.getElementById('mcts_results').innerHTML = "請在輪到玩家且未點擊時搜尋。";
return;
}替換成:
if (
![2, 4].includes(game_status) ||
route_tmp.length !== 1
) {
document.getElementById("mcts_results").textContent =
t("searchOnlyAtTurnStart");
return;
}同一事件中的:
this.innerText = "停止搜尋";替換成:
this.textContent = t("stopSearch");以及:
document.getElementById('mcts_results').innerHTML = "搜尋中,請稍候...";替換成:
document.getElementById("mcts_results").textContent =
t("searchingPleaseWait");所有這種:
document.getElementById('mcts_start_btn').innerText = "開始搜尋";都改成:
document.getElementById("mcts_start_btn").textContent =
t("startSearch");搜尋中止訊息:
document.getElementById('mcts_results').innerHTML = "搜尋已中止。";改為:
document.getElementById("mcts_results").textContent =
t("searchAborted");十四、修改 AI 執行走棋
完整替換 simulateMove():
function simulateMove(path) {
document.getElementById("mcts_modal").style.display = "none";
/*
* 不再模擬點擊本方棋子。
* beginTurn() 已經自動把遊戲切換到 2 或 4。
*/
if (![2, 4].includes(game_status)) {
document.getElementById("thinking_ui").style.display = "none";
return;
}
let step = 1;
function clickNext() {
if (
step < path.length &&
[2, 4].includes(game_status)
) {
dotOnClick.call(dots[path[step]]);
step++;
setTimeout(clickNext, 240);
return;
}
if ([2, 4].includes(game_status)) {
if (!btn_finish.classList.contains("hide")) {
btn_finish.onclick();
}
setTimeout(() => {
document.getElementById("thinking_ui").style.display =
"none";
}, 1200);
}
}
setTimeout(clickNext, 240);
}十五、刪除 AI 中舊的「點人物判射殺」邏輯
在 runMCTS() 中,刪除從以下註解:
// --- 新增:在展開路徑前,先檢查當前局面是否已經可以直接射殺對手 ---開始,到:
return; // 終止後續的 MCTS 展開
}結束的整段代碼。
原因是現在 beginTurn() 已經在 AI 搜尋前自動完成:
- 射殺判定;
- 無路可走判定;
- 進入行動狀態;
- 啟動 AI。
若保留舊代碼,AI 仍會試圖呼叫 manOnClick(),與新邏輯衝突。
十六、AI 沒有合法完成路線時的處理
新增以下函式,放在 getLegalMoves() 前:
function getFallbackMovement(state) {
const start =
state.turn === 0 ? state.p0 : state.p1;
const opponent =
state.turn === 0 ? state.p1 : state.p0;
const visited = new Set([start]);
function dfs(currentDot, path) {
/*
* 找到至少一步,而且終點不是對手所在位置。
* 這條路徑可能不包含新木橋,完成後會按照規則判負。
*/
if (path.length > 1 && currentDot !== opponent) {
return path.slice();
}
if (path.length >= PARAMS.MAX_PATH_LENGTH + 1) {
return null;
}
const nextDots = new Set([
...(state.empty_map[currentDot] || []),
...(state.arrow_map[currentDot] || [])
]);
for (const nextDot of nextDots) {
if (visited.has(nextDot)) continue;
visited.add(nextDot);
path.push(nextDot);
const result = dfs(nextDot, path);
if (result) return result;
path.pop();
visited.delete(nextDot);
}
return null;
}
return dfs(start, [start]);
}在 runMCTS() 中,把原本:
if (rootMoves.length === 0) {
...
}整段替換成:
if (rootMoves.length === 0) {
const elapsedSec =
((performance.now() - searchStartTime) / 1000).toFixed(1);
const fallbackMove = getFallbackMovement(rootState);
if (!isAuto) {
document.getElementById("mcts_results").textContent =
`${t("noLegalRoute")} ${t("totalTime", {
time: elapsedSec
})}`;
} else {
const thinkingUI = document.getElementById("thinking_ui");
thinkingUI.innerHTML =
`❌ <b>${t("noLegalRoute")}</b><br>` +
`⏱️ ${elapsedSec} 秒`;
thinkingUI.style.display = "block";
if (fallbackMove) {
/*
* 有路可走但找不到能放新橋的合法完成路線:
* AI 仍走一步,完成後由 judgeResult 判「沒有放橋」而輸。
*/
setTimeout(() => {
simulateMove(fallbackMove);
}, 1200);
} else {
/*
* 防禦性處理;正常情況已由 beginTurn 判困斃。
*/
setTimeout(() => {
thinkingUI.style.display = "none";
game_history.push(
JSON.stringify([-2, rootState.turn === 0
? rootState.p0
: rootState.p1])
);
endGame(
"trapped",
1 - rootState.turn,
rootState.turn
);
}, 1200);
}
}
isMctsRunning = false;
document.getElementById("mcts_start_btn").textContent =
t("startSearch");
return;
}十七、讓 AI 搜尋結果跟隨語言
完整替換 renderMctsResults():
function renderMctsResults() {
const maxPerPage = 5;
const total = currentMctsResults.length;
const totalPages = Math.ceil(total / maxPerPage) || 1;
const start = currentMctsPage * maxPerPage;
const end = Math.min(start + maxPerPage, total);
let resultsHtml =
`<b>${t("searchComplete", { count: total })}</b><br>` +
`<span style="font-size:13px;color:#555;">` +
`⏱️ ${t("totalTime", { time: lastSearchTime })}` +
`</span><br>`;
resultsHtml += `
<div style="
margin-top:10px;
display:flex;
justify-content:space-between;
align-items:center;
gap:8px;
">
<button type="button"
onclick="changeMctsPage(-1)"
${currentMctsPage === 0 ? "disabled" : ""}>
${t("previousPage")}
</button>
<span>
${t("page", {
current: currentMctsPage + 1,
total: totalPages
})}
</span>
<button type="button"
onclick="changeMctsPage(1)"
${currentMctsPage >= totalPages - 1
? "disabled"
: ""}>
${t("nextPage")}
</button>
</div>
`;
for (let index = start; index < end; index++) {
const stat = currentMctsResults[index];
const pathString = stat.move.join(" ➔ ");
let scoreText;
if (stat.immediateLoss) {
scoreText =
`<span style="color:red;">${t("fatalMove")}</span>`;
} else if (stat.guaranteedLoss) {
scoreText =
`<span style="color:orange;">${t("forcedLoss")}</span>`;
} else if (stat.guaranteedWin) {
scoreText =
`<span style="color:green;">${t("forcedWin")}</span>`;
} else {
const rate =
((stat.wins / stat.plays) * 100).toFixed(1);
scoreText = t("winRate", { rate });
}
resultsHtml += `
<div class="mcts_result_item"
onclick="simulateMove([${stat.move.join(",")}])">
${index + 1}. [${pathString}] | ${scoreText}
</div>
`;
}
document.getElementById("mcts_results").innerHTML =
resultsHtml;
}AI 左上角的 #thinking_ui 外觀和主要顯示邏輯沒有改動,符合「AI 思考消息框不必改動」。
十八、鍵盤控制修正
目前數字鍵會優先點擊人物。在新邏輯下,開始回合不需要點人物,因此在鍵盤事件的數字鍵部分,將:
if (manToClick) {
triggerClick(manToClick);
} else if (dots[dotIdx]) {
triggerClick(dots[dotIdx]);
}替換成:
/*
* 行動階段一律以節點點擊處理。
* 若節點上有人,dotOnClick 仍會按節點編號執行。
*/
if ([2, 4].includes(game_status) && dots[dotIdx]) {
dotOnClick.call(dots[dotIdx]);
} else if (manToClick) {
triggerClick(manToClick);
} else if (dots[dotIdx]) {
triggerClick(dots[dotIdx]);
}十九、修改後的遊戲流程
二十、重要行為差異
修改後:
- 不再需要點擊本方人物。
- 路線規劃期間不再顯示原來的折線。
- 當前一步能到達的節點顯示淺紅色描邊:
stroke: #f0a0a0stroke-width: 4
- 每走一步重新計算可走節點。
- 尚未走一步時不顯示打勾。
- 無第一步可走時立即困斃,不需要再按打勾。
- 射擊成立時立即顯示紅色路線並結束。
- 應放木橋但沒放時判負。
- 勝、負、和棋全部使用新的勝負消息框。
- 勝負框可最小化;最小化後左上角顯示恢復 SVG 按鈕。
- 「重新開始」直接開始新局。
- 設定窗口桌面寬度為
620px,手機寬度為90%。 - 教學模式只在遊戲尚未開始、即
game_status === 0時顯示。 - 中文為預設語言,切換 English 後設定窗口、搜尋結果及勝負消息會更新。