修復和改進
一,計分邏輯出錯
現在形成頂鑫結構完全不會計分,查找原因並修復。
二,增加旋轉功能
在工具欄的最後,增加一個意為「旋轉」的svg按鈕。圖案為上下兩個相反的弧形箭頭。當點擊使之激活時,輪到後手方,則把雙方的控制區旋轉180度,工具欄也將旋轉180度後置於頂部,消息框也旋轉180度後置於底部。輪到先手方就再恢復原狀。
三,先手方首回合放置改進
當先手方第一手棋放置時,棋子的中心應對應棋盤的中心(坐標是0,0),且旋轉角度和翻轉狀態都應跟控制區中的棋子一致。
這時如果用手機觸屏或電腦鼠標拖動棋盤,不會平移,而是以棋盤中心為中心旋轉棋子,旋轉角度數值應為整數。如果縮放,應保持棋盤中心不變。
在旋轉的同時,消息框應顯示當前旋轉角度,不是增量,而是當前值。
當回退到棋盤上沒有棋子時,棋盤的平移和縮放將回到初始狀態,以使先手方第一手能把棋子放至棋盤中心。。
四,手機端改小工具欄按鈕
當手機端時,工具按鈕應變小。
五,增加下載功能
當點擊下載按鈕時,應能直接下載當前的棋盤svg,文件名為BraceTheTriGolden_yyyymmdd_hhiiss.svg。
下載按鈕在前面增加的「旋轉按鈕」的左邊。
回答要求:
請明確給出所要修改的地方和所要替換的代碼。
代码: 全选
修復和改進
一,計分邏輯出錯
現在形成頂鑫結構完全不會計分,查找原因並修復。
二,增加旋轉功能
在工具欄的最後,增加一個意為「旋轉」的svg按鈕。圖案為上下兩個相反的弧形箭頭。當點擊使之激活時,輪到後手方,則把雙方的控制區旋轉180度,工具欄也將旋轉180度後置於頂部,消息框也旋轉180度後置於底部。輪到先手方就再恢復原狀。
三,先手方首回合放置改進
當先手方第一手棋放置時,棋子的中心應對應棋盤的中心(坐標是0,0),且旋轉角度和翻轉狀態都應跟控制區中的棋子一致。
這時如果用手機觸屏或電腦鼠標拖動棋盤,不會平移,而是以棋盤中心為中心旋轉棋子,旋轉角度數值應為整數。如果縮放,應保持棋盤中心不變。
在旋轉的同時,消息框應顯示當前旋轉角度,不是增量,而是當前值。
當回退到棋盤上沒有棋子時,棋盤的平移和縮放將回到初始狀態,以使先手方第一手能把棋子放至棋盤中心。。
四,手機端改小工具欄按鈕
當手機端時,工具按鈕應變小。
五,增加下載功能
當點擊下載按鈕時,應能直接下載當前的棋盤svg,文件名為BraceTheTriGolden_yyyymmdd_hhiiss.svg。
下載按鈕在前面增加的「旋轉按鈕」的左邊。
回答要求:
請明確給出所要修改的地方和所要替換的代碼。
gemini 3.1 pro + thinking
代码: 全选
這些修改能大幅提升《頂鑫棋》的遊戲體驗與邏輯嚴謹度。以下為針對你提出的五點需求,具體的代碼修改方案:
### 一、修復頂鑫結構無法計分的問題
問題出在原代碼中 `getGraphPath` 只檢查單向的父子關係圖(`edgeLinks`),導致剛落下的暫存棋子無法正確與其他棋子連通。我們需要將鄰接圖改為雙向查找。
找到 `function getGraphPath(start, end, allP)` 函數,替換為以下代碼:
```javascript
function getGraphPath(start, end, allP) {
let q = [{ node: start, dist: 0, path: [start] }];
let visited = new Set([start.id]);
while (q.length > 0) {
let curr = q.shift();
if (curr.node.id === end.id) return curr.path;
// 建立雙向查找,包含自己指向別人的,以及別人指向自己的
let neighbors = [];
if (curr.node.edgeLinks) {
Object.values(curr.node.edgeLinks).forEach(id => neighbors.push(id));
}
allP.forEach(p => {
if (p.edgeLinks && Object.values(p.edgeLinks).includes(curr.node.id)) {
neighbors.push(p.id);
}
});
for (let nId of neighbors) {
if (!visited.has(nId)) {
visited.add(nId);
let nNode = allP.find(p => p.id === nId);
if (nNode) {
q.push({ node: nNode, dist: curr.dist + 1, path: [...curr.path, nNode] });
}
}
}
}
return null;
}
```
### 二、增加旋轉與下載功能,並縮小手機端工具按鈕
我們需要在工具欄加入這兩個按鈕,並加入對應的 CSS 樣式及邏輯。
**1. 修改 CSS 樣式**
在 `<style>` 中尋找 `@media (max-width: 768px)`,在裡面加入按鈕縮小的樣式;同時在外面加入控制旋轉的樣式:
```css
/* 新增的旋轉視角樣式 */
body.rotated-view .panel {
transform: rotate(180deg);
}
body.rotated-view .board-container {
transform: rotate(180deg);
}
body.rotated-view #toolbar {
top: 10px;
bottom: auto;
transform: translateX(-50%) rotate(180deg);
}
body.rotated-view #message-box {
top: auto;
bottom: 20px;
transform: translateX(-50%) rotate(180deg);
}
/* Mobile Layout 區塊更新 */
@media (max-width: 768px) {
/* ...原有的 mobile 樣式保留,新增以下: */
.tool-btn {
width: 24px;
height: 24px;
}
#toolbar {
gap: 10px;
padding: 6px 10px;
}
}
```
**2. 在工具欄新增按鈕**
找到 `<div id="toolbar">`,在裡面現有的 `<svg>` 按鈕**最右側**(取代原先的空白按鈕),加入下載與旋轉按鈕:
```html
<div id="toolbar">
<svg class="tool-btn" id="btn-download" viewBox="0 0 24 24" onclick="downloadSVG()">
<path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
</svg>
<svg class="tool-btn" id="btn-rotate" viewBox="0 0 24 24" onclick="toggleAutoRotate()">
<path fill="currentColor" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>
</svg>
</div>
```
**3. 添加下載與旋轉的 JavaScript 邏輯**
在 `<script>` 的頂部(全域變數區)加入:
```javascript
let autoRotate = false;
```
在指令碼末端(`function updateTransform()` 附近)加入這三個函數:
```javascript
function toggleAutoRotate() {
autoRotate = !autoRotate;
document.getElementById('btn-rotate').style.fill = autoRotate ? '#7289da' : '#ccc';
applyRotation();
}
function applyRotation() {
if (autoRotate && currentPlayer === 2) {
document.body.classList.add('rotated-view');
} else {
document.body.classList.remove('rotated-view');
}
}
function downloadSVG() {
const svg = document.getElementById('etanitrigolden').cloneNode(true);
const serializer = new XMLSerializer();
let source = serializer.serializeToString(svg);
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
const url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
const a = document.createElement('a');
a.href = url;
const now = new Date();
const pad = n => (n < 10 ? '0'+n : n);
const filename = `BraceTheTriGolden_${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}.svg`;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
```
*注意:你還需要在 `actionCheck`、`undo`、`redo`、`resetGame` 函數改變 `currentPlayer` 後(約函數末尾處),呼叫 `applyRotation();` 以自動觸發視角旋轉。*
### 三、先手方首回合放置中心化與拖動旋轉改進
為了在首回合完美對齊中心,並且支持自定義整數旋轉,我們需要修改數學轉換與鼠標控制邏輯。
**1. 修改 `showMessage` 使其支援直接顯示字串**
找到 `showMessage` 函數並替換:
```javascript
function showMessage(key, duration = 2400) {
let box = document.getElementById('message-box');
box.innerText = i18n[key] ? i18n[key][currentLang] : key;
box.style.display = 'block';
clearTimeout(msgTimeout);
if (duration > 0) {
msgTimeout = setTimeout(() => {
box.style.display = 'none';
}, duration);
}
}
```
**2. 加入全域旋轉變數**
在全域變數宣告區加入:
```javascript
let firstPieceCenter = null;
let globalRotation = 0;
```
**3. 更新 `exactToScreen` 函數**
替換現有的 `exactToScreen`,讓首步棋的中心絕對歸零,並套用全局旋轉:
```javascript
function exactToScreen(p) {
const PI5 = Math.PI / 5;
let [a, b, c, d] = p;
if (firstPieceCenter) {
a -= firstPieceCenter[0];
b -= firstPieceCenter[1];
c -= firstPieceCenter[2];
d -= firstPieceCenter[3];
}
const x = a + b * Math.cos(PI5) + c * Math.cos(2 * PI5) + d * Math.cos(3 * PI5);
const y = b * Math.sin(PI5) + c * Math.sin(2 * PI5) + d * Math.sin(3 * PI5);
let sx = 36 * x;
let sy = -36 * y;
if (globalRotation !== 0) {
let rad = globalRotation * Math.PI / 180;
let cos = Math.cos(rad);
let sin = Math.sin(rad);
return { x: sx * cos - sy * sin, y: sx * sin + sy * cos };
}
return { x: sx, y: sy };
}
```
**4. 攔截首步棋的選取中心化**
在 `selectTile(player, tileId)` 函數中,找到 `if (pieces.length === 0)` 的區塊,修改 `tempPieces` 的賦值並記錄中心與視角:
```javascript
if (pieces.length === 0) {
// ... 前方保留 ...
tempPieces = [
{
id: 'p_' + Date.now(),
owner: currentPlayer,
type: SHAPE_MAP[tileId],
svgId: tileId,
isFlipped: isFlipped[tileId],
vertices: vertices
}
];
// 紀錄準確幾何中心
firstPieceCenter = [
vertices.reduce((s, v) => s + v[0], 0) / 4,
vertices.reduce((s, v) => s + v[1], 0) / 4,
vertices.reduce((s, v) => s + v[2], 0) / 4,
vertices.reduce((s, v) => s + v[3], 0) / 4
];
globalRotation = 0;
boardTransform = { tx: 240, ty: 240, scale: 1 };
updateTransform();
piecesCount[tileId]--;
}
```
*提示:在 `actionCross` 和 `resetGame` 當 `pieces.length === 0 && tempPieces.length === 0` 時,記得加上 `firstPieceCenter = null; globalRotation = 0;` 來清除狀態。*
**5. 替換鼠標/觸摸控制邏輯 (Pan, Zoom, 旋轉)**
將 `// Pan & Zoom` 底下的所有 EventListener 替換為以下代碼,這完美結合了縮放置中和第一步旋轉的規則:
```javascript
// Pan, Zoom & First Piece Rotation
let isDragging = false;
let startX, startY;
let startAngle = 0;
let initialGlobalRot = 0;
boardSvg.addEventListener('pointerdown', e => {
if (pieces.length === 0 && tempPieces.length === 0) return;
isDragging = true;
if (pieces.length === 0 && tempPieces.length === 1) {
let rect = boardSvg.getBoundingClientRect();
let cx = rect.left + rect.width / 2;
let cy = rect.top + rect.height / 2;
startAngle = Math.atan2(e.clientY - cy, e.clientX - cx) * 180 / Math.PI;
initialGlobalRot = globalRotation;
} else {
startX = e.clientX - boardTransform.tx;
startY = e.clientY - boardTransform.ty;
}
});
boardSvg.addEventListener('pointermove', e => {
if (!isDragging) return;
if (pieces.length === 0 && tempPieces.length === 1) {
let rect = boardSvg.getBoundingClientRect();
let cx = rect.left + rect.width / 2;
let cy = rect.top + rect.height / 2;
let currentAngle = Math.atan2(e.clientY - cy, e.clientX - cx) * 180 / Math.PI;
globalRotation = Math.round(initialGlobalRot + (currentAngle - startAngle));
globalRotation = (globalRotation % 360 + 360) % 360; // 確保為 0-359 整數
let rotText = currentLang === 'zh' ? `當前旋轉角度: ${globalRotation}°` : `Current Angle: ${globalRotation}°`;
showMessage(rotText, 0); // 持續顯示
renderBoard();
} else {
boardTransform.tx = e.clientX - startX;
boardTransform.ty = e.clientY - startY;
updateTransform();
}
});
const stopDrag = () => {
isDragging = false;
if (pieces.length === 0 && tempPieces.length === 1) {
document.getElementById('message-box').style.display = 'none';
}
};
boardSvg.addEventListener('pointerup', stopDrag);
boardSvg.addEventListener('pointerleave', stopDrag);
boardSvg.addEventListener('wheel', e => {
e.preventDefault();
if (pieces.length === 0 && tempPieces.length === 0) return;
const ds = e.deltaY > 0 ? 0.9 : 1.1;
const nextScale = boardTransform.scale * ds;
if (nextScale < 0.2 || nextScale > 5) return;
const rect = boardSvg.getBoundingClientRect();
// 如果是第一步,以屏幕中心為縮放點;否則依鼠標位置縮放
const mx = (pieces.length === 0 && tempPieces.length === 1) ? (rect.width / 2) : (e.clientX - rect.left);
const my = (pieces.length === 0 && tempPieces.length === 1) ? (rect.height / 2) : (e.clientY - rect.top);
boardTransform.tx = mx - (mx - boardTransform.tx) * ds;
boardTransform.ty = my - (my - boardTransform.ty) * ds;
boardTransform.scale = nextScale;
updateTransform();
});
let pinchStartDist = 0;
boardSvg.addEventListener('touchstart', e => {
if (e.touches.length === 2 && (pieces.length > 0 || tempPieces.length > 0)) {
isDragging = false;
pinchStartDist = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
}
});
boardSvg.addEventListener('touchmove', e => {
if (e.touches.length === 2 && (pieces.length > 0 || tempPieces.length > 0)) {
e.preventDefault();
const dist = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
const ds = dist / pinchStartDist;
const nextScale = boardTransform.scale * ds;
if (nextScale > 0.2 && nextScale < 5) {
const rect = boardSvg.getBoundingClientRect();
const mx = (pieces.length === 0 && tempPieces.length === 1) ? (rect.width / 2) : ((e.touches[0].clientX + e.touches[1].clientX) / 2 - rect.left);
const my = (pieces.length === 0 && tempPieces.length === 1) ? (rect.height / 2) : ((e.touches[0].clientY + e.touches[1].clientY) / 2 - rect.top);
boardTransform.tx = mx - (mx - boardTransform.tx) * ds;
boardTransform.ty = my - (my - boardTransform.ty) * ds;
boardTransform.scale = nextScale;
updateTransform();
}
pinchStartDist = dist;
}
}, { passive: false });
```