分享和讨论Javascript相关的话题
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6055
注册时间: 2022年 11月 18日 17:36
联系:

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

想不到gemini3.1pro回答輕而易舉

代码: 全选

這是一個非常精彩且具有深度的棋盤遊戲專案!你對記分規則(尤其是責任傳遞機制)的設計非常有創意。針對你的四項需求,我已經整理了相應的代碼修復與改進方案。

以下是需要修改和替換的代碼,請按照指示覆蓋原有的部分:

### 一、 頁面樣式修復與導入/導出窗口美化 (CSS & HTML)

**1. 替換 `<style>` 標籤中的相關 CSS:**
請找到並替換 `.piece-count`, `@media (max-width: 768px)`, `#export-modal` 等相關樣式,並加入關閉按鈕的新樣式:

```css
/* 替換或新增以下 CSS */
.piece-count {
  position: static;
  font-size: 26px; /* 調大字體 */
  font-weight: 900;
  color: #333;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15); /* 增加立體感 */
}

@media (max-width: 768px) {
  .piece-count {
    font-size: 22px; /* 手機端稍小但依然清晰 */
  }
  .piece-area {
    flex-direction: row;
    align-items: center;
    justify-content: space-evenly;
    padding: 0;
    width: 100%;
  }
  #opp-control .piece-wrapper,
  #my-control .piece-wrapper {
    flex-direction: row; /* 強制水平分布 */
    align-items: center;
    justify-content: center;
    gap: 8px;
  }
}

/* 彈出視窗現代化樣式 */
#export-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(3px);
  z-index: 200;
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-content {
  background: #fff;
  padding: 25px;
  border-radius: 16px;
  width: 380px;
  max-width: 90%;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  position: relative;
  font-family: inherit;
}
.modal-content h3 {
  margin-top: 0;
  color: #333;
  border-bottom: 2px solid #eee;
  padding-bottom: 10px;
}
.modal-section {
  margin-top: 15px;
}
.modal-section label {
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
  color: #555;
}
.textarea-group {
  display: flex;
  gap: 10px;
  height: 70px; /* 統一高度 */
}
.textarea-group textarea {
  flex: 1;
  resize: none;
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 8px;
  font-family: monospace;
}
.textarea-group button {
  cursor: pointer;
  padding: 0 15px;
  border: none;
  background: var(--p1-color);
  color: white;
  border-radius: 6px;
  font-weight: bold;
  transition: background 0.2s;
}
.textarea-group button:hover {
  background: #003d82;
}
.close-btn {
  position: absolute;
  top: 15px;
  right: 15px;
  width: 24px;
  height: 24px;
  cursor: pointer;
  fill: #888;
  transition: fill 0.2s;
}
.close-btn:hover {
  fill: #333;
}

```

**2. 替換 `#floating-tools` 與 `#export-modal` 的 HTML 結構:**
將工具欄(新增 Debug 按鈕)與彈出視窗的 HTML 替換為以下內容:

```html
<div id="floating-tools">
  <svg class="tool-btn" onclick="downloadBoardSVG()" viewBox="0 0 24 24" title="下載棋盤">
    <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" fill="#333" />
  </svg>
  <svg class="tool-btn" onclick="openExportModal()" viewBox="0 0 24 24" title="導出/導入">
    <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z" fill="#333" />
  </svg>
  <svg class="tool-btn tool-btn-undo" onclick="undoMove()" viewBox="0 0 24 24" title="後退">
    <path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" fill="#333" />
  </svg>
  <svg class="tool-btn tool-btn-redo" onclick="redoMove()" viewBox="0 0 24 24" title="前進" transform="scale(-1,1)">
    <path d="M11.5 8C16.15 8 20.08 11.03 21.43 15.22l-2.37.78c-1.05-3.19-4.06-5.5-7.6-5.5-1.95 0-3.73.72-5.12 1.88L10 16H1V7l3.6 3.6C6.45 8.99 8.85 8 11.5 8z" fill="#333" />
  </svg>
  <svg class="tool-btn" id="debug-btn" onclick="toggleDebug()" viewBox="0 0 24 24" title="Debug Blue">
    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" fill="#333" />
  </svg>
</div>

<div id="export-modal" style="display: none">
  <div class="modal-content">
    <svg class="close-btn" onclick="document.getElementById('export-modal').style.display = 'none'" viewBox="0 0 24 24">
      <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
    </svg>
    <h3>棋譜 導出 / 導入</h3>
    <div class="modal-section">
      <label>導出當前棋譜:</label>
      <div class="textarea-group">
        <textarea id="export-text" readonly></textarea>
        <button onclick="copyExport()">複製</button>
      </div>
    </div>
    <div class="modal-section">
      <label>導入棋譜:</label>
      <div class="textarea-group">
        <textarea id="import-text"></textarea>
        <button onclick="submitImport()">導入</button>
      </div>
    </div>
  </div>
</div>

```

---

### 二、 記譜格式改進與坐標保留 (JavaScript)

請在 `<script>` 中替換原有的 `getIdxChar`,擴大 `findValidMove` 的搜尋範圍,並新增 `getOldFormat` 函數:

```javascript
// 支持無限擴展的字母坐標 (A, B... Z, AA, AB...)
function getIdxChar(i) {
  if (i === 0) return '';
  let isUpper = i > 0;
  let n = Math.abs(i);
  let colName = '';
  while (n > 0) {
    let rem = (n - 1) % 26;
    colName = String.fromCharCode((isUpper ? 65 : 97) + rem) + colName;
    n = Math.floor((n - 1) / 26);
  }
  return colName;
}

// 產生舊版坐標格式以供驗證
function getOldFormat(t1, t2) {
  return `[(${getIdxChar(t1.idx)},${t1.N}),(${getIdxChar(t2.idx)},${t2.N})]`;
}

// 替換 commitMove 函式中的打印部分
function commitMove(pid, t1, t2, px, py, player) {
  occupied.add(`${t1.idx},${t1.N}`);
  occupied.add(`${t2.idx},${t2.N}`);
  piecesLeft[player][pid]--;
  isFirstMove = false;
  let notation = encodeNotation(pid, t1, t2);

  moveHistory = moveHistory.slice(0, historyIndex + 1);
  moveHistory.push({ notation, pid, t1, t2, px, py, player });
  historyIndex++;
  // 同時顯示新格式與舊格式坐標
  document.getElementById('test-box').innerText = `落子: ${notation} ${getOldFormat(t1, t2)}`;
}

// 替換 findValidMove 函式,擴大搜尋範圍以支持超出 Z 的坐標
function findValidMove(notationStr, player) {
  for (let pid = 0; pid < 3; pid++) {
    if (piecesLeft[player][pid] <= 0) continue;
    // 範圍擴大到 80,確保能解析 AA, AB 等坐標
    for (let n = -80; n <= 80; n++) {
      for (let idx = -80; idx <= 80; idx++) {
        if (idx === 0) continue;
        let t1 = { idx, N: n, isRight: isRightPointing(idx, n) };
        let t2 = getPairTri(t1, pid);
        if (occupied.has(`${t1.idx},${t1.N}`) || occupied.has(`${t2.idx},${t2.N}`)) continue;

        let nbs = [
          ...getNeighbors(t1.idx, t1.N, t1.isRight),
          ...getNeighbors(t2.idx, t2.N, isRightPointing(t2.idx, t2.N))
        ];
        if (nbs.some(nb => occupied.has(`${nb.idx},${nb.N}`))) {
          if (encodeNotation(pid, t1, t2) === notationStr) {
            let cx = t1.isRight
              ? (t1.idx > 0 ? t1.idx - 1 : t1.idx) * 31.1769 + 10.392
              : (t1.idx > 0 ? t1.idx - 1 : t1.idx) * 31.1769 + 20.784;
            t1.cx = cx;
            t1.cy = n * 18;
            return { t1, t2, pid, pos: calcDropPos(t1, pid) };
          }
        }
      }
    }
  }
  return null;
}

```

---

### 三、 遞迴記分系統與 Debug 模式 (JavaScript)

這是整個改進的核心。為了實現「責任傳遞」與層級遞迴,請在 `<script>` 中加入以下邏輯,並更新 UI 計算。

**1. 加入記分核心演算法:**

```javascript
// --- 記分系統與責任傳遞邏輯 ---
function getTriCenter(idx, n, isRight) {
  let halfWidth = 31.1769;
  let k_left = idx > 0 ? idx - 1 : idx;
  return isRight ? k_left * halfWidth + 10.392 : k_left * halfWidth + 20.784;
}

function getPieceTargets(piece, player) {
  let t1 = piece.t1, t2 = piece.t2;
  t1.cx = getTriCenter(t1.idx, t1.N, t1.isRight);
  t2.cx = getTriCenter(t2.idx, t2.N, t2.isRight);
  // 比較 cx 區分左右半部
  let tL = t1.cx < t2.cx ? t1 : t2;
  let tR = t1.cx < t2.cx ? t2 : t1;
  // P1 向下 (N+1),P2 向上 (N-1)
  let dir = player === 1 ? 1 : -1;
  return {
    leftTarget: { idx: tL.idx, N: tL.N + dir },
    rightTarget: { idx: tR.idx, N: tR.N + dir }
  };
}

function getPieceAt(idx, n) {
  return moveHistory.find(m => (m.t1.idx === idx && m.t1.N === n) || (m.t2.idx === idx && m.t2.N === n));
}

// 遞迴解析目標三角形是否滿足所需層級與顏色
function resolveTarget(targetTri, expectedPid, player, layer, visited) {
  let key = `${targetTri.idx},${targetTri.N},${expectedPid},${layer}`;
  if (visited.has(key)) return false; // 防止循環
  visited.add(key);

  let p = getPieceAt(targetTri.idx, targetTri.N);
  if (!p) return false;

  if (p.pid === 0) {
    // 若遇到藍色棋子,則它必須承接並獨自完成這個層級的任務
    return checkBluePieceLayer(p, player, layer, visited);
  } else if (p.pid === expectedPid) {
    // 顏色正確。若是第1層,直接達成。
    if (layer === 1) return true;
    // 若層級 > 1,紅/橙棋子將責任繼續往下傳遞(只要其下方任一分支完成即可)
    let targets = getPieceTargets(p, player);
    return resolveTarget(targets.leftTarget, expectedPid, player, layer - 1, visited) ||
           resolveTarget(targets.rightTarget, expectedPid, player, layer - 1, visited);
  }
  return false;
}

// 檢查藍色棋子是否能完成指定層級
function checkBluePieceLayer(bluePiece, player, layer, visited) {
  let targets = getPieceTargets(bluePiece, player);
  // 左下需要橙(2)或紅(1),右下需要紅(1)或橙(2)
  let reqLeft = player === 1 ? 2 : 1;
  let reqRight = player === 1 ? 1 : 2;
  
  let okL = resolveTarget(targets.leftTarget, reqLeft, player, layer, visited);
  let okR = resolveTarget(targets.rightTarget, reqRight, player, layer, visited);
  return okL && okR; // 必須左右皆滿足
}

function getBluePieceScore(bluePiece, player) {
  let score = 0;
  for (let k = 1; k <= 20; k++) { // 避免無限遞迴,上限設20層
    let visited = new Set();
    if (checkBluePieceLayer(bluePiece, player, k, visited)) {
      score++;
    } else {
      break;
    }
  }
  return score;
}

function calculateScores() {
  let s1 = 0, s2 = 0;
  for (let m of moveHistory) {
    if (m.pid === 0) {
      if (m.player === 1) s1 += getBluePieceScore(m, 1);
      if (m.player === 2) s2 += getBluePieceScore(m, 2);
    }
  }
  return { s1, s2 };
}

```

**2. 更新 `updateUI` 函式:**
使用新寫的計分系統替換原有的寫法。

```javascript
function updateUI() {
  let scores = calculateScores();
  document.getElementById('score-1').innerText = scores.s1;
  document.getElementById('score-2').innerText = scores.s2;
  
  for (let p = 1; p <= 2; p++) {
    for (let c = 0; c < 3; c++) {
      let countEl = document.getElementById(`count-${p}-${c}`);
      if (countEl) countEl.innerText = piecesLeft[p][c];
      let box = document.getElementById(`p${p}-${c}`);
      if (piecesLeft[p][c] === 0 || currentPlayer !== p) {
        box.style.opacity = '0.5';
        box.style.cursor = 'not-allowed';
      } else {
        box.style.opacity = '1';
        box.style.cursor = 'pointer';
      }
    }
  }
}

```

**3. Debug 模式實作 (JavaScript):**

```javascript
let isDebugMode = false;
function toggleDebug() {
  isDebugMode = !isDebugMode;
  const btn = document.getElementById('debug-btn');
  btn.style.background = isDebugMode ? '#ffcc00' : 'rgba(255, 255, 255, 0.8)';
  document.getElementById('test-box').innerText = isDebugMode ? 'Debug Mode ON: 點擊藍色棋子查看詳細' : 'Debug Mode OFF';
}

```

最後,請在 `setupBoardClick` 函式內的 `svg.addEventListener('click', function (e) { ... }` 頂部加入攔截邏輯:

```javascript
function setupBoardClick() {
  const svg = document.getElementById('etani');
  const dropG = document.getElementById('etanidrop');
  const pt = svg.createSVGPoint();

  svg.addEventListener('click', function (e) {
    if (hasDragged) {
      hasDragged = false;
      return;
    }
    
    // --- 新增:Debug 模式攔截點擊事件 ---
    if (isDebugMode) {
      pt.x = e.clientX; pt.y = e.clientY;
      const svgP = pt.matrixTransform(dropG.getScreenCTM().inverse());
      const halfWidth = 31.1769;
      let k = Math.floor(svgP.x / halfWidth);
      let idx = svgP.x > 0 ? k + 1 : k;
      if (svgP.x === 0) idx = 1;
      let approxN = Math.round(svgP.y / 18);
      
      let bestDist = Infinity, bestTri = null;
      for (let n = approxN - 2; n <= approxN + 2; n++) {
        let isRight = isRightPointing(idx, n);
        let k_left = idx > 0 ? idx - 1 : idx;
        let cx = isRight ? k_left * halfWidth + 10.392 : k_left * halfWidth + 20.784;
        let cy = n * 18;
        let dist = Math.hypot(svgP.x - cx, svgP.y - cy);
        if (dist < bestDist) { bestDist = dist; bestTri = { idx, N: n, isRight, cx, cy }; }
      }
      
      if (bestTri) {
        let p = getPieceAt(bestTri.idx, bestTri.N);
        if (p && p.pid === 0) {
          let score = getBluePieceScore(p, p.player);
          let targets = getPieceTargets(p, p.player);
          let pL = getPieceAt(targets.leftTarget.idx, targets.leftTarget.N);
          let pR = getPieceAt(targets.rightTarget.idx, targets.rightTarget.N);
          let info = `P${p.player} 藍棋分數: ${score} | 左下: ${pL ? (pL.pid===0?'藍':pL.pid===1?'紅':'橙') : '無'} | 右下: ${pR ? (pR.pid===0?'藍':pR.pid===1?'紅':'橙') : '無'}`;
          document.getElementById('test-box').innerText = info;
        } else {
          document.getElementById('test-box').innerText = '這不是藍色棋子';
        }
      }
      return;
    }
    // --- Debug 結束 ---

    if (currentSelectedPiece === null) {
      const ctrl = document.getElementById('board-controls');
      ctrl.style.display = ctrl.style.display === 'none' ? 'block' : 'none';
      return;
    }
    document.getElementById('board-controls').style.display = 'none';
    pt.x = e.clientX;
    pt.y = e.clientY;
    const svgP = pt.matrixTransform(dropG.getScreenCTM().inverse());
    processBoardClick(svgP.x, svgP.y);
  });
}

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: 没有注册用户 和 0 访客