「益智遊戲」和「抽象弈棋」
头像
ejsoon
一枝独秀
帖子: 6412
注册时间: 2022年 11月 18日 17:36
联系:

Re: 一個新的遊戲創意

帖子 ejsoon »

當後手方點擊一個己方棋子,再點擊一個場上的對方棋子時,顯示預放棋子,相關的代碼在哪裡?

代码: 全选

在提供的源代碼中,當玩家(包括後手方)選擇一個自己面板上的棋子後,再點擊場上對方的棋子以顯示預放棋子(ghost pieces),主要涉及以下三個核心函數與邏輯區塊:

### 1. 綁定點擊事件的邏輯 (`renderBoard`)

在 `renderBoard()` 函數中,當遍歷並渲染棋盤上的棋子時,若該棋子屬於**對方**(`p.owner !== currentPlayer`),程式會動態移除 `placed-piece`,賦予 `board-target` 類別,並綁定點擊事件來觸發 `handleTargetClick(p.id)`:

```javascript
if (p.owner !== currentPlayer && tempPieces.length < ((turnNumber===1||pieces.length===35)?1:2)) {
    use.classList.remove('placed-piece');
    use.classList.add('board-target');
    use.onclick = (e) => { e.stopPropagation(); handleTargetClick(p.id); };
}

```

---

### 2. 處理目標點擊的邏輯 (`handleTargetClick`)

當點擊對方的棋子後,會執行 `handleTargetClick(pieceId)` 函數。此函數會確認是否已先選定模板棋子 (`selectedTile`)、驗證點擊目標是否為對手棋子,並在通過條件後設定 `targetOpponentPieceId`,接著調用 `generateGhosts()`:

```javascript
function handleTargetClick(pieceId) {
    if (!selectedTile) return;
    let p = pieces.find(x => x.id === pieceId);
    if (!p || p.owner === currentPlayer) return;

    if (tempPieces.length === 1 && turnNumber > 1) {
        let firstScored = checkTriGoldenScoring(tempPieces[0], true);
        if (pieceId !== tempPieces[0].targetId && !firstScored) {
            targetOpponentPieceId = pieceId; 
        } else {
            targetOpponentPieceId = pieceId;
        }
    } else {
        targetOpponentPieceId = pieceId;
    }
    
    generateGhosts(); // 生成預放棋子
    updateUI();
    renderBoard();
}

```

---

### 3. 計算並生成預放棋子的邏輯 (`generateGhosts`)

此函數負責實際計算預放棋子(Ghosts)的幾何位置。它會讀取目標棋子的邊長,透過 `attachByEdge` 嘗試將所選形狀貼合在目標棋子的邊緣上,並進行重疊與貼合次數的合法性檢查,最後將結果存入 `ghosts` 陣列中以便在畫面上渲染半透明預放塊:

```javascript
function generateGhosts() {
    ghosts = [];
    if (!selectedTile || !targetOpponentPieceId) return;
    
    const tPiece = pieces.find(p => p.id === targetOpponentPieceId);
    const selType = SHAPE_MAP[selectedTile];
    const selMir = isFlipped[selectedTile];
    
    // 透過迴圈計算邊緣貼合與碰撞檢測...
    // 滿足條件的候選位置會被push進 ghosts 陣列中
}

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

Re: 一個新的遊戲創意

帖子 ejsoon »

图片
附件
trigolden_game22.html.7z
(8.57 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6412
注册时间: 2022年 11月 18日 17:36
联系:

Re: 一個新的遊戲創意

帖子 ejsoon »

修復與改進:
一,判斷棋子翻轉狀態出錯

控制區棋子的翻轉狀態跟它在棋盤上的正好相反,請修復。

二,判斷棋盤上的棋子是否有面重疊出錯

如圖所示,兩個棋子的面相互重疊。查找原因並修復。

三,預放棋子加上透明度

將預放棋子的fill-opacity設為0.36。

四,檢查是否實現落子規則

以下是遊戲規則,檢查是否已經實現。

先手方開始行動,之後雙方輪流行動。

每方每種棋子有6個,則每方一共有18個棋子。除了先手方的第一回合及最後一回合只放一個棋子之外,每方每回合要放兩個棋子。

先手方首回合將把他的一個棋子放至棋盤中心,此後每一方的回合中,他所放的每一個棋子都要滿足以下條件:

1,每個棋子落下時,它的其中一條邊要完全貼合另一個在場上已有的「對方棋子」的一條邊。

2,不能有另一條邊跟場上已有的棋子貼合,包括部分重合但不完全貼合的情況。

3,不能跟場上已有的棋子的面重疊。

4,這一回合所放的兩個棋子都一定要貼合到同一個「對方棋子」的兩個不同的邊上,除非符合以下兩點。

5,如果第一手棋在落下之後,形成了「有棋子的端點正好落在另一個棋子的邊上」的情況,則第二手棋可以落在任意一個「對方棋子」的邊上。

6,如果第二手棋在落下之後,可以形成「有棋子的端點正好落在另一個棋子的邊上」的情況,則允許這手棋不跟第一手棋落在同一個「對方棋子」的邊上。

五,回答要求

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

代码: 全选

修復與改進:
一,判斷棋子翻轉狀態出錯

控制區棋子的翻轉狀態跟它在棋盤上的正好相反,請修復。

二,判斷棋盤上的棋子是否有面重疊出錯

如圖所示,兩個棋子的面相互重疊。查找原因並修復。

三,預放棋子加上透明度

將預放棋子的fill-opacity設為0.36。

四,檢查是否實現落子規則

以下是遊戲規則,檢查是否已經實現。

先手方開始行動,之後雙方輪流行動。

每方每種棋子有6個,則每方一共有18個棋子。除了先手方的第一回合及最後一回合只放一個棋子之外,每方每回合要放兩個棋子。

先手方首回合將把他的一個棋子放至棋盤中心,此後每一方的回合中,他所放的每一個棋子都要滿足以下條件:

1,每個棋子落下時,它的其中一條邊要完全貼合另一個在場上已有的「對方棋子」的一條邊。

2,不能有另一條邊跟場上已有的棋子貼合,包括部分重合但不完全貼合的情況。

3,不能跟場上已有的棋子的面重疊。

4,這一回合所放的兩個棋子都一定要貼合到同一個「對方棋子」的兩個不同的邊上,除非符合以下兩點。

5,如果第一手棋在落下之後,形成了「有棋子的端點正好落在另一個棋子的邊上」的情況,則第二手棋可以落在任意一個「對方棋子」的邊上。

6,如果第二手棋在落下之後,可以形成「有棋子的端點正好落在另一個棋子的邊上」的情況,則允許這手棋不跟第一手棋落在同一個「對方棋子」的邊上。

五,回答要求

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

Re: 一個新的遊戲創意

帖子 ejsoon »

gemini 3.1 pro + thinking

代码: 全选

這裡為你整理了所有需要修復與改進的地方。針對你的需求,我已經找到了代碼中的問題並提供了精確的替換代碼。

關於第四點的**遊戲規則**:經過檢查,**你目前的代碼已經完美實現了這所有的規則(包含 1 到 6 的所有細節與特例)**!

* **首尾回合只放一顆 / 其餘放兩顆**:由 `let requiredPieces = turnNumber === 1 || pieces.length === 35 ? 1 : 2;` 精準控制。
* **必須貼合且僅貼合一條邊**:由 `edgeOverlapCount !== 1 || perfectMatchCount !== 1` 進行了嚴格限制。
* **連擊(得分)放寬目標規則**:代碼中的 `restrictToScoring` 邏輯完美實現了「第一手得分,第二手可任意放」以及「第二手若能得分,可不與第一手放同一個敵方棋子」的例外規則(規則 4, 5, 6)。

因此,規則部分**不需要任何修改**。以下是針對前三個問題的具體修復代碼:

---

### 一、修復控制區棋子翻轉狀態相反的問題

**原因:**
由於 SVG 在渲染棋盤時,Y 軸被做了鏡像反轉(在 `exactToScreen` 函數中 `y = -36 * y`)。因此代碼在面板上判定 `scale(-1,1)` 的邏輯與實際數學坐標系剛好顛倒了。

**修改位置:**
找到 `updateUI` 函數裡面的這一行(大約在第 338 行附近):

**替換前的代碼:**

```javascript
boxSvgUse.setAttribute('transform', isFlipped[t] ? 'scale(-1,1)' : 'scale(1,1)');

```

**替換後的代碼:**

```javascript
boxSvgUse.setAttribute('transform', isFlipped[t] ? 'scale(1,1)' : 'scale(-1,1)');

```

---

### 二、修復面重疊(交集)判定出錯的問題

**原因:**
目前的 `shapesOverlap` 使用的是「線段嚴格交叉」和「點嚴格在多邊形內」來判斷。如你提供的截圖所示,如果兩個棋子剛好**共用了一個頂點,且邊互相穿過**,不會觸發「嚴格」交叉,也沒有點在內部,導致系統誤判為沒有重疊。
**解法:**
引入「分離軸定理 (Separating Axis Theorem, SAT)」,利用現有的精確數學函數 `exactOrientation`,只要兩個凸多邊形之間找不到任何一條分離軸,就代表它們的「面」一定發生了重疊。

**修改位置:**
找到原來的 `shapesOverlap` 函數(大約在第 272 行附近),將整個函數替換。

**替換前的代碼:**

```javascript
      function shapesOverlap(shapeA, shapeB) {
        const vA = shapeA.vertices,
          vB = shapeB.vertices;
        for (let i = 0; i < vA.length; i++)
          for (let j = 0; j < vB.length; j++)
            if (segmentsCrossStrict(vA[i], vA[(i + 1) % vA.length], vB[j], vB[(j + 1) % vB.length])) return true;
        if (pointStrictlyInConvexPolygon(vA[0], vB) || pointStrictlyInConvexPolygon(vB[0], vA)) return true;
        return false;
      }

```

**替換後的代碼:**

```javascript
      function shapesOverlap(shapeA, shapeB) {
        const vA = shapeA.vertices, vB = shapeB.vertices;

        function getInteriorSign(poly, i) {
          const S = poly[i], E = poly[(i + 1) % poly.length];
          for (let k = 0; k < poly.length; k++) {
            const o = exactOrientation(S, E, poly[k]);
            if (o !== 0) return o;
          }
          return 0;
        }

        function hasSeparatingAxis(poly1, poly2) {
          for (let i = 0; i < poly1.length; i++) {
            const S = poly1[i], E = poly1[(i + 1) % poly1.length];
            const sign1 = getInteriorSign(poly1, i);
            if (sign1 === 0) continue;

            let separated = true;
            for (let j = 0; j < poly2.length; j++) {
              const o = exactOrientation(S, E, poly2[j]);
              // 如果 poly2 的某個頂點在 poly1 該邊的嚴格內側,這條邊就不是分離軸
              if (o === sign1) {
                separated = false;
                break;
              }
            }
            if (separated) return true; // 找到分離軸
          }
          return false;
        }

        // 如果互相都找不到分離軸,代表面發生了重疊
        return !(hasSeparatingAxis(vA, vB) || hasSeparatingAxis(vB, vA));
      }

```

---

### 三、預放棋子加上透明度

**原因:**
只需要在渲染棋盤的 `renderBoard` 函數中,判斷正在生成的棋子是否屬於 `tempPieces`(預放棋子數組),如果是,就加上透明度屬性。

**修改位置:**
找到 `renderBoard` 函數內部的生成迴圈(大約在第 360 行附近):

**替換前的代碼:**

```javascript
        pieces.concat(tempPieces).forEach(p => {
          const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
          use.setAttribute('href', '#' + p.svgId);
          use.setAttribute('transform', getTransformString(p.svgId, p.vertices));
          use.classList.add('placed-piece');
          if (p.owner !== currentPlayer && tempPieces.length < (turnNumber === 1 || pieces.length === 35 ? 1 : 2)) {
            use.classList.remove('placed-piece');

```

**替換後的代碼:**

```javascript
        pieces.concat(tempPieces).forEach(p => {
          const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
          use.setAttribute('href', '#' + p.svgId);
          use.setAttribute('transform', getTransformString(p.svgId, p.vertices));
          use.classList.add('placed-piece');
          
          // 【新增】如果是預放的棋子(存在於 tempPieces 中),則加上透明度
          if (tempPieces.some(tp => tp.id === p.id)) {
            use.setAttribute('fill-opacity', '0.36');
          }

          if (p.owner !== currentPlayer && tempPieces.length < (turnNumber === 1 || pieces.length === 35 ? 1 : 2)) {
            use.classList.remove('placed-piece');

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

Re: 一個新的遊戲創意

帖子 ejsoon »

修復和改進:
一,落子時的問題

當一方要落子時,它的預放棋子應該是半透明的,fill-opacity=0.36,但是現在並沒有(圖一)。

當棋子翻轉時,沒有顯示出符合規則的預放棋子(圖二),說明在「檢查棋子邊與邊是否貼合」等邏輯上仍存在問題。猜測是因為反轉之後,端點的順時針或逆時針沒有調換,導致貼合判定的手順方向發生錯誤。

檢查問題並修復。

二,規則更新

以下是舊的遊戲規則:

先手方開始行動,之後雙方輪流行動。

每方每種棋子有6個,則每方一共有18個棋子。除了先手方的第一回合及最後一回合只放一個棋子之外,每方每回合要放兩個棋子。

先手方首回合將把他的一個棋子放至棋盤中心,此後每一方的回合中,他所放的每一個棋子都要滿足以下條件:

1,每個棋子落下時,它的其中一條邊要完全貼合另一個在場上已有的「對方棋子」的一條邊。

2,不能有另一條邊跟場上已有的棋子貼合,包括部分重合但不完全貼合的情況。

3,不能跟場上已有的棋子的面重疊。

4,這一回合所放的兩個棋子都一定要貼合到同一個「對方棋子」的兩個不同的邊上,除非符合以下兩點。

5,如果第一手棋在落下之後,形成了「有棋子的端點正好落在另一個棋子的邊上」的情況,則第二手棋可以落在任意一個「對方棋子」的邊上。

6,如果第二手棋在落下之後,可以形成「有棋子的端點正好落在另一個棋子的邊上」的情況,則允許這手棋不跟第一手棋落在同一個「對方棋子」的邊上。

新的規則將改動第5點和第6點:

5.我們把「一個棋子的端點正好落在另一個棋子的邊上」稱作「頂鑫結構」。當第一手棋在落下之後,它跟一個場上已有棋子形成了頂鑫結構,則第二手棋可以落在任意一個「對方棋子」的邊上。

6.當第二手棋落在另一個「對方棋子」的邊上之後,跟第一手棋形成了「頂鑫結構」,則允許其落下。

三,回答要求

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

代码: 全选

修復和改進:
一,落子時的問題

當一方要落子時,它的預放棋子應該是半透明的,fill-opacity=0.36,但是現在並沒有(圖一)。

當棋子翻轉時,沒有顯示出符合規則的預放棋子(圖二),說明在「檢查棋子邊與邊是否貼合」等邏輯上仍存在問題。猜測是因為反轉之後,端點的順時針或逆時針沒有調換,導致貼合判定的手順方向發生錯誤。

檢查問題並修復。

二,規則更新

以下是舊的遊戲規則:

先手方開始行動,之後雙方輪流行動。

每方每種棋子有6個,則每方一共有18個棋子。除了先手方的第一回合及最後一回合只放一個棋子之外,每方每回合要放兩個棋子。

先手方首回合將把他的一個棋子放至棋盤中心,此後每一方的回合中,他所放的每一個棋子都要滿足以下條件:

1,每個棋子落下時,它的其中一條邊要完全貼合另一個在場上已有的「對方棋子」的一條邊。

2,不能有另一條邊跟場上已有的棋子貼合,包括部分重合但不完全貼合的情況。

3,不能跟場上已有的棋子的面重疊。

4,這一回合所放的兩個棋子都一定要貼合到同一個「對方棋子」的兩個不同的邊上,除非符合以下兩點。

5,如果第一手棋在落下之後,形成了「有棋子的端點正好落在另一個棋子的邊上」的情況,則第二手棋可以落在任意一個「對方棋子」的邊上。

6,如果第二手棋在落下之後,可以形成「有棋子的端點正好落在另一個棋子的邊上」的情況,則允許這手棋不跟第一手棋落在同一個「對方棋子」的邊上。

新的規則將改動第5點和第6點:

5.我們把「一個棋子的端點正好落在另一個棋子的邊上」稱作「頂鑫結構」。當第一手棋在落下之後,它跟一個場上已有棋子形成了頂鑫結構,則第二手棋可以落在任意一個「對方棋子」的邊上。

6.當第二手棋落在另一個「對方棋子」的邊上之後,跟第一手棋形成了「頂鑫結構」,則允許其落下。

三,回答要求

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

Re: 一個新的遊戲創意

帖子 ejsoon »

gemini 3.1 pro + thiking

代码: 全选

這三個問題分別涉及到 SVG 的屬性層級、幾何翻轉時的向量對齊,以及新遊戲規則的邏輯重構。

以下是針對每個問題的詳細修復說明與需要替換的程式碼:

### 一、修復預放棋子的半透明顯示問題

在 SVG 中,`<use>` 標籤使用 `fill-opacity` 時,如果被引用的 `<g>` 群組內本身已經定義了 `fill`,部分瀏覽器會無法正確覆蓋透明度。改用整體的 `opacity` 屬性即可解決。

**修改位置:`renderBoard()` 函數**

尋找以下代碼:

```javascript
          if (tempPieces.some(tp => tp.id === p.id)) {
            use.setAttribute('fill-opacity', '0.36');
          }

```

**替換為:**

```javascript
          if (tempPieces.some(tp => tp.id === p.id)) {
            use.setAttribute('opacity', '0.36');
          }

```

---

### 二、修復棋子翻轉後無法貼合的問題

你的猜測完全正確。在原本的幾何系統中,棋子默認為逆時針(CCW)。當棋子被翻轉(`mirrored = true`)時,頂點的生成會變成順時針(CW)。
原代碼在計算貼合向量時,一律將本地向量對齊到目標邊的 `Q -> P` 向量(逆時針能保證內部在正確的一側)。但對於順時針生成的棋子,繼續對齊 `Q -> P` 會導致棋子的面與目標棋子重疊,從而被 `shapesOverlap` 攔截。我們需要根據是否翻轉,動態調整對齊的向量與錨點。

**修改位置:`attachByEdge()` 函數**

將整段 `attachByEdge` 函數替換為以下代碼:

```javascript
      function attachByEdge(templateKey, mirrored, edgeIndexToAlign, worldP, worldQ) {
        const template = SHAPES[templateKey];
        const { vertices: localVerts } = buildShapeVertices(ZERO, 0, template, mirrored);
        const i = edgeIndexToAlign,
          j = (i + 1) % localVerts.length;
        const localEdgeVec = ringSub(localVerts[j], localVerts[i]);
        
        // 修復:如果鏡像翻轉(順時針),需對齊到 P->Q 向量,以保證新棋子在外部不重疊;否則對齊到 Q->P
        const targetVec = mirrored ? ringSub(worldQ, worldP) : ringSub(worldP, worldQ);
        
        let foundK = null;
        for (let k = 0; k < 10; k++) {
          if (pointsEqual(ringMul(localEdgeVec, zetaPow(k)), targetVec)) {
            foundK = k;
            break;
          }
        }
        if (foundK === null) return null;
        
        // 修復:根據旋轉方向,決定錨點是 worldQ 還是 worldP
        const anchor = mirrored ? worldP : worldQ;
        const worldVerts = localVerts.map(v => ringAdd(anchor, ringMul(ringSub(v, localVerts[i]), zetaPow(foundK))));
        
        return { vertices: worldVerts, rotation: foundK, mirrored };
      }

```

---

### 三、更新第5與第6點規則(頂鑫結構)

新規則將判定條件收束為「第一手棋是否與**場上已有棋子**形成頂鑫結構」以及「第二手棋是否與**第一手棋**形成頂鑫結構」。為此,我們需要新增一個專屬的判定函數,並簡化原本複雜的點擊與生成邏輯。

#### 1. 替換判分函數

將原來的 `checkTriGoldenScoring` 函數整段刪除,並在同一個位置(`actionCheck` 之後,`getGraphDistance` 之前)加入新的 `formsTriGolden` 函數:

```javascript
      function formsTriGolden(A, B) {
        // 檢查 A 的端點是否落在 B 的邊上
        for (let v of A.vertices) {
          for (let e = 0; e < 4; e++) {
            if (pointOnOpenSegment(v, B.vertices[e], B.vertices[(e + 1) % 4])) return true;
          }
        }
        // 檢查 B 的端點是否落在 A 的邊上
        for (let v of B.vertices) {
          for (let e = 0; e < 4; e++) {
            if (pointOnOpenSegment(v, A.vertices[e], A.vertices[(e + 1) % 4])) return true;
          }
        }
        return false;
      }

```

#### 2. 簡化點擊處理

**修改位置:`handleTargetClick()` 函數**
原本此函數夾雜了規則判斷,現在全權交給 `generateGhosts` 處理即可。將其替換為:

```javascript
      function handleTargetClick(pieceId) {
        if (!selectedTile) return;
        let p = pieces.find(x => x.id === pieceId);
        if (!p || p.owner === currentPlayer) return;

        targetOpponentPieceId = pieceId;

        generateGhosts();
        updateUI();
        renderBoard();
      }

```

#### 3. 實裝新規則邏輯

**修改位置:`generateGhosts()` 函數**

尋找以下這段代碼:

```javascript
        let req2ndSame = false;
        let restrictToScoring = false;
        if (tempPieces.length === 1) {
          let firstScored = checkTriGoldenScoring(tempPieces[0], true);
          if (!firstScored && targetOpponentPieceId !== tempPieces[0].targetId) {
            restrictToScoring = true;
          }
        }

```

**替換為:**

```javascript
        let restrictToScoringWithFirst = false;
        let firstPiece = null;
        if (tempPieces.length === 1 && turnNumber > 1) {
          firstPiece = tempPieces[0];
          // 規則5:檢查第一手棋是否跟一個「場上已有棋子」形成了頂鑫結構
          let firstScored = pieces.some(p => formsTriGolden(firstPiece, p));
          
          // 規則6:如果第一手沒形成,且第二手要落在不同的對方棋子上,則必須與第一手棋形成頂鑫結構
          if (!firstScored && targetOpponentPieceId !== firstPiece.targetId) {
            restrictToScoringWithFirst = true;
          }
        }

```

接著,在同一個 `generateGhosts()` 函數的下方,尋找這行用來排除不合法落子的代碼:

```javascript
              if (restrictToScoring && !checkTriGoldenScoring(gp, true)) continue;

```

**替換為:**

```javascript
              if (restrictToScoringWithFirst && !formsTriGolden(gp, firstPiece)) continue;

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

在线用户

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