分享身边的点滴趣事
回复
头像
ejsoon
一枝独秀
帖子: 6281
注册时间: 2022年 11月 18日 17:36
联系:

台灣優秀桌遊設計師的作品:質數迷宮

帖子 ejsoon »

從1到49的質數迷宮,想不到竟然這麼難!
ejprimemaze6.html.7z
(8.29 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6281
注册时间: 2022年 11月 18日 17:36
联系:

Re: 台灣優秀桌遊設計師的作品:質數迷宮

帖子 ejsoon »

前一版本
附件
primemaze3_systemfont.html.7z
(7.87 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6281
注册时间: 2022年 11月 18日 17:36
联系:

Re: 台灣優秀桌遊設計師的作品:質數迷宮

帖子 ejsoon »

最新版ejprimemaze7.html
附件
ejprimemaze7.html.7z
(8.29 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6281
注册时间: 2022年 11月 18日 17:36
联系:

Re: 台灣優秀桌遊設計師的作品:質數迷宮

帖子 ejsoon »

寫提示詞非常花費時間和精力!

代码: 全选

改進質數迷宮:
一,改進導入導出

之前當點擊導入或導出時,下面的io-panel是突然出現的。現在改成跟點擊「遊戲規則」時一樣,也要有往下撐開的動畫。

io-panel的寬度改為全寬(跟上面的controls一樣),在電腦端,io-panel內部的button也改為放到textarea的右側並與其等高(與手機端一致)。

若導入時有問題,把警告彈窗alert改成一個消息框,在1.2s後自動消失。

二,增加「遊戲設置」功能

在rulesbar內,新增一個「遊戲設置」按鈕到中間位置,則rulesbar內的三個元素平分寬度並且距離相等。

「遊戲設置」按鈕跟「遊戲規則」按鈕一樣,也是左邊有個三角形,右邊是文字,點擊後下方動畫展開內容,並且三角形旋轉。

「遊戲設置」的內容是,一個勾選項「保證唯一解」(默認勾選),其餘為單選:「四方,五方,六方,七方,八方,九方,十方」(默認七方),幾方指的是n*n個格子,比如九方指的是9*9個格子。將勾選項和單選項做成現代美觀的樣式。

選項更改後並不會立即生效,而是「新建」時將按照選項創建新的盤面。更改程式內的計算方法,使之能生成「不止有唯一解」以及各種格子數量規格。同時加上英文翻譯。

三,改進解答功能

當點擊解答時,它的文字會變為「復原」,現在取消這個改動,它的文字始終保持為「解答」。(英文也一樣)

當解答時,如果不止有一個答案,則在展示第一個答案的同時,把所有的答案都做成多個可點擊的「圈圈內的數字」,列在「格子區域」的下方,但不要超出屏幕,必要時可能要使「格子區域」縮小。

「圏圏數字」的顏色要適配當前主題,居中,保持一定間隔。一開始是第1個高亮,點擊哪個就是哪個高亮,並且展示這個數字所代表的解法。

四,改變解答完畢邏輯

當解答完畢時,格子區域會閃爍幾下然後鎖定。現在改為不鎖定,點擊其中一個數字,仍然會截斷其後續的數字。

需要明確給出所要修改的地方和所要替換的代碼。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6281
注册时间: 2022年 11月 18日 17:36
联系:

Re: 台灣優秀桌遊設計師的作品:質數迷宮

帖子 ejsoon »

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 访客