Re: 台灣優秀桌遊設計師的作品:質數迷宮
前一版本
- 附件
-
primemaze3_systemfont.html.7z- (7.87 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 台灣優秀桌遊設計師的作品:質數迷宮
最新版ejprimemaze7.html
- 附件
-
ejprimemaze7.html.7z- (8.29 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 台灣優秀桌遊設計師的作品:質數迷宮
寫提示詞非常花費時間和精力!
代码: 全选
改進質數迷宮:
一,改進導入導出
之前當點擊導入或導出時,下面的io-panel是突然出現的。現在改成跟點擊「遊戲規則」時一樣,也要有往下撐開的動畫。
io-panel的寬度改為全寬(跟上面的controls一樣),在電腦端,io-panel內部的button也改為放到textarea的右側並與其等高(與手機端一致)。
若導入時有問題,把警告彈窗alert改成一個消息框,在1.2s後自動消失。
二,增加「遊戲設置」功能
在rulesbar內,新增一個「遊戲設置」按鈕到中間位置,則rulesbar內的三個元素平分寬度並且距離相等。
「遊戲設置」按鈕跟「遊戲規則」按鈕一樣,也是左邊有個三角形,右邊是文字,點擊後下方動畫展開內容,並且三角形旋轉。
「遊戲設置」的內容是,一個勾選項「保證唯一解」(默認勾選),其餘為單選:「四方,五方,六方,七方,八方,九方,十方」(默認七方),幾方指的是n*n個格子,比如九方指的是9*9個格子。將勾選項和單選項做成現代美觀的樣式。
選項更改後並不會立即生效,而是「新建」時將按照選項創建新的盤面。更改程式內的計算方法,使之能生成「不止有唯一解」以及各種格子數量規格。同時加上英文翻譯。
三,改進解答功能
當點擊解答時,它的文字會變為「復原」,現在取消這個改動,它的文字始終保持為「解答」。(英文也一樣)
當解答時,如果不止有一個答案,則在展示第一個答案的同時,把所有的答案都做成多個可點擊的「圈圈內的數字」,列在「格子區域」的下方,但不要超出屏幕,必要時可能要使「格子區域」縮小。
「圏圏數字」的顏色要適配當前主題,居中,保持一定間隔。一開始是第1個高亮,點擊哪個就是哪個高亮,並且展示這個數字所代表的解法。
四,改變解答完畢邏輯
當解答完畢時,格子區域會閃爍幾下然後鎖定。現在改為不鎖定,點擊其中一個數字,仍然會截斷其後續的數字。
需要明確給出所要修改的地方和所要替換的代碼。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 台灣優秀桌遊設計師的作品:質數迷宮
gemini 3.1 pro
代码: 全选
這是一項相當全面且能大幅提升玩家體驗的升級!針對你的四個需求(優化導入/導出面板、新增動態大小和多解支持的遊戲設置、改進解答展示以及改變解答鎖定邏輯),以下是具體需要修改與替換的代碼。
### 第一部分:CSS 樣式替換
請將原本 `<style>` 標籤中,從 `.rulesbar` 開始一直到 `@media (min-width: 560px)` 結尾處的樣式,**全部替換**為以下內容。這包含了新的等距工具列、設定面板、動畫下拉的 `io-panel`、解答圈圈列以及 Toast 提示框:
```css
/* ---- header & rulesbar ---- */
.titlewrap{
flex:0 0 auto;
display:flex; align-items:center; justify-content:center; gap:10px; padding-top:2px;
}
.titlewrap svg{ flex:0 0 auto; opacity:.85; }
h1.title{
margin:0; text-align:center; font-family:ui-serif, Georgia, Cambria, 'Times New Roman', serif;
font-weight:700; font-size:clamp(20px,4.6vmin,28px); letter-spacing:.06em;
color:var(--gold-bright); text-shadow:0 0 18px var(--gold-glow);
}
.rulesbar{
flex:0 0 auto;
display:flex; align-items:center; justify-content:space-between; gap:10px; padding:6px 10px;
}
.rules-toggle{
display:flex; align-items:center; gap:6px; background:none; border:none;
color:var(--ink); font-family:inherit; font-size:clamp(12.5px,3vmin,14px); font-weight:500;
cursor:pointer; padding:2px 0;
}
.rules-toggle .car{
display:inline-block; transition:transform .2s ease; color:var(--gold); font-size:11px;
}
.rules-toggle.open .car{ transform:rotate(90deg); }
.langswitch{
flex:0 0 auto; display:flex; align-items:center; gap:6px; font-size:clamp(11.5px,2.8vmin,13px);
}
.langswitch .sep{ color:var(--ink-faint); }
.lang-btn{
background:none; border:none; color:var(--ink-faint); font-family:inherit; font-size:inherit;
cursor:pointer; padding:2px 3px;
}
.lang-btn.active{ color:var(--gold-bright); font-weight:700; }
/* ---- dropdown panels ---- */
.rules-panel{
flex:0 0 auto; max-height:0; overflow:hidden; transition:max-height .25s ease;
}
.rules-panel.open{ max-height:140px; }
.rules-inner{
padding:8px 12px 10px; font-size:clamp(12px,2.8vmin,13.5px); line-height:1.6; color:var(--ink-dim);
}
.settings-inner { display:flex; flex-direction:column; gap:10px; }
.cb-label {
display:flex; align-items:center; gap:6px; cursor:pointer; color:var(--gold-bright); font-weight:500;
}
.size-radios { display:flex; flex-wrap:wrap; gap:8px; }
.size-radios label { display:flex; align-items:center; gap:4px; cursor:pointer; font-size: 12px; }
/* ---- controls ---- */
.controls{
flex:0 0 auto; display:flex; gap:8px;
}
.controls button{
flex:1 1 0; padding:9px 4px; background:var(--panel-2); border:1px solid var(--line);
border-radius:var(--radius-s); color:var(--ink); font-family:inherit;
font-weight:500; font-size:clamp(12px,3vmin,14px); cursor:pointer;
transition:background .15s ease, border-color .15s ease, opacity .15s ease; white-space:nowrap;
}
.controls button:active{ background:var(--cell-bg); }
.controls button:disabled{ opacity:.4; cursor:default; }
.controls button.primary{ border-color:var(--gold); color:var(--gold-bright); }
.controls button.active-red{ background:var(--danger); border-color:var(--danger); color:#fff; }
.controls button.active-red:disabled{ opacity:1; }
/* ---- import/export panel (全寬 & Flex Row) ---- */
.io-panel{
flex:0 0 auto; max-height:0; overflow:hidden; transition:max-height .25s ease; width:100%; box-sizing:border-box;
}
.io-panel.open{ max-height:160px; margin-bottom:4px; }
.io-inner{
display:flex; flex-direction:row; align-items:stretch; gap:8px; padding-top:2px;
}
.io-panel textarea{
flex:1 1 auto; background:var(--panel-2); border:1px solid var(--line); border-radius:var(--radius-s);
color:var(--ink); font-family:ui-monospace, SFMono-Regular, Consolas, monospace;
font-size:13px; line-height:1.5; padding:8px 10px; resize:none; outline:none;
box-sizing:border-box; min-height:64px; max-height:120px;
}
.io-panel textarea:focus{ border-color:var(--teal); }
.io-panel textarea::placeholder{ color:var(--ink-faint); }
.io-panel .io-action{
flex:0 0 auto; border:1px solid var(--blue); background:var(--blue); color:#fff;
font-family:inherit; font-weight:600; font-size:clamp(12px,3vmin,14px); border-radius:var(--radius-s);
cursor:pointer; white-space:nowrap; padding:0 16px;
}
.io-panel .io-action:active{ opacity:.85; }
/* ---- HUD & Solutions Bar ---- */
.hud{
flex:0 0 auto; display:flex; justify-content:center; gap:18px; font-size:clamp(11.5px,2.8vmin,13px); color:var(--ink-dim); min-height: 18px;
}
.hud .status-msg{ color:var(--danger); font-weight:500; }
.solutions-bar {
flex: 0 0 auto; display: flex; gap: 8px; overflow-x: auto; padding: 4px 10px; justify-content: center; min-height: 40px;
}
.sol-circle {
width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%;
background: var(--panel-2); border: 1px solid var(--line);
display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 13px; font-weight: 700; color: var(--ink-dim); transition: all .2s;
}
.sol-circle.active {
background: var(--teal); color: #fff; border-color: var(--teal); box-shadow: 0 0 10px var(--teal-glow);
}
/* ---- Toast Message ---- */
.toast {
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: var(--danger); color: #fff; padding: 10px 20px;
border-radius: 20px; font-size: 14px; font-weight: 500;
opacity: 0; pointer-events: none; transition: opacity .3s ease;
z-index: 1000; box-shadow: 0 4px 12px rgba(0,0,0,.3);
}
.toast.show { opacity: 1; }
```
### 第二部分:HTML 結構替換
請將 `<div class="rulesbar">` 一直到 `<div id="board" class="board"></div>` 的包裝區塊,**全部替換**為以下結構。我們加入了設定區塊、解答列表容器與全局 Toast:
```html
<div class="rulesbar">
<button class="rules-toggle" id="rulesToggle" aria-expanded="false">
<span class="car">▶</span><span id="rulesLabel">遊戲規則</span>
</button>
<button class="rules-toggle" id="settingsToggle" aria-expanded="false">
<span class="car">▶</span><span id="settingsLabel">遊戲設置</span>
</button>
<div class="langswitch">
<button class="lang-btn active" id="langZh" data-lang="zh">中文</button>
<span class="sep">|</span>
<button class="lang-btn" id="langEn" data-lang="en">English</button>
</div>
</div>
<div class="rules-panel" id="rulesPanel">
<div class="rules-inner" id="rulesText">從1開始,把連續數字填入相鄰格子中,直至填滿,著色方塊上一定是質數。</div>
</div>
<div class="rules-panel" id="settingsPanel">
<div class="rules-inner settings-inner">
<label class="cb-label"><input type="checkbox" id="chkUnique" checked> <span id="lblUnique">保證唯一解</span></label>
<div class="size-radios" id="sizeRadios"></div>
</div>
</div>
<div class="controls">
<button id="btnNew">新建</button>
<button id="btnExport">導出</button>
<button id="btnImport">導入</button>
<button id="btnSolve">解答</button>
</div>
<div class="io-panel" id="ioPanel">
<div class="io-inner">
<textarea id="ioTextarea" rows="3"></textarea>
<button id="ioActionBtn" class="io-action">複製</button>
</div>
</div>
<div class="hud">
<div class="status-msg" id="statusMsg"></div>
</div>
<div class="board-wrap">
<div id="board" class="board"></div>
</div>
<div id="solutionsBar" class="solutions-bar"></div>
</div>
<div id="toast" class="toast"></div>
```
### 第三部分:JavaScript 核心變數、翻譯與初始化替換
在 `<script>` 標籤中,請將 `const I18N` 到 `let cellEls = new Array(N);` 這個區段替換為動態建立的變數與函式:
```javascript
/* ============ i18n ============ */
const I18N = {
zh: {
title: '質數迷宮', rules: '遊戲規則', settings: '遊戲設置',
rulesText: '從1開始,把連續數字填入相鄰格子中,直至填滿,著色方塊上一定是質數。',
btnNew: '新建', btnExport: '導出', btnImport: '導入', btnSolve: '解答',
ioCopy: '複製', ioConfirm: '確認', ioPlaceholder: '請貼上盤面文字…',
importFormatBad: '格式不正確', importNoSolution: '此謎題無解', importNotUnique: '此謎題有多組解法,並非唯一解',
lblUnique: '保證唯一解', sizeFmt: '{n}方'
},
en: {
title: 'Prime Maze', rules: 'Rules', settings: 'Settings',
rulesText: 'Starting from 1, fill consecutive numbers into adjacent cells until the grid is full. Colored cells must always hold a prime number.',
btnNew: 'New', btnExport: 'Export', btnImport: 'Import', btnSolve: 'Solve',
ioCopy: 'Copy', ioConfirm: 'Confirm', ioPlaceholder: 'Paste puzzle text here…',
importFormatBad: 'Invalid format', importNoSolution: 'This puzzle has no solution', importNotUnique: 'This puzzle has multiple solutions (not unique)',
lblUnique: 'Ensure Unique Solution', sizeFmt: '{n}x{n}'
}
};
let LANG = 'zh';
const t = (k) => I18N[LANG][k];
const tSize = (n) => I18N[LANG].sizeFmt.replace('{n}', n);
/* ============ constants & memory ============ */
let R = 7, C = 7, N = R * C;
let PRIME, cellEls, mask, filled, assigned, stack, posOf;
let allSolutionPaths = []; // 儲存所有解答
const isPrimeNum = (n) => {
if (n <= 1) return false;
if (n === 2) return true;
if (n % 2 === 0) return false;
for (let i = 3; i * i <= n; i += 2) if (n % i === 0) return false;
return true;
};
function initMemory() {
N = R * C;
PRIME = new Uint8Array(N + 1);
for (let k = 1; k <= N; k++) PRIME[k] = isPrimeNum(k) ? 1 : 0;
cellEls = new Array(N);
mask = new Uint8Array(N);
filled = new Uint8Array(N);
assigned = new Uint8Array(N);
stack = new Int32Array(N);
posOf = new Int32Array(N + 2);
}
initMemory(); // 首次初始化
```
### 第四部分:綁定新 DOM 以及 Toast 功能
在 `/* ============ DOM refs ============ */` 區塊中加入這些新元素,並加入 `showToast` 函式:
```javascript
/* ============ DOM refs ============ */
const boardEl = document.getElementById('board');
const statusMsgEl = document.getElementById('statusMsg');
const btnNew = document.getElementById('btnNew');
const btnExport = document.getElementById('btnExport');
const btnImport = document.getElementById('btnImport');
const btnSolve = document.getElementById('btnSolve');
const ioPanel = document.getElementById('ioPanel');
const ioTextarea = document.getElementById('ioTextarea');
const ioActionBtn = document.getElementById('ioActionBtn');
const rulesToggle = document.getElementById('rulesToggle');
const rulesPanel = document.getElementById('rulesPanel');
const rulesText = document.getElementById('rulesText');
const rulesLabel = document.getElementById('rulesLabel');
const settingsToggle = document.getElementById('settingsToggle');
const settingsPanel = document.getElementById('settingsPanel');
const settingsLabel = document.getElementById('settingsLabel');
const chkUnique = document.getElementById('chkUnique');
const lblUnique = document.getElementById('lblUnique');
const sizeRadios = document.getElementById('sizeRadios');
const solutionsBar = document.getElementById('solutionsBar');
const titleText = document.getElementById('titleText');
const langZh = document.getElementById('langZh');
const langEn = document.getElementById('langEn');
// 生成設定選單的 Radio Buttons
for(let i = 4; i <= 10; i++) {
const lbl = document.createElement('label');
lbl.innerHTML = `<input type="radio" name="gridSize" value="${i}" ${i===7?'checked':''}> <span class="sz-text"></span>`;
sizeRadios.appendChild(lbl);
}
function showToast(msg) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => { toast.classList.remove('show'); }, 1200);
}
```
### 第五部分:核心演算法與解析 (支援動態大小與多解)
將原本的 `solveCount`、`decodeMaskLenient`、`buildBoard` 以及 `fitBoard` **全部替換**如下:
```javascript
function solveCount(maskArr, sR, sC, maxCount, nodeBudget) {
const visited = new Uint8Array(N);
let solutions = 0, nodes = 0, exceeded = false, firstPath = null;
const allPaths = []; // 儲存多個解答
const pathStack = [[sR, sC]];
function neighbors(r, c) {
const res = [];
if (r > 0) res.push([r - 1, c]);
if (r < R - 1) res.push([r + 1, c]);
if (c > 0) res.push([r, c - 1]);
if (c < C - 1) res.push([r, c + 1]);
return res;
}
function dfs(r, c, k) {
nodes++;
if (nodes > nodeBudget) { exceeded = true; return; }
if (k === N) {
solutions++;
if (!firstPath) firstPath = pathStack.slice();
allPaths.push(pathStack.slice());
return;
}
const nbrs = neighbors(r, c);
for (const [nr, nc] of nbrs) {
const id = nr * C + nc;
if (visited[id]) continue;
if (maskArr[id] !== PRIME[k + 1]) continue;
visited[id] = 1;
pathStack.push([nr, nc]);
dfs(nr, nc, k + 1);
pathStack.pop();
visited[id] = 0;
if (solutions >= maxCount || exceeded) return;
}
}
visited[sR * C + sC] = 1;
dfs(sR, sC, 1);
return { solutions, nodes, exceeded, firstPath, allPaths };
}
function decodeMaskLenient(str) {
const rows = str.split('8');
const newR = rows.length;
const newC = newR; // 假設皆為正方形
const newN = newR * newC;
const m = new Uint8Array(newN);
let sR = -1, sC = -1;
let valid = true;
for (let r = 0; r < newR; r++) {
let c = 0;
for (let i = 0; i < (rows[r] ? rows[r].length : 0); i++) {
const ch = rows[r][i];
if (ch >= '1' && ch <= '7') {
const run = +ch;
for (let k = 0; k < run && c < newC; k++) { m[r * newC + c] = 0; c++; }
} else if (ch === '9') {
if (sR !== -1) valid = false; else { sR = r; sC = c; }
m[r * newC + c] = 0; c++;
} else if (ch === '0') {
m[r * newC + c] = 1; c++;
}
}
if (c !== newC) valid = false;
}
if (sR === -1) { valid = false; sR = 0; sC = 0; }
return { mask: m, startR: sR, startC: sC, valid, R: newR, C: newC };
}
function buildBoard() {
boardEl.innerHTML = '';
boardEl.style.gridTemplateColumns = `repeat(${C}, 1fr)`;
boardEl.style.gridTemplateRows = `repeat(${R}, 1fr)`;
for (let r = 0; r < R; r++) {
for (let c = 0; c < C; c++) {
const id = r * C + c;
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.r = r; cell.dataset.c = c;
cellEls[id] = cell;
boardEl.appendChild(cell);
}
}
for (let r = 0; r < R; r++) {
for (let c = 0; c < C; c++) { updateCellDOM(r, c); }
}
fitBoard();
}
let fitRaf = null;
function fitBoard() {
if (fitRaf) cancelAnimationFrame(fitRaf);
fitRaf = requestAnimationFrame(() => {
const wrap = boardEl.parentElement;
const w = wrap.clientWidth, h = wrap.clientHeight;
const side = Math.max(120, Math.min(w, h));
boardEl.style.width = side + 'px';
boardEl.style.height = side + 'px';
const cellPx = Math.floor((side - 12 - (C - 1) * 3) / C);
for(let i = 0; i < N; i++) {
if(cellEls[i]) cellEls[i].style.fontSize = Math.max(11, Math.floor(cellPx * 0.5)) + 'px';
}
});
}
```
### 第六部分:遊戲流程與導入導出面板方法
替換 `generateNewPuzzle()`、`openIOPanel()`、`closeIOPanel()` 和 `handleImportConfirm()`。**請將這些函數與原有的 `resetSolveButton` 及 `cancelSolveIfActive` 相關段落一併刪除或取代**:
```javascript
function generateNewPuzzle() {
if (gameState === 'generating') return;
closeIOPanel();
if (solveTimer) { clearInterval(solveTimer); solveTimer = null; }
solutionsBar.innerHTML = '';
// 讀取設置
const newSize = parseInt(document.querySelector('input[name="gridSize"]:checked').value);
const needUnique = chkUnique.checked;
if (R !== newSize) { R = newSize; C = newSize; initMemory(); }
gameState = 'generating'; inputLocked = true;
btnNew.disabled = true; btnImport.disabled = true; btnSolve.disabled = true; btnExport.disabled = true;
btnNew.classList.add('active-red');
setTimeout(() => {
const t0 = performance.now();
let found = null;
while (true) {
const path = genPath();
const m = maskFromPath(path);
const sR = path[0][0], sC = path[0][1];
const targetSols = needUnique ? 2 : 1;
const res = solveCount(m, sR, sC, targetSols, 500000);
const elapsed = performance.now() - t0;
if (!res.exceeded) {
if (needUnique && res.solutions === 1) {
found = { mask: m, startR: sR, startC: sC, path: res.firstPath, allPaths: res.allPaths }; break;
} else if (!needUnique && res.solutions >= 1) {
found = { mask: m, startR: sR, startC: sC, path: res.firstPath, allPaths: res.allPaths }; break;
}
}
if (elapsed > 4000) {
found = { mask: m, startR: sR, startC: sC, path: res.firstPath || path, allPaths: [res.firstPath || path] }; break;
}
}
mask = found.mask; startR = found.startR; startC = found.startC;
solutionPath = found.path; allSolutionPaths = found.allPaths;
freshState(); buildBoard(); setStatus('');
gameState = 'running'; inputLocked = false;
btnNew.disabled = false; btnImport.disabled = false; btnSolve.disabled = false; btnExport.disabled = false;
btnNew.classList.remove('active-red');
}, 30);
}
function openIOPanel(mode) {
if (solveTimer) { clearInterval(solveTimer); solveTimer = null; }
ioMode = mode;
ioPanel.classList.add('open');
if (mode === 'export') {
ioTextarea.value = encodeMask(mask, startR, startC); ioTextarea.readOnly = true;
ioTextarea.placeholder = ''; ioActionBtn.textContent = t('ioCopy'); ioActionBtn.onclick = handleCopy;
btnExport.classList.add('active-red'); btnImport.classList.remove('active-red');
ioTextarea.focus(); ioTextarea.select();
} else {
ioTextarea.value = ''; ioTextarea.readOnly = false; ioTextarea.placeholder = t('ioPlaceholder');
ioActionBtn.textContent = t('ioConfirm'); ioActionBtn.onclick = handleImportConfirm;
btnImport.classList.add('active-red'); btnExport.classList.remove('active-red'); ioTextarea.focus();
}
}
function closeIOPanel() {
if (ioMode === null) return;
ioMode = null; ioPanel.classList.remove('open');
btnExport.classList.remove('active-red'); btnImport.classList.remove('active-red');
}
function handleImportConfirm() {
const raw = ioTextarea.value.trim();
const decoded = decodeMaskLenient(raw);
if (R !== decoded.R) { R = decoded.R; C = decoded.C; initMemory(); }
const res = solveCount(decoded.mask, decoded.startR, decoded.startC, 50, 1200000);
const issues = [];
if (!decoded.valid) issues.push(t('importFormatBad'));
if (res.solutions === 0) issues.push(t('importNoSolution'));
else if (res.solutions >= 2) issues.push(t('importNotUnique'));
if (issues.length) {
showToast(issues.join(' / '));
if (res.solutions === 0 || !decoded.valid) return;
}
mask = decoded.mask; startR = decoded.startR; startC = decoded.startC;
solutionPath = res.firstPath || null;
allSolutionPaths = res.allPaths || [];
freshState();
if (solveTimer) { clearInterval(solveTimer); solveTimer = null; }
solutionsBar.innerHTML = '';
buildBoard();
gameState = 'running'; inputLocked = false;
setStatus('');
closeIOPanel();
}
```
### 第七部分:改寫解題展示與完成邏輯
刪除原本舊的 `revealSolution()`,並修改 `onWin()` 與替換 `btnSolve.addEventListener`:
```javascript
function onWin() {
boardEl.classList.remove('win-anim');
void boardEl.offsetWidth;
boardEl.classList.add('win-anim');
}
function renderSolutionCircles() {
solutionsBar.innerHTML = '';
if (allSolutionPaths.length <= 1) return;
allSolutionPaths.forEach((path, idx) => {
const circle = document.createElement('div');
circle.className = 'sol-circle';
circle.textContent = idx + 1;
circle.onclick = () => playSolution(idx);
solutionsBar.appendChild(circle);
});
}
function playSolution(idx) {
if (solveTimer) { clearInterval(solveTimer); solveTimer = null; }
solutionPath = allSolutionPaths[idx];
freshState();
buildBoard();
Array.from(solutionsBar.children).forEach((c, i) => c.classList.toggle('active', i === idx));
let k = 0;
gameState = 'running';
solveTimer = setInterval(() => {
if (k >= solutionPath.length) {
clearInterval(solveTimer); solveTimer = null; return;
}
const [r, c] = solutionPath[k];
const id = r * C + c;
const val = k + 1;
if (!(r === startR && c === startC)) {
filled[id] = 1; assigned[id] = val;
stack[stackLen++] = id; posOf[val] = id;
}
current = val + 1;
k++;
updateCellDOM(r, c);
const el = cellEls[id];
if (el) {
el.classList.remove('reveal-pop');
void el.offsetWidth;
el.classList.add('reveal-pop');
}
}, 28);
}
// 替換解答按鈕事件
btnSolve.addEventListener('click', () => {
if (ioMode !== null) closeIOPanel();
if (gameState !== 'running' && gameState !== 'finished') return;
if (!solutionPath) { showToast(t('importNoSolution')); return; }
if (!allSolutionPaths || allSolutionPaths.length === 0) {
const res = solveCount(mask, startR, startC, 50, 1200000);
allSolutionPaths = res.allPaths || [solutionPath];
}
renderSolutionCircles();
playSolution(0);
});
```
### 第八部分:點擊互動與事件更新
1. 在 `boardEl.addEventListener('click', ...)` 的事件最開頭,加入清除計時器的方法,確保手動點擊能順利截斷並繼續遊玩:
```javascript
boardEl.addEventListener('click', (e) => {
const cellEl = e.target.closest('.cell'); if (!cellEl) return;
if (inputLocked || gameState !== 'running') return;
const r = +cellEl.dataset.r, c = +cellEl.dataset.c;
const id = r * C + c;
// 如果點擊任何格子時解答動畫還在跑,就中斷它
if (solveTimer) { clearInterval(solveTimer); solveTimer = null; }
// 後續邏輯完全不變...
```
2. 補充 UI 語言與選單的事件綁定,加在程式最後方 (`applyLang` 之前):
```javascript
settingsToggle.addEventListener('click', () => {
const open = settingsPanel.classList.toggle('open');
settingsToggle.classList.toggle('open', open);
settingsToggle.setAttribute('aria-expanded', String(open));
});
function applyLang() {
titleText.textContent = t('title');
rulesLabel.textContent = t('rules');
settingsLabel.textContent = t('settings');
rulesText.textContent = t('rulesText');
lblUnique.textContent = t('lblUnique');
btnNew.textContent = t('btnNew');
btnExport.textContent = t('btnExport');
btnImport.textContent = t('btnImport');
btnSolve.textContent = t('btnSolve'); // 始終維持解答文字
document.querySelectorAll('.sz-text').forEach((span, i) => { span.textContent = tSize(i + 4); });
if (ioMode === 'export') { ioActionBtn.textContent = t('ioCopy'); }
else if (ioMode === 'import') { ioActionBtn.textContent = t('ioConfirm'); ioTextarea.placeholder = t('ioPlaceholder'); }
langZh.classList.toggle('active', LANG === 'zh');
langEn.classList.toggle('active', LANG === 'en');
document.documentElement.lang = LANG === 'zh' ? 'zh-Hant' : 'en';
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 14 访客