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

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

gemini 3.1 pro

代码: 全选

這裡為你整理了所有需要修復與改進的代碼塊。我們將依序解決撤銷重做 BUG、更新規則多語言與 HTML、修復結算面板多語言,以及全面優化 SVG 導出的動畫與數值精度。

### 1. 修改 `I18N` 字典與 `toggleLanguage` (解決第二點)

我們需要在中文和英文的 `rule` 屬性中加入第二段落的計分規則。為了讓換行符號 `<br><br>` 生效,還需要將切換語言時的 `innerText` 修改為 `innerHTML`。

**替換程式碼:** (大約在第 746 行)

```javascript
      // --- 多語言字典 ---
      const I18N = {
        zh: {
          title: '三菱棋',
          sub: '一個抽象策略遊戲,在二維與三維之間',
          ruleTitle: '規則簡介',
          rule: '雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動,分數高者獲勝。<br><br>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分。',
          startBtn: '開始遊戲',
// ... (保留中間其他翻譯) ...

```

**(大約在第 813 行)**

```javascript
        en: {
          title: 'Trirhombus',
          sub: 'An abstract strategy game between 2D and 3D',
          ruleTitle: 'Rules Overview',
          rule: 'Players take turns. The first player must place a blue piece first. The second player must place a red piece first. Play alternates thereafter. Highest score wins.<br><br>Scoring Rules: Players sit opposite each other, so your "up" is the opponent\'s "down". From each perspective, blue pieces are seen as the top face of a cube, while red and orange pieces are the side faces. Each perceived cube scores one point.',
          startBtn: 'Start Game',
// ... (保留中間其他翻譯) ...

```

**(大約在第 1358 行 - `toggleLanguage` 函數中)**

將原本的 `.innerText` 替換為 `.innerHTML`:

```javascript
        document.getElementById('text-title').innerText = getText('title');
        document.getElementById('text-sub').innerText = getText('sub');
        document.getElementById('text-rule-title').innerText = getText('ruleTitle');
        document.getElementById('rule-content').innerHTML = getText('rule'); // 這裡改用 innerHTML
        document.getElementById('text-start-btn').innerText = getText('startBtn');

```

---

### 2. 更新 HTML 中初始顯示的規則文字 (解決第二點)

請找到 `<div id="start-screen">` 內部的 `#rule-content` 元素,並同步更新它的初始 HTML:

**替換程式碼:** (大約在第 356 行)

```html
        <div class="rules-content" id="rule-content">
          雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動,分數高者獲勝。<br><br>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分。
        </div>

```

---

### 3. 修復後退(Undo)時棋子不消失的問題 (解決第一點)

由於你在盤面上新增了小白點 (`last-move-indicator`) 作為提示,它成為了 `etanidrop` 的 `lastChild`,導致原本依賴 `lastChild` 檢查 `.tiledropped` 的刪除邏輯失效。我們改為精準選取最後一個 `tiledropped` 元素。

**替換程式碼:** (在 `undoMove()` 函數中,大約第 1121 行)

```javascript
            let scores = gameLogic.undo();
            p1CurrentScore = scores.p1Score;
            p2CurrentScore = scores.p2Score;
            piecesLeft[move.player][move.pid]++;
            historyIndex--;

            let dropG = document.getElementById('etanidrop');
            let tiles = dropG.querySelectorAll('.tiledropped');
            if (tiles.length > 0) {
              dropG.removeChild(tiles[tiles.length - 1]);
            }

            isFirstMove = historyIndex < roundStart;

```

---

### 4. 修復結算視窗的語言顯示問題 (解決第三點)

把寫死的 `P1(我方)` 等字眼替換為動態獲取 `I18N` 的值。

**替換程式碼:** (在 `showRoundEnd()` 函數中,大約第 1419 行)

```javascript
        if (currentRound === 1) {
          p1Round1Score = s1;
          p2Round1Score = s2;
          p1TotalScore = s1;
          p2TotalScore = s2;
          title.innerText = getText('gameOver') + ' - Round 1';
          scoresDiv.innerHTML = `${getText('round1Score')}<br>${getText('p1You')}: ${s1} - ${getText('p2Opp')}: ${s2}`;

          actionsDiv.innerHTML = `
            <button class="btn-secondary" onclick="finalEnd()">${getText('endGameBtn')}</button>
            <button class="btn-primary" onclick="startRound2()">${getText('swapBtn')}</button>
          `;
        } else {
          p1TotalScore = p1Round1Score + s1;
          p2TotalScore = p2Round1Score + s2;
          title.innerText = getText('gameOver') + ' - Final';
          let winnerText =
            p1TotalScore > p2TotalScore
              ? getText('winnerMe')
              : p2TotalScore > p1TotalScore
                ? getText('winnerOpp')
                : getText('draw');

          scoresDiv.innerHTML = `
            ${getText('round2Score')} ${getText('p1You')}: ${s1} - ${getText('p2Opp')}: ${s2}<br>
            <hr style="margin:15px 0;">
            <strong>${getText('totalScore')} ${getText('p1You')}: ${p1TotalScore} - ${getText('p2Opp')}: ${p2TotalScore}</strong><br>
            <span style="color:var(--p2-color)">${winnerText}</span>
          `;
          actionsDiv.innerHTML = `<button class="btn-primary" onclick="restartGame()">${getText('restartBtn')}</button>`;
        }

```

---

### 5. 優化 SVG 動畫導出與數值修復 (解決第四點)

加入過濾多餘小數點的格式化函數 `fmt()`,動態計算文本動畫相對於當前視圖 `pan` 及 `scale` 的坐標,並為所有棋子加入專屬的初始退場動畫。

**替換程式碼:** (覆蓋整個 `updateDlPreview` 函數,大約第 1217 行)

```javascript
      function updateDlPreview() {
        const originalSvg = document.getElementById('etani');
        let svgClone = originalSvg.cloneNode(true);
        svgClone.setAttribute('id', 'etani-preview');

        // 定義數字格式化,最多 3 位小數並自動去掉末尾多餘的 0
        const fmt = v => Number(v.toFixed(3));

        let gridOpt = document.getElementById('dl-opt-grid').checked;
        let numOpt = document.getElementById('dl-opt-num').checked;
        let animOpt = document.getElementById('dl-opt-anim').checked;

        // 1. 處理網格
        if (!gridOpt) {
          let etboard = svgClone.querySelector('.etboard');
          let gridPath = etboard.querySelector('path');
          if (gridPath) gridPath.remove();
        }

        // 2. 獲取當前局的所有合法移動
        let roundStart = currentRound === 2 ? 36 : 0;
        let activeMoves = moveHistory.slice(roundStart, historyIndex + 1);

        // 3. 處理數字與動畫:重構 etdrop 內的棋子,確保順序與 ID 正確
        let etdrop = svgClone.querySelector('.etdrop');
        // 保留 board (含網格與紫點)
        let etboard = etdrop.querySelector('.etboard');
        etdrop.innerHTML = '';
        etdrop.appendChild(etboard);

        // 加入自定義字體
        if (numOpt || animOpt) {
          let styleEl = document.createElement('style');
          styleEl.innerHTML = `
            @font-face {
              font-family: 'ZT Nature';
              src: url('data:font/woff2;charset=utf-8;base64,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') format('woff2');
              font-weight: normal;
              font-style: normal;
              font-display: swap;
            }
          `;
          etboard.appendChild(styleEl);
        }

        // 準備全域動畫基底
        if (animOpt) {
          etdrop.setAttribute('opacity', '0');
          let baseAnim = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
          baseAnim.setAttribute('id', 'tr0');
          baseAnim.setAttribute('begin', '0;trend.end');
          baseAnim.setAttribute('attributeName', 'opacity');
          baseAnim.setAttribute('values', '0;0;1');
          baseAnim.setAttribute('fill', 'freeze');
          baseAnim.setAttribute('dur', '2s');
          etdrop.appendChild(baseAnim);
        }

        // 重繪所有目前局的棋子
        activeMoves.forEach((m, i) => {
          let px = fmt(m.px);
          let py = fmt(m.py);
          let useNode = document.createElementNS('http://www.w3.org/2000/svg', 'use');
          useNode.setAttribute('href', `#tile${m.pid}`);
          useNode.setAttribute('class', 'tiledropped');
          useNode.setAttribute('fill', TILE_COLORS[m.pid]);
          useNode.setAttribute('transform', `translate(${px}, ${py})`);

          if (animOpt) {
            let outY = fmt(m.player === 1 ? (480 - panY) / currentScale + 100 : -panY / currentScale - 100);

            // 加入初始動畫,讓棋子從目前的位置瞬間移出視窗(模擬退場的起始態)
            let initAnim = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            initAnim.setAttribute('attributeName', 'transform');
            initAnim.setAttribute('attributeType', 'XML');
            initAnim.setAttribute('type', 'translate');
            initAnim.setAttribute('from', `${px},${py}`);
            initAnim.setAttribute('to', `${px},${outY}`);
            initAnim.setAttribute('dur', '1s');
            initAnim.setAttribute('fill', 'freeze');
            initAnim.setAttribute('begin', 'tr0.begin');
            useNode.appendChild(initAnim);

            let animNode = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            animNode.setAttribute('id', `tr${i + 1}`);
            animNode.setAttribute('attributeName', 'transform');
            animNode.setAttribute('attributeType', 'XML');
            animNode.setAttribute('type', 'translate');
            animNode.setAttribute('from', `${px},${outY}`);
            animNode.setAttribute('to', `${px},${py}`);
            animNode.setAttribute('dur', '1s');
            animNode.setAttribute('fill', 'freeze');
            animNode.setAttribute('begin', `tr${i}.end`);
            useNode.appendChild(animNode);
          }
          etdrop.appendChild(useNode);

          // 處理數字
          if (numOpt) {
            let halfWidth = 31.1769;
            let cx1 = m.t1.isRight ? (m.t1.idx - 1) * halfWidth + 10.392 : (m.t1.idx - 1) * halfWidth + 20.784;
            let cy1 = m.t1.N * 18;

            let isRight2 = gameLogic.isRight(m.t2.idx, m.t2.N);
            let cx2 = isRight2 ? (m.t2.idx - 1) * halfWidth + 10.392 : (m.t2.idx - 1) * halfWidth + 20.784;
            let cy2 = m.t2.N * 18;

            let pieceCx = (cx1 + cx2) / 2;
            let pieceCy = (cy1 + cy2) / 2;

            let numStr = String(i + 1);
            let offsetX1 = -7,
              offsetY1 = 8;
            let offsetX2 = -14,
              offsetY2 = 8;
            let dx = numStr.length === 1 ? offsetX1 : offsetX2;
            let dy = numStr.length === 1 ? offsetY1 : offsetY2;

            let textNode = document.createElementNS('http://www.w3.org/2000/svg', 'text');
            textNode.setAttribute('x', fmt(pieceCx + dx).toString());
            textNode.setAttribute('y', fmt(pieceCy + dy).toString());
            textNode.setAttribute('stroke', 'none');
            textNode.setAttribute('fill', 'black');
            textNode.setAttribute('font-size', '24');
            textNode.setAttribute('font-family', 'ZT Nature');
            textNode.textContent = numStr;
            etdrop.appendChild(textNode);
          }
        });

        // 4. 動畫結算邏輯
        if (animOpt && activeMoves.length > 0) {
          let lastTrId = `tr${activeMoves.length}`;
          let trmeBase = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
          trmeBase.setAttribute('id', 'trme');
          trmeBase.setAttribute('begin', `${lastTrId}.end+1s`);
          trmeBase.setAttribute('dur', '4s');
          etdrop.appendChild(trmeBase);

          let trheBase = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
          trheBase.setAttribute('id', 'trhe');
          trheBase.setAttribute('begin', 'trme.end+1s');
          trheBase.setAttribute('dur', '4s');
          etdrop.appendChild(trheBase);

          let finalScores = gameLogic.calculateScores();
          let useElements = etdrop.querySelectorAll('.tiledropped');

          // 計算無用棋子 (拔掉後對應玩家分數不變)
          for (let i = 0; i < activeMoves.length; i++) {
            let testLogic = new HexGridGame();
            for (let j = 0; j < activeMoves.length; j++) {
              if (i === j) continue;
              let m = activeMoves[j];
              testLogic.tryPlacePiece(m.pid + 1, { x: m.t1.idx, y: m.t1.N }, { x: m.t2.idx, y: m.t2.N }, true);
            }
            let s = testLogic.calculateScores();

            let m = activeMoves[i];
            let px = fmt(m.px);
            let py = fmt(m.py);
            let len = Math.hypot(m.px, m.py) || 1;
            let outX = fmt(m.px + (m.px / len) * 400);
            let outY2 = fmt(m.py + (m.py / len) * 400);

            // 我方(P1)無用棋子
            if (s.p1Score === finalScores.p1Score) {
              let anim = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
              anim.setAttribute('attributeName', 'transform');
              anim.setAttribute('attributeType', 'XML');
              anim.setAttribute('type', 'translate');
              anim.setAttribute(
                'values',
                `${px},${py}; ${outX},${outY2}; ${outX},${outY2}; ${outX},${outY2}; ${px},${py}`
              );
              anim.setAttribute('dur', '4s');
              anim.setAttribute('fill', 'freeze');
              anim.setAttribute('begin', 'trme.begin');
              useElements[i].appendChild(anim);
            }
            // 對方(P2)無用棋子
            if (s.p2Score === finalScores.p2Score) {
              let anim = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
              anim.setAttribute('attributeName', 'transform');
              anim.setAttribute('attributeType', 'XML');
              anim.setAttribute('type', 'translate');
              anim.setAttribute(
                'values',
                `${px},${py}; ${outX},${outY2}; ${outX},${outY2}; ${outX},${outY2}; ${px},${py}`
              );
              anim.setAttribute('dur', '4s');
              anim.setAttribute('fill', 'freeze');
              anim.setAttribute('begin', 'trhe.begin');
              useElements[i].appendChild(anim);
            }
          }

          // 添加分數文字動畫:動態計算相對於縮放及平移的畫面坐標
          let p1TextX = fmt((480 - panX) / currentScale + 12 / currentScale);
          let p1TextY = fmt((480 - panY) / currentScale - 12 / currentScale);
          let p1SlideX = fmt(-144 / currentScale);

          let p1Text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
          p1Text.setAttribute('id', 'trmetxt');
          p1Text.setAttribute('x', p1TextX.toString());
          p1Text.setAttribute('y', p1TextY.toString());
          p1Text.setAttribute('stroke', 'none');
          p1Text.setAttribute('fill', 'black');
          p1Text.setAttribute('font-size', '60');
          p1Text.setAttribute('font-family', 'ZT Nature');
          p1Text.textContent = finalScores.p1Score.toString();

          let p1Anim = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
          p1Anim.setAttribute('attributeName', 'transform');
          p1Anim.setAttribute('attributeType', 'XML');
          p1Anim.setAttribute('type', 'translate');
          p1Anim.setAttribute('values', `0,0; ${p1SlideX},0; ${p1SlideX},0; ${p1SlideX},0; 0,0`);
          p1Anim.setAttribute('dur', '4s');
          p1Anim.setAttribute('fill', 'freeze');
          p1Anim.setAttribute('begin', 'trme.begin');
          p1Text.appendChild(p1Anim);
          etboard.appendChild(p1Text);

          let p2TextX = fmt(panX / currentScale + 12 / currentScale);
          let p2TextY = fmt(panY / currentScale - 12 / currentScale);
          let p2SlideX = fmt(-144 / currentScale);

          let p2Text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
          p2Text.setAttribute('id', 'trhetxt');
          p2Text.setAttribute('x', p2TextX.toString());
          p2Text.setAttribute('y', p2TextY.toString());
          p2Text.setAttribute('stroke', 'none');
          p2Text.setAttribute('fill', 'black');
          p2Text.setAttribute('font-size', '60');
          p2Text.setAttribute('font-family', 'ZT Nature');
          p2Text.setAttribute('transform', 'rotate(180)');
          p2Text.textContent = finalScores.p2Score.toString();

          let p2Anim = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
          p2Anim.setAttribute('attributeName', 'transform');
          p2Anim.setAttribute('attributeType', 'XML');
          p2Anim.setAttribute('type', 'translate');
          p2Anim.setAttribute('values', `0,0; ${p2SlideX},0; ${p2SlideX},0; ${p2SlideX},0; 0,0`);
          p2Anim.setAttribute('dur', '4s');
          p2Anim.setAttribute('fill', 'freeze');
          p2Anim.setAttribute('additive', 'sum'); // 疊加在 rotate(180) 上
          p2Anim.setAttribute('begin', 'trhe.begin');
          p2Text.appendChild(p2Anim);
          etboard.appendChild(p2Text);

          // 結尾漸隱
          let endAnim = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
          endAnim.setAttribute('id', 'trend');
          endAnim.setAttribute('begin', 'trhe.end+1s');
          endAnim.setAttribute('attributeName', 'opacity');
          endAnim.setAttribute('values', '1;1;0');
          endAnim.setAttribute('fill', 'freeze');
          endAnim.setAttribute('dur', '2s');
          etdrop.appendChild(endAnim);
        }

        // 5. 序列化與渲染預覽
        let serializer = new XMLSerializer();
        currentDlSvgSource = serializer.serializeToString(svgClone);
        if (!currentDlSvgSource.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
          currentDlSvgSource = currentDlSvgSource.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
        }

        document.getElementById('dl-preview-container').innerHTML = '';
        document.getElementById('dl-preview-container').appendChild(svgClone);

        let sizeKb = new Blob([currentDlSvgSource]).size.toLocaleString();
        document.getElementById('dl-size').innerText = sizeKb + ' Bytes';
      }

```

代码: 全选

好的,為你調整 **第 1 點(I18N 字典)** 與 **第 2 點(HTML 初始文字)** 的修改方案。我們將文字更新為包含「立方體可堆疊累加」,並將原本的 `<br><br>` 全部替換為更符合語意且結構乾淨的 `<p>` 標籤。

### 1. 修改 `I18N` 字典與 `toggleLanguage`

請將 JavaScript 中的 `I18N` 字典中關於 `rule` 的部分替換為以下程式碼(包含英文翻譯的更新):

**替換程式碼:** (大約在第 746 行起)

```javascript
      // --- 多語言字典 ---
      const I18N = {
        zh: {
          title: '三菱棋',
          sub: '一個抽象策略遊戲,在二維與三維之間',
          ruleTitle: '規則簡介',
          rule: '<p>雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動,分數高者獲勝。</p><p>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。</p>',
          startBtn: '開始遊戲',
// ... (保留中間其他中文翻譯) ...

```

**(大約在第 813 行起)**

```javascript
        en: {
          title: 'Trirhombus',
          sub: 'An abstract strategy game between 2D and 3D',
          ruleTitle: 'Rules Overview',
          rule: '<p>Players take turns. The first player must place a blue piece first. The second player must place a red piece first. Play alternates thereafter. Highest score wins.</p><p>Scoring Rules: Players sit opposite each other, so your "up" is the opponent\'s "down". From each perspective, blue pieces are seen as the top face of a cube, while red and orange pieces are the side faces. Each perceived cube scores one point, and cubes can be stacked and accumulated.</p>',
          startBtn: 'Start Game',
// ... (保留中間其他英文翻譯) ...

```

*註:請確保在 `toggleLanguage` 函數中,`document.getElementById('rule-content')` 依然是使用 `.innerHTML` 賦值(如前一次修改所示),這樣 `<p>` 標籤才能正確渲染。*

---

### 2. 更新 HTML 中初始顯示的規則文字

同步將 HTML 結構中初始顯示的文字改用 `<p>` 標籤包裹:

**替換程式碼:** (大約在第 356 行)

```html
        <div class="rules-content" id="rule-content">
          <p>雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動,分數高者獲勝。</p>
          <p>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。</p>
        </div>

```

*(提示:如果使用 `<p>` 標籤後發現段落上下的間距過大或過小,可以在 CSS 的 `#rule-content p` 中加入 `margin: 10px 0;` 進行微調。)*
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

trirhombus2880.html

導出動畫以及標數字的功能已做好。

規則需要加上一句「除了第一個棋子之外,所落下的棋子必須跟場上已有棋子貼合。」
附件
trirhombus2880.html.7z
(35.5 KiB) 已下载 7 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

輸給expert,所以現在的AI已經足夠強了。

代码: 全选

[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,1),(0,2)] [(1,-2),(1,-1)] [(1,1),(1,2)] [(1,3),(2,3)] [(2,4),(2,5)] [(0,4),(1,4)] [(0,5),(0,6)] [(2,-1),(2,0)] [(-1,-3),(0,-3)] [(-1,1),(-1,2)] [(-1,-2),(-1,-1)] [(-2,-2),(-2,-1)] [(1,5),(1,6)] [(-3,-1),(-3,0)] [(3,-1),(3,0)] [(3,-3),(3,-2)] [(2,-4),(3,-4)] [(3,1),(4,1)] [(4,0),(5,0)] [(-1,-5),(-1,-4)] [(0,-6),(0,-5)] [(3,4),(3,5)] [(2,-3),(2,-2)] [(5,-2),(5,-1)] [(3,-5),(4,-5)] [(2,1),(2,2)] [(6,-2),(6,-1)] [(-1,3),(0,3)] [(-1,4),(-1,5)] [(2,6),(3,6)] [(2,7),(2,8)] [(-2,0),(-1,0)] [(5,-3),(6,-3)] [(3,2),(3,3)] [(0,0),(1,0)] [(1,1),(1,2)] [(0,-2),(0,-1)] [(0,1),(0,2)] [(2,1),(2,2)] [(1,-1),(2,-1)] [(1,3),(2,3)] [(3,1),(3,2)] [(2,-3),(2,-2)] [(2,-4),(3,-4)] [(0,4),(1,4)] [(3,-3),(3,-2)] [(4,-3),(4,-2)] [(2,0),(3,0)] [(2,-6),(2,-5)] [(-1,-3),(0,-3)] [(-1,-1),(-1,0)] [(-2,-1),(-2,0)] [(4,-5),(4,-4)] [(-2,1),(-2,2)] [(-1,-5),(-1,-4)] [(-1,1),(-1,2)] [(3,-6),(3,-5)] [(-2,-2),(-1,-2)] [(0,-5),(0,-4)] [(-3,-2),(-3,-1)] [(-1,3),(0,3)] [(-3,0),(-3,1)] [(1,-3),(1,-2)] [(-3,-3),(-2,-3)] [(1,-5),(1,-4)] [(1,5),(1,6)] [(-1,5),(0,5)] [(-1,6),(-1,7)] [(3,-1),(4,-1)] [(0,6),(0,7)]
Screenshot from 2026-07-22 09-10-59.png
Screenshot from 2026-07-22 09-10-59.png (12.34 KiB) 查看 211 次

代码: 全选

<svg id="etani-preview" version="1.1" xmlns="http://www.w3.org/2000/svg" width="480" height="480" viewBox="0 0 480 480" preserveAspectRatio="xMidYMid meet">
          <defs>
            <g id="tile0"><path d="M 0,-18 L 31.177,0 L 0,18 L -31.177,0 Z"/></g>
            <g id="tile1"><path d="M 15.588,9 L -15.588,27 L -15.588,-9 L 15.588,-27 Z"/></g>
            <g id="tile2"><path d="M -15.588,9 L -15.588,-27 L 15.588,-9 L 15.588,27 Z"/></g>
          </defs>
          <g class="etdrop" stroke="#777" stroke-width="2" stroke-linejoin="round" transform="translate(206.000, 214.000) scale(1.240)" id="etanidrop"><g class="etboard"><path d="M -1496.491,-1200 L -1496.491,1200 M -1465.314,-1200 L -1465.314,1200 M -1434.137,-1200 L -1434.137,1200 M -1402.960,-1200 L -1402.960,1200 M -1371.784,-1200 L -1371.784,1200 M -1340.607,-1200 L -1340.607,1200 M -1309.430,-1200 L -1309.430,1200 M -1278.253,-1200 L -1278.253,1200 M -1247.076,-1200 L -1247.076,1200 M -1215.899,-1200 L -1215.899,1200 M -1184.722,-1200 L -1184.722,1200 M -1153.545,-1200 L -1153.545,1200 M -1122.368,-1200 L -1122.368,1200 M -1091.191,-1200 L -1091.191,1200 M -1060.015,-1200 L -1060.015,1200 M -1028.838,-1200 L -1028.838,1200 M -997.661,-1200 L -997.661,1200 M -966.484,-1200 L -966.484,1200 M -935.307,-1200 L -935.307,1200 M -904.130,-1200 L -904.130,1200 M -872.953,-1200 L -872.953,1200 M -841.776,-1200 L -841.776,1200 M -810.599,-1200 L -810.599,1200 M -779.423,-1200 L -779.423,1200 M -748.246,-1200 L -748.246,1200 M -717.069,-1200 L -717.069,1200 M -685.892,-1200 L -685.892,1200 M -654.715,-1200 L -654.715,1200 M -623.538,-1200 L -623.538,1200 M -592.361,-1200 L -592.361,1200 M -561.184,-1200 L -561.184,1200 M -530.007,-1200 L -530.007,1200 M -498.830,-1200 L -498.830,1200 M -467.654,-1200 L -467.654,1200 M -436.477,-1200 L -436.477,1200 M -405.300,-1200 L -405.300,1200 M -374.123,-1200 L -374.123,1200 M -342.946,-1200 L -342.946,1200 M -311.769,-1200 L -311.769,1200 M -280.592,-1200 L -280.592,1200 M -249.415,-1200 L -249.415,1200 M -218.238,-1200 L -218.238,1200 M -187.061,-1200 L -187.061,1200 M -155.885,-1200 L -155.885,1200 M -124.708,-1200 L -124.708,1200 M -93.531,-1200 L -93.531,1200 M -62.354,-1200 L -62.354,1200 M -31.177,-1200 L -31.177,1200 M 0.000,-1200 L 0.000,1200 M 31.177,-1200 L 31.177,1200 M 62.354,-1200 L 62.354,1200 M 93.531,-1200 L 93.531,1200 M 124.708,-1200 L 124.708,1200 M 155.885,-1200 L 155.885,1200 M 187.061,-1200 L 187.061,1200 M 218.238,-1200 L 218.238,1200 M 249.415,-1200 L 249.415,1200 M 280.592,-1200 L 280.592,1200 M 311.769,-1200 L 311.769,1200 M 342.946,-1200 L 342.946,1200 M 374.123,-1200 L 374.123,1200 M 405.300,-1200 L 405.300,1200 M 436.477,-1200 L 436.477,1200 M 467.654,-1200 L 467.654,1200 M 498.830,-1200 L 498.830,1200 M 530.007,-1200 L 530.007,1200 M 561.184,-1200 L 561.184,1200 M 592.361,-1200 L 592.361,1200 M 623.538,-1200 L 623.538,1200 M 654.715,-1200 L 654.715,1200 M 685.892,-1200 L 685.892,1200 M 717.069,-1200 L 717.069,1200 M 748.246,-1200 L 748.246,1200 M 779.423,-1200 L 779.423,1200 M 810.599,-1200 L 810.599,1200 M 841.776,-1200 L 841.776,1200 M 872.953,-1200 L 872.953,1200 M 904.130,-1200 L 904.130,1200 M 935.307,-1200 L 935.307,1200 M 966.484,-1200 L 966.484,1200 M 997.661,-1200 L 997.661,1200 M 1028.838,-1200 L 1028.838,1200 M 1060.015,-1200 L 1060.015,1200 M 1091.191,-1200 L 1091.191,1200 M 1122.368,-1200 L 1122.368,1200 M 1153.545,-1200 L 1153.545,1200 M 1184.722,-1200 L 1184.722,1200 M 1215.899,-1200 L 1215.899,1200 M 1247.076,-1200 L 1247.076,1200 M 1278.253,-1200 L 1278.253,1200 M 1309.430,-1200 L 1309.430,1200 M 1340.607,-1200 L 1340.607,1200 M 1371.784,-1200 L 1371.784,1200 M 1402.960,-1200 L 1402.960,1200 M 1434.137,-1200 L 1434.137,1200 M 1465.314,-1200 L 1465.314,1200 M 1496.491,-1200 L 1496.491,1200 M -1200,-1574.820 L 1200,-189.180 M -1200,-153.180 L 1200,-1538.820 M -1200,-1538.820 L 1200,-153.180 M -1200,-117.180 L 1200,-1502.820 M -1200,-1502.820 L 1200,-117.180 M -1200,-81.180 L 1200,-1466.820 M -1200,-1466.820 L 1200,-81.180 M -1200,-45.180 L 1200,-1430.820 M -1200,-1430.820 L 1200,-45.180 M -1200,-9.180 L 1200,-1394.820 M -1200,-1394.820 L 1200,-9.180 M -1200,26.820 L 1200,-1358.820 M -1200,-1358.820 L 1200,26.820 M -1200,62.820 L 1200,-1322.820 M -1200,-1322.820 L 1200,62.820 M -1200,98.820 L 1200,-1286.820 M -1200,-1286.820 L 1200,98.820 M -1200,134.820 L 1200,-1250.820 M -1200,-1250.820 L 1200,134.820 M -1200,170.820 L 1200,-1214.820 M -1200,-1214.820 L 1200,170.820 M -1200,206.820 L 1200,-1178.820 M -1200,-1178.820 L 1200,206.820 M -1200,242.820 L 1200,-1142.820 M -1200,-1142.820 L 1200,242.820 M -1200,278.820 L 1200,-1106.820 M -1200,-1106.820 L 1200,278.820 M -1200,314.820 L 1200,-1070.820 M -1200,-1070.820 L 1200,314.820 M -1200,350.820 L 1200,-1034.820 M -1200,-1034.820 L 1200,350.820 M -1200,386.820 L 1200,-998.820 M -1200,-998.820 L 1200,386.820 M -1200,422.820 L 1200,-962.820 M -1200,-962.820 L 1200,422.820 M -1200,458.820 L 1200,-926.820 M -1200,-926.820 L 1200,458.820 M -1200,494.820 L 1200,-890.820 M -1200,-890.820 L 1200,494.820 M -1200,530.820 L 1200,-854.820 M -1200,-854.820 L 1200,530.820 M -1200,566.820 L 1200,-818.820 M -1200,-818.820 L 1200,566.820 M -1200,602.820 L 1200,-782.820 M -1200,-782.820 L 1200,602.820 M -1200,638.820 L 1200,-746.820 M -1200,-746.820 L 1200,638.820 M -1200,674.820 L 1200,-710.820 M -1200,-710.820 L 1200,674.820 M -1200,710.820 L 1200,-674.820 M -1200,-674.820 L 1200,710.820 M -1200,746.820 L 1200,-638.820 M -1200,-638.820 L 1200,746.820 M -1200,782.820 L 1200,-602.820 M -1200,-602.820 L 1200,782.820 M -1200,818.820 L 1200,-566.820 M -1200,-566.820 L 1200,818.820 M -1200,854.820 L 1200,-530.820 M -1200,-530.820 L 1200,854.820 M -1200,890.820 L 1200,-494.820 M -1200,-494.820 L 1200,890.820 M -1200,926.820 L 1200,-458.820 M -1200,-458.820 L 1200,926.820 M -1200,962.820 L 1200,-422.820 M -1200,-422.820 L 1200,962.820 M -1200,998.820 L 1200,-386.820 M -1200,-386.820 L 1200,998.820 M -1200,1034.820 L 1200,-350.820 M -1200,-350.820 L 1200,1034.820 M -1200,1070.820 L 1200,-314.820 M -1200,-314.820 L 1200,1070.820 M -1200,1106.820 L 1200,-278.820 M -1200,-278.820 L 1200,1106.820 M -1200,1142.820 L 1200,-242.820 M -1200,-242.820 L 1200,1142.820 M -1200,1178.820 L 1200,-206.820 M -1200,-206.820 L 1200,1178.820 M -1200,1214.820 L 1200,-170.820 M -1200,-170.820 L 1200,1214.820 M -1200,1250.820 L 1200,-134.820 M -1200,-134.820 L 1200,1250.820 M -1200,1286.820 L 1200,-98.820 M -1200,-98.820 L 1200,1286.820 M -1200,1322.820 L 1200,-62.820 M -1200,-62.820 L 1200,1322.820 M -1200,1358.820 L 1200,-26.820 M -1200,-26.820 L 1200,1358.820 M -1200,1394.820 L 1200,9.180 M -1200,9.180 L 1200,1394.820 M -1200,1430.820 L 1200,45.180 M -1200,45.180 L 1200,1430.820 M -1200,1466.820 L 1200,81.180 M -1200,81.180 L 1200,1466.820 M -1200,1502.820 L 1200,117.180 M -1200,117.180 L 1200,1502.820 M -1200,1538.820 L 1200,153.180 " stroke="#b0b0b0" stroke-width="1" stroke-dasharray="5 5" fill="none"/><style xmlns="http://www.w3.org/1999/xhtml">
            @font-face {
              font-family: 'ZT Nature';
              src: url('data:font/woff2;charset=utf-8;base64,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') format('woff2');
              font-weight: normal;
              font-style: normal;
              font-display: swap;
            }
          </style></g><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(0, 0)"/><text x="-7" y="8" stroke="none" fill="black" font-size="24" font-family="ZT Nature">1</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(15.588, 27)"/><text x="8.588" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">2</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(-15.589, -27)"/><text x="-22.589" y="-19" stroke="none" fill="black" font-size="24" font-family="ZT Nature">3</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-15.589, 27)"/><text x="-22.589" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">4</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(46.765, 27)"/><text x="39.765" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">5</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(31.177, -18)"/><text x="24.176" y="-10" stroke="none" fill="black" font-size="24" font-family="ZT Nature">6</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(31.176, 54)"/><text x="24.176" y="62" stroke="none" fill="black" font-size="24" font-family="ZT Nature">7</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(77.942, 27)"/><text x="70.942" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">8</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(46.765, -45)"/><text x="39.765" y="-37" stroke="none" fill="black" font-size="24" font-family="ZT Nature">9</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(62.354, -72)"/><text x="48.353" y="-64" stroke="none" fill="black" font-size="24" font-family="ZT Nature">10</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-0.001, 72)"/><text x="-14" y="80" stroke="none" fill="black" font-size="24" font-family="ZT Nature">11</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(77.942, -45)"/><text x="63.942" y="-37" stroke="none" fill="black" font-size="24" font-family="ZT Nature">12</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(109.119, -45)"/><text x="95.119" y="-37" stroke="none" fill="black" font-size="24" font-family="ZT Nature">13</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(62.353, 0)"/><text x="48.353" y="8" stroke="none" fill="black" font-size="24" font-family="ZT Nature">14</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(46.765, -99)"/><text x="32.765" y="-91" stroke="none" fill="black" font-size="24" font-family="ZT Nature">15</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-31.178, -54)"/><text x="-45.177" y="-46" stroke="none" fill="black" font-size="24" font-family="ZT Nature">16</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(-46.766, -9)"/><text x="-60.766" y="-1" stroke="none" fill="black" font-size="24" font-family="ZT Nature">17</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-77.943, -9)"/><text x="-91.943" y="-1" stroke="none" fill="black" font-size="24" font-family="ZT Nature">18</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(109.119, -81)"/><text x="95.119" y="-73" stroke="none" fill="black" font-size="24" font-family="ZT Nature">19</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-77.943, 27)"/><text x="-91.943" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">20</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(-46.766, -81)"/><text x="-60.766" y="-73" stroke="none" fill="black" font-size="24" font-family="ZT Nature">21</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(-46.766, 27)"/><text x="-60.766" y="35" stroke="none" fill="black" font-size="24" font-family="ZT Nature">22</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(77.942, -99)"/><text x="63.942" y="-91" stroke="none" fill="black" font-size="24" font-family="ZT Nature">23</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-62.355, -36)"/><text x="-76.354" y="-28" stroke="none" fill="black" font-size="24" font-family="ZT Nature">24</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-15.589, -81)"/><text x="-29.589" y="-73" stroke="none" fill="black" font-size="24" font-family="ZT Nature">25</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-109.12, -27)"/><text x="-123.12" y="-19" stroke="none" fill="black" font-size="24" font-family="ZT Nature">26</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-31.178, 54)"/><text x="-45.177" y="62" stroke="none" fill="black" font-size="24" font-family="ZT Nature">27</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-109.12, 9)"/><text x="-123.12" y="17" stroke="none" fill="black" font-size="24" font-family="ZT Nature">28</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(15.588, -45)"/><text x="1.588" y="-37" stroke="none" fill="black" font-size="24" font-family="ZT Nature">29</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-93.531, -54)"/><text x="-107.531" y="-46" stroke="none" fill="black" font-size="24" font-family="ZT Nature">30</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(15.588, -81)"/><text x="1.588" y="-73" stroke="none" fill="black" font-size="24" font-family="ZT Nature">31</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(15.588, 99)"/><text x="1.588" y="107" stroke="none" fill="black" font-size="24" font-family="ZT Nature">32</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(-31.178, 90)"/><text x="-45.177" y="98" stroke="none" fill="black" font-size="24" font-family="ZT Nature">33</text><use href="#tile2" class="tiledropped" fill="#FF8C00" transform="translate(-46.766, 117)"/><text x="-60.766" y="125" stroke="none" fill="black" font-size="24" font-family="ZT Nature">34</text><use href="#tile0" class="tiledropped" fill="#00BFFF" transform="translate(93.53, -18)"/><text x="79.53" y="-10" stroke="none" fill="black" font-size="24" font-family="ZT Nature">35</text><use href="#tile1" class="tiledropped" fill="#FF0000" transform="translate(-15.589, 117)"/><text x="-29.589" y="125" stroke="none" fill="black" font-size="24" font-family="ZT Nature">36</text></g>
        </svg>
SVG preview
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

改進:
一,更改導入導出的記譜格式


在導入導出的文本格式中,首先把横坐標改為字母,0為a,-1為b,以此類推負數為小寫字母,1為A,2為B,以此類推正數為大寫字母。例如[(1,-2),(1,-1)]將改為[(A,-2),(A,-1)],[(-1,3),(0,3)]將改為[(b,3),(a,3)]。

在此基礎上,先手方第一手藍色棋子坐標為[(a,0),(A,0)],棋譜記作「0」。

如果有藍色棋子的横坐標為a和A,則如果其縱坐標為正數則為「A$」,負數則為「a$」,$代表無符號數字。比如[(a,6),(A,6)]記作「A6」,[(a,-2),(A,-2)]記作「a2」。

如果有藍色棋子不屬於以上情況,則記作「前字母$後字母」,$為無符號數字。當其縱坐標為正數時,前字母和後字母為字母順序,否則為倒序。比如[(d,-1),(c,-1)]記作「d1c」,[(B,7),(A,7)]記作「A7B」。

紅色或橙色棋子,記作「前$字母後$」,$代表無符號數字。如果其縱坐標存在正數,則前數字小於後數字,否則前數字大於後數字。比如[(B,8),(B,9)]記作「8B9」,[(c,-2),(c,-3)]記作「3c2」。

棋譜中的每一手棋都用空白隔開,則第一手為我方落子,第二手為對方,依次類推。

如「[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,2),(0,1)] [(1,-2),(1,-1)] [(0,-3),(-1,-3)]」將記為「0 2a1 1a2 2A1 b3a」。

這個記譜格式將只用於導入和導出,不要改變原先程式中的數據格式。

二,導出svg顯示的數字調小一些

當導出svg時,如果勾選了「數字」,則把它的字體大小改小一些。

三,規則文字變動

落子規則:雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動。除了第一手棋之外,每一手棋都要跟場上已有棋子貼合。不可形成空洞。

計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。

四,中文時把P1和P2去掉,英文時則只顯示P1和P2

當一局結束時,中文應只顯示「我方」和「對方」,不必顯示「P1」和「P2」。

請明確給出所要修改的地方和所要替換的代碼。

代码: 全选

改進:
一,更改導入導出的記譜格式


在導入導出的文本格式中,首先把横坐標改為字母,0為a,-1為b,以此類推負數為小寫字母,1為A,2為B,以此類推正數為大寫字母。例如[(1,-2),(1,-1)]將改為[(A,-2),(A,-1)],[(-1,3),(0,3)]將改為[(b,3),(a,3)]。

在此基礎上,先手方第一手藍色棋子坐標為[(a,0),(A,0)],棋譜記作「0」。

如果有藍色棋子的横坐標為a和A,則如果其縱坐標為正數則為「A$」,負數則為「a$」,$代表無符號數字。比如[(a,6),(A,6)]記作「A6」,[(a,-2),(A,-2)]記作「a2」。

如果有藍色棋子不屬於以上情況,則記作「前字母$後字母」,$為無符號數字。當其縱坐標為正數時,前字母和後字母為字母順序,否則為倒序。比如[(d,-1),(c,-1)]記作「d1c」,[(B,7),(A,7)]記作「A7B」。

紅色或橙色棋子,記作「前$字母後$」,$代表無符號數字。如果其縱坐標存在正數,則前數字小於後數字,否則前數字大於後數字。比如[(B,8),(B,9)]記作「8B9」,[(c,-2),(c,-3)]記作「3c2」。

棋譜中的每一手棋都用空白隔開,則第一手為我方落子,第二手為對方,依次類推。

如「[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,2),(0,1)] [(1,-2),(1,-1)] [(0,-3),(-1,-3)]」將記為「0 2a1 1a2 2A1 b3a」。

這個記譜格式將只用於導入和導出,不要改變原先程式中的數據格式。

二,導出svg顯示的數字調小一些

當導出svg時,如果勾選了「數字」,則把它的字體大小改小一些。

三,規則文字變動

落子規則:雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動。除了第一手棋之外,每一手棋都要跟場上已有棋子貼合。不可形成空洞。

計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。 

四,中文時把P1和P2去掉,英文時則只顯示P1和P2

當一局結束時,中文應只顯示「我方」和「對方」,不必顯示「P1」和「P2」。

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

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

gemini 3.1 pro

代码: 全选

以下為您所要求的修改,請在原有的 HTML 檔案中找到對應的部分並進行替換:

### 1. 新增棋譜轉換輔助函數

請在 `<script>` 標籤的最上方(`let gameLogic = null;` 的上方)加入以下三個函數:

```javascript
      function indexToLetter(x) {
        if (x === 0) return 'a';
        if (x > 0) return String.fromCharCode(64 + x);
        return String.fromCharCode(97 - x);
      }

      function letterToIndex(char) {
        if (char === 'a') return 0;
        let code = char.charCodeAt(0);
        if (code >= 65 && code <= 90) return code - 64;
        if (code >= 98 && code <= 122) return 97 - code;
        return 0;
      }

      function getNewNotation(pid, x1, y1, x2, y2) {
        if (x1 > x2 || (x1 === x2 && y1 > y2)) {
          let tx = x1, ty = y1;
          x1 = x2; y1 = y2;
          x2 = tx; y2 = ty;
        }
        if (pid === 0) {
          if (x1 === 0 && x2 === 1 && y1 === 0 && y2 === 0) return "0";
          let y = Math.abs(y1);
          if (x1 === 0 && x2 === 1) return y1 > 0 ? "A" + y : "a" + y;
          let l1 = indexToLetter(x1);
          let l2 = indexToLetter(x2);
          let isAsc = l1.toLowerCase().localeCompare(l2.toLowerCase()) < 0;
          if (y1 > 0) {
            return isAsc ? (l1 + y + l2) : (l2 + y + l1);
          } else {
            return isAsc ? (l2 + y + l1) : (l1 + y + l2);
          }
        } else {
          let L = indexToLetter(x1);
          let n1 = Math.abs(y1), n2 = Math.abs(y2);
          if (y1 > 0 || y2 > 0) {
            return Math.min(n1, n2) + L + Math.max(n1, n2);
          } else {
            return Math.max(n1, n2) + L + Math.min(n1, n2);
          }
        }
      }

      let gameLogic = null;
      let p1CurrentScore = 0;

```

### 2. 替換多國語言與規則字串

找到 `const I18N = {` 中的這部分:

**原代碼:**

```javascript
          ruleTitle: '規則簡介',
          rule: '<p>雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動,分數高者獲勝。</p><p>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。</p>',
// ...略...
          p1You: 'P1(我方)',
          p2Opp: 'P2(對方)',

```

與英文中的:

```javascript
          p1You: 'P1 (You)',
          p2Opp: 'P2 (Opp)',

```

**替換為:**

```javascript
          ruleTitle: '規則簡介',
          rule: '<p>雙方輪流落子,先手方第一手必須下藍色棋子,後手方第一手必須下紅色棋子,此後雙方輪流行動。除了第一手棋之外,每一手棋都要跟場上已有棋子貼合。不可形成空洞。</p><p>計分規則:雙方相對而坐,則我方的上面是對方的下面。從各方視角望去,藍色棋子視為一個立方體的頂面,紅色和橙色棋子視為側面,一個立方體記一分,立方體可堆疊累加。</p>',
// ...(中間保留原樣)...
          p1You: '我方',
          p2Opp: '對方',

```

英文替換為:

```javascript
          p1You: 'P1',
          p2Opp: 'P2',

```

### 3. 將原先產生棋譜字串的地方改用新格式

找到 `commitMove` 函數內部的字串組合:

**原代碼:**

```javascript
        // 確保坐標從小到大排序
        let sortedC1, sortedC2;
        if (t1.idx < t2.idx || (t1.idx === t2.idx && t1.N < t2.N)) {
          sortedC1 = t1;
          sortedC2 = t2;
        } else {
          sortedC1 = t2;
          sortedC2 = t1;
        }
        let notation = `[(${sortedC1.idx},${sortedC1.N}),(${sortedC2.idx},${sortedC2.N})]`;

```

**替換為:**

```javascript
        let notation = getNewNotation(pid, t1.idx, t1.N, t2.idx, t2.N);

```

找到 `autoPlayFirstBlue` 函數內部的字串組合:

**原代碼:**

```javascript
            if (result.success) {
              p1CurrentScore = result.scores.p1Score;
              p2CurrentScore = result.scores.p2Score;
            }
            let notation = `[(${t1.idx},${t1.N}),(${t2.idx},${t2.N})]`;

```

**替換為:**

```javascript
            if (result.success) {
              p1CurrentScore = result.scores.p1Score;
              p2CurrentScore = result.scores.p2Score;
            }
            let notation = getNewNotation(0, t1.idx, t1.N, t2.idx, t2.N);

```

找到 `buildMinimaxNodeDOM` 函數內部的字串組合:

**原代碼:**

```javascript
        let pieceName = pid === 0 ? '🔵藍' : pid === 1 ? '🔴紅' : '🟠橙';
        let notation = `[(${c1.x},${c1.y}),(${c2.x},${c2.y})]`;

```

**替換為:**

```javascript
        let pieceName = pid === 0 ? '🔵藍' : pid === 1 ? '🔴紅' : '🟠橙';
        let notation = getNewNotation(pid, c1.x, c1.y, c2.x, c2.y);

```

找到 `buildTreeNodeDOM` 函數內部的字串組合:

**原代碼:**

```javascript
        let pieceName = pid === 0 ? '🔵藍' : pid === 1 ? '🔴紅' : '🟠橙';
        let notation = `[(${c1.x},${c1.y}),(${c2.x},${c2.y})]`;

```

**替換為:**

```javascript
        let pieceName = pid === 0 ? '🔵藍' : pid === 1 ? '🔴紅' : '🟠橙';
        let notation = getNewNotation(pid, c1.x, c1.y, c2.x, c2.y);

```

### 4. 修改導入解析邏輯 (`submitImport`)

找到 `submitImport` 函數內部解析 tokens 的迴圈:

**原代碼:**

```javascript
          for (let i = 0; i < tokens.length; i++) {
            let match = tokens[i].match(/\[\((-?\d+),(-?\d+)\),\((-?\d+),(-?\d+)\)\]/);
            if (!match) throw new Error('格式錯誤');
            let t1_idx = parseInt(match[1]),
              t1_N = parseInt(match[2]);
            let t2_idx = parseInt(match[3]),
              t2_N = parseInt(match[4]);

            // 推導棋子種類 (0: 藍, 1: 紅, 2: 橙)
            let pid = 0;
            if (t1_N !== t2_N) {
              let isRight = gameLogic.isRight(t1_idx, t1_N);
              let dn = t2_N - t1_N;
              pid = isRight ? (dn === -1 ? 1 : 2) : dn === 1 ? 1 : 2;
            }

            let isRight1 = gameLogic.isRight(t1_idx, t1_N);

```

**替換為:**

```javascript
          for (let i = 0; i < tokens.length; i++) {
            let token = tokens[i];
            let t1_idx, t1_N, t2_idx, t2_N, pid;

            if (token === "0") {
              t1_idx = 0; t1_N = 0;
              t2_idx = 1; t2_N = 0;
              pid = 0;
            } else {
              let match1 = token.match(/^([aA])(\d+)$/);
              let match2 = token.match(/^([A-Za-z])(\d+)([A-Za-z])$/);
              let match3 = token.match(/^(\d+)([A-Za-z])(\d+)$/);

              if (match1) {
                pid = 0;
                t1_idx = 0;
                t2_idx = 1;
                t1_N = t2_N = match1[1] === 'A' ? parseInt(match1[2]) : -parseInt(match1[2]);
              } else if (match2) {
                pid = 0;
                let L1 = match2[1], L2 = match2[3];
                t1_idx = letterToIndex(L1);
                t2_idx = letterToIndex(L2);
                let isAsc = L1.toLowerCase().localeCompare(L2.toLowerCase()) < 0;
                t1_N = t2_N = isAsc ? parseInt(match2[2]) : -parseInt(match2[2]);
              } else if (match3) {
                let n1 = parseInt(match3[1]), n2 = parseInt(match3[3]);
                t1_idx = t2_idx = letterToIndex(match3[2]);
                if (n1 < n2) {
                  t1_N = n1; t2_N = n2;
                } else {
                  t1_N = -n1; t2_N = -n2;
                }
                let isRight = gameLogic.isRight(t1_idx, t1_N);
                let dn = t2_N - t1_N;
                pid = isRight ? (dn === -1 ? 1 : 2) : (dn === 1 ? 1 : 2);
              } else {
                throw new Error('格式錯誤');
              }
            }

            let isRight1 = gameLogic.isRight(t1_idx, t1_N);

```

### 5. 導出 SVG 時的數字字體調小

找到 `updateDlPreview` 函數處理數字 SVG 節點的部分:

**原代碼:**

```javascript
            textNode.setAttribute('stroke', 'none');
            textNode.setAttribute('fill', 'black');
            textNode.setAttribute('font-size', '24');
            textNode.setAttribute('font-family', 'ZT Nature');

```

**替換為:**

```javascript
            textNode.setAttribute('stroke', 'none');
            textNode.setAttribute('fill', 'black');
            textNode.setAttribute('font-size', '16');
            textNode.setAttribute('font-family', 'ZT Nature');

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

Placing Rules: Both players take turns placing pieces, with the first player required to play a blue piece on their first turn and the second player required to play a red piece on theirs, after which players alternate turns as normal; except for the very first move, every subsequent piece must be placed adjacent to an existing piece on the board, and no enclosed holes may be formed.
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

兩個3秒AI對局:

代码: 全选

0 2a1 1a2 2A1 2A3 1B0 4A5 4A3 4B3 4a3 5B6 4C3 4b3 a3b 2b1 6a5 B5A 1b2 C6B 3B4 D5C 2c1 4D3 c0b 5E4 4c3 E6D 0C1 D7C C2B 0D1 A1B 9C8 C10B 12B11 B2C
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

修復:
一,導出svg功能出錯

當改變了「導入導出的文本格式」之後,導出svg的功能就會出錯,不再有use棋子。查找原因並修復。

二,回退,前進功能出錯

原因跟上面相同,也是改變了「導入導出的文本格式」之後就無法再使用回退和前進功能。

請明確給出所要修改的地方和所要替換的代碼。

代码: 全选

修復:
一,導出svg功能出錯

當改變了「導入導出的文本格式」之後,導出svg的功能就會出錯,不再有use棋子。查找原因並修復。

二,回退,前進功能出錯

原因跟上面相同,也是改變了「導入導出的文本格式」之後就無法再使用回退和前進功能。

請明確給出所要修改的地方和所要替換的代碼。
附件
trirhombus2881.html.7z
(35.97 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

更改導入導出的記譜格式
在導入導出的文本格式中,首先把横坐標改為字母,0為a,-1為b,以此類推負數為小寫字母,1為A,2為B,以此類推正數為大寫字母。例如[(1,-2),(1,-1)]將改為[(A,-2),(A,-1)],[(-1,3),(0,3)]將改為[(b,3),(a,3)]。

在此基礎上,先手方第一手藍色棋子坐標為[(a,0),(A,0)],棋譜記作「0」。

如果有藍色棋子的横坐標為a和A,則如果其縱坐標為正數則為「A$」,負數則為「a$」,$代表無符號數字。比如[(a,6),(A,6)]記作「A6」,[(a,-2),(A,-2)]記作「a2」。

如果有藍色棋子不屬於以上情況,則記作「前字母$後字母」,$為無符號數字。當其縱坐標為正數時,前字母和後字母為字母順序,否則為倒序。比如[(d,-1),(c,-1)]記作「d1c」,[(B,7),(A,7)]記作「A7B」。

紅色或橙色棋子,記作「前$字母後$」,$代表無符號數字。如果其縱坐標存在正數,則前數字小於後數字,否則前數字大於後數字。比如[(B,8),(B,9)]記作「8B9」,[(c,-2),(c,-3)]記作「3c2」。

棋譜中的每一手棋都用空白隔開,則第一手為我方落子,第二手為對方,依次類推。

如「[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,2),(0,1)] [(1,-2),(1,-1)] [(0,-3),(-1,-3)]」將記為「0 2a1 1a2 2A1 b3a」。

這個記譜格式將只用於導入和導出,不要改變原先程式中的數據格式。

特別注意:不要影響到程式的原有功能,比如回退前進,導出svg功能,等等。

請明確給出所要修改的地方和所要替換的代碼。

代码: 全选

更改導入導出的記譜格式
在導入導出的文本格式中,首先把横坐標改為字母,0為a,-1為b,以此類推負數為小寫字母,1為A,2為B,以此類推正數為大寫字母。例如[(1,-2),(1,-1)]將改為[(A,-2),(A,-1)],[(-1,3),(0,3)]將改為[(b,3),(a,3)]。

在此基礎上,先手方第一手藍色棋子坐標為[(a,0),(A,0)],棋譜記作「0」。

如果有藍色棋子的横坐標為a和A,則如果其縱坐標為正數則為「A$」,負數則為「a$」,$代表無符號數字。比如[(a,6),(A,6)]記作「A6」,[(a,-2),(A,-2)]記作「a2」。

如果有藍色棋子不屬於以上情況,則記作「前字母$後字母」,$為無符號數字。當其縱坐標為正數時,前字母和後字母為字母順序,否則為倒序。比如[(d,-1),(c,-1)]記作「d1c」,[(B,7),(A,7)]記作「A7B」。

紅色或橙色棋子,記作「前$字母後$」,$代表無符號數字。如果其縱坐標存在正數,則前數字小於後數字,否則前數字大於後數字。比如[(B,8),(B,9)]記作「8B9」,[(c,-2),(c,-3)]記作「3c2」。

棋譜中的每一手棋都用空白隔開,則第一手為我方落子,第二手為對方,依次類推。

如「[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,2),(0,1)] [(1,-2),(1,-1)] [(0,-3),(-1,-3)]」將記為「0 2a1 1a2 2A1 b3a」。

這個記譜格式將只用於導入和導出,不要改變原先程式中的數據格式。

特別注意:不要影響到程式的原有功能,比如回退前進,導出svg功能,等等。

請明確給出所要修改的地方和所要替換的代碼。
附件
trirhombus2882.html.7z
(35.58 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6323
注册时间: 2022年 11月 18日 17:36
联系:

Re: 研究提升三菱棋的AI棋力

帖子 ejsoon »

gemini 3.1 pro

代码: 全选

這是一個非常有趣且能縮短棋譜長度的優化。為了將記譜格式按照您的規則轉換,我們需要在導出(`openExportModal`)時將內部原本的 `[(x,y),(x,y)]` 轉為新格式,並在導入(`submitImport`)時將新格式解析回內部的坐標格式,以確保程式內的所有功能(悔棋、SVG導出等)完全不受影響。

請在你的代碼中找到 `openExportModal()` 和 `submitImport()` 函數的區塊(大約在 HTML 檔案的最下方,與 `downloadBoardSVG` 等功能放在一起),並**將其替換**為以下包含坐標轉換函數的完整代碼:

```javascript
      // === 棋譜導出導入轉換函數 ===
      function xToCharFormat(x) {
        if (x === 0) return 'a';
        if (x < 0) return String.fromCharCode(97 - x);
        return String.fromCharCode(64 + x);
      }

      function charToXFormat(c) {
        if (c === 'a') return 0;
        let code = c.charCodeAt(0);
        if (code >= 98 && code <= 122) return 97 - code; // a-z
        if (code >= 65 && code <= 90) return code - 64;  // A-Z
        return 0;
      }

      function formatExportNotation(m) {
        let x1 = m.t1.idx, y1 = m.t1.N;
        let x2 = m.t2.idx, y2 = m.t2.N;
        // 確保 x1, y1 是較小的坐標
        if (x1 > x2 || (x1 === x2 && y1 > y2)) {
          let tmpX = x1, tmpY = y1; x1 = x2; y1 = y2; x2 = tmpX; y2 = tmpY;
        }

        // 先手方第一手 (0,0), (1,0)
        if (m.pid === 0 && x1 === 0 && x2 === 1 && y1 === 0 && y2 === 0) return "0";

        if (y1 === y2) { // 藍色棋子
          let c1 = xToCharFormat(x1), c2 = xToCharFormat(x2);
          if (x1 === 0 && x2 === 1) { // 坐標為 a 和 A
            if (y1 > 0) return "A" + y1;
            else return "a" + Math.abs(y1);
          } else {
            let chars = [c1, c2].sort((a, b) => {
              let A = a.toLowerCase(), B = b.toLowerCase();
              if (A === B) return a < b ? -1 : 1;
              return A < B ? -1 : 1;
            });
            if (y1 > 0) return chars[0] + y1 + chars[1]; // 字母順序
            else return chars[1] + Math.abs(y1) + chars[0]; // 倒序
          }
        } else { // 紅色或橙色棋子
          let charX = xToCharFormat(x1);
          let abs1 = Math.abs(y1), abs2 = Math.abs(y2);
          let hasPos = (y1 > 0 || y2 > 0);
          let smaller = Math.min(abs1, abs2), larger = Math.max(abs1, abs2);
          if (hasPos) return smaller + charX + larger; // 存在正數,前小後大
          else return larger + charX + smaller; // 否則,前大後小
        }
      }

      function parseImportNotation(token) {
        if (token === "0") return { t1_idx: 0, t1_N: 0, t2_idx: 1, t2_N: 0 };
        let match;
        if ((match = token.match(/^([Aa])(\d+)$/))) {
          let y = parseInt(match[2]);
          if (match[1] === 'A') return { t1_idx: 0, t1_N: y, t2_idx: 1, t2_N: y };
          else return { t1_idx: 0, t1_N: -y, t2_idx: 1, t2_N: -y };
        } else if ((match = token.match(/^([A-Za-z])(\d+)([A-Za-z])$/))) {
          let c1 = match[1], c2 = match[3];
          let yAbs = parseInt(match[2]);
          let chars = [c1, c2].sort((a, b) => {
            let A = a.toLowerCase(), B = b.toLowerCase();
            if (A === B) return a < b ? -1 : 1;
            return A < B ? -1 : 1;
          });
          let y = (c1 === chars[0]) ? yAbs : -yAbs;
          return { t1_idx: charToXFormat(c1), t1_N: y, t2_idx: charToXFormat(c2), t2_N: y };
        } else if ((match = token.match(/^(\d+)([A-Za-z])(\d+)$/))) {
          let num1 = parseInt(match[1]), num2 = parseInt(match[3]);
          let x = charToXFormat(match[2]);
          let y1, y2;
          if (num1 < num2) { y1 = num1; y2 = num2; }
          else { y1 = -num1; y2 = -num2; }
          return { t1_idx: x, t1_N: y1, t2_idx: x, t2_N: y2 };
        }
        throw new Error('格式錯誤');
      }

      // === 修改後的導出導入介面邏輯 ===
      function openExportModal() {
        let exportData = moveHistory.map(m => formatExportNotation(m)).join(' ');
        document.getElementById('export-text').value = exportData;
        document.getElementById('import-text').value = '';
        document.getElementById('export-modal').style.display = 'flex';
      }

      let toastTimeout;
      function showToast(msg) {
        const overlay = document.getElementById('toast-overlay');
        document.getElementById('toast-msg').innerText = msg;
        overlay.style.opacity = '1';
        clearTimeout(toastTimeout);
        toastTimeout = setTimeout(() => {
          overlay.style.opacity = '0';
        }, 1200);
      }

      function copyExport() {
        let input = document.getElementById('export-text');
        input.select();
        document.execCommand('copy');
        showToast(getText('copySuccess'));
      }

      function submitImport() {
        let txt = document.getElementById('import-text').value.trim();
        if (!txt) return;
        forceHumanPlayer(); // 導入棋譜時,強制切換雙方為人類,並中斷 AI
        let tokens = txt.split(/\s+/);
        try {
          let importedHistory = [];
          let halfWidth = 31.1769;
          let cPlayer = 1; // 第一局從 P1 開始

          for (let i = 0; i < tokens.length; i++) {
            // 使用新格式的解析器
            let parsed = parseImportNotation(tokens[i]);
            let t1_idx = parsed.t1_idx, t1_N = parsed.t1_N;
            let t2_idx = parsed.t2_idx, t2_N = parsed.t2_N;

            // 推導棋子種類 (0: 藍, 1: 紅, 2: 橙)
            let pid = 0;
            if (t1_N !== t2_N) {
              let isRight = gameLogic.isRight(t1_idx, t1_N);
              let dn = t2_N - t1_N;
              pid = isRight ? (dn === -1 ? 1 : 2) : dn === 1 ? 1 : 2;
            }

            let isRight1 = gameLogic.isRight(t1_idx, t1_N);
            let isRight2 = gameLogic.isRight(t2_idx, t2_N);
            let cx1 = isRight1 ? (t1_idx - 1) * halfWidth + 10.392 : (t1_idx - 1) * halfWidth + 20.784;

            let t1 = { idx: t1_idx, N: t1_N, isRight: isRight1, cx: cx1, cy: t1_N * 18 };
            let t2 = { idx: t2_idx, N: t2_N, isRight: isRight2 };
            let { px, py } = calcDropPos(t1, pid);

            // 如果遇到第 37 手 (index 36),代表進入第二局,首手為 P2
            if (i === 36) cPlayer = 2;

            // 還原為程式內部使用的原始 Notation 格式,確保相容所有 UI 與 SVG 導出功能
            let sortedC1, sortedC2;
            if (t1_idx < t2_idx || (t1_idx === t2_idx && t1_N < t2_N)) {
              sortedC1 = t1; sortedC2 = t2;
            } else {
              sortedC1 = t2; sortedC2 = t1;
            }
            let internalNotation = `[(${sortedC1.idx},${sortedC1.N}),(${sortedC2.idx},${sortedC2.N})]`;

            importedHistory.push({ notation: internalNotation, pid, t1, t2, px, py, player: cPlayer });
            cPlayer = cPlayer === 1 ? 2 : 1;
          }

          document.getElementById('export-modal').style.display = 'none';

          // 使用重繪函數直接渲染盤面
          moveHistory = importedHistory;
          historyIndex = moveHistory.length - 1;
          currentRound = historyIndex >= 36 ? 2 : 1;
          currentFirstPlayer = currentRound === 2 ? 2 : 1;
          redrawFromHistory();

          if (historyIndex >= 35) {
            let dummy = new HexGridGame();
            for (let j = 0; j <= 35; j++) {
              let m = moveHistory[j];
              let r = dummy.tryPlacePiece(m.pid + 1, { x: m.t1.idx, y: m.t1.N }, { x: m.t2.idx, y: m.t2.N }, true);
              if (j === 35) {
                p1Round1Score = r.scores.p1Score;
                p2Round1Score = r.scores.p2Score;
              }
            }
          }

          showToast(getText('importSuccess'));

          let totalPieces = piecesLeft[1].reduce((a, b) => a + b) + piecesLeft[2].reduce((a, b) => a + b);
          if (totalPieces === 0) {
            gameState = 'ended';
            setTimeout(showRoundEnd, 300);
          }
        } catch (e) {
          alert(getText('importFail'));
        }
      }

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

在线用户

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