Re: 準備為三菱棋做遊戲和MCTS搜尋
跟「困難」等級打了個平手,非常難得!
代码: 全选
[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,1),(0,2)] [(1,-2),(1,-1)] [(-1,-3),(0,-3)] [(-1,3),(0,3)] [(-1,0),(-1,1)] [(-2,0),(-2,1)] [(1,1),(1,2)] [(-2,2),(-1,2)] [(1,-3),(2,-3)] [(2,-1),(2,0)] [(3,-1),(3,0)] [(1,3),(2,3)] [(2,-2),(3,-2)] [(-3,1),(-3,2)] [(-2,3),(-2,4)] [(-3,5),(-2,5)] [(0,4),(0,5)] [(3,1),(4,1)] [(2,4),(2,5)] [(4,-1),(4,0)] [(-1,4),(-1,5)] [(-2,6),(-1,6)] [(1,4),(1,5)] [(2,-5),(2,-4)] [(2,-6),(3,-6)] [(3,-4),(3,-3)] [(4,-4),(4,-3)] [(2,1),(2,2)] [(4,2),(4,3)] [(-3,3),(-3,4)] [(3,2),(3,3)] [(1,-5),(1,-4)] [(3,-5),(4,-5)] [(-1,-5),(-1,-4)]https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
當棋手從AI切換為人類時,應改變棋子狀態(不再是disable),並且立即停止思考。
處於旋轉狀態則立即切換成人類,並且不能再切換回AI。
處於旋轉狀態則立即切換成人類,並且不能再切換回AI。
上次由 ejsoon 在 2026年 7月 9日 14:32,总共编辑 1 次。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
trirhombus287.html
- 附件
-
trirhombus287.html.7z- (26.79 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
當點擊「下載」時,程式會直接下載當前的svg棋盤。現在改為彈出一個全屏窗口,其具體要求描述如下:
代码: 全选
點擊下載功能的改進
當點擊「下載」時,程式會直接下載當前的svg棋盤。現在改為彈出一個全屏窗口,其具體要求描述如下:
一,窗口右上角有叉形關閉按鈕,其內部有svg顯示區,下方選項區,以及下載按鈕。所有的元素都要展示出來。
二,svg顯示區將等比例顯示所要下載的svg圖像文件,其內容為svg棋盤的clone。
三,選項區有「是否顯示網格,是否顯示數字,是否下載動畫」,做成現代美觀的切換按鈕,其左方文字為「網格、數字、動畫」。把三者排在一行,平分間隔。
四,網格默認是勾選的,當取消時,將把clone中的網格去掉。
五,數字默認不勾選,當勾選時,將在ejdrop內的最後加上text標籤:
<text x="棋子横坐標" y="棋子縱坐標" stroke="none" fill="black" font-size="24" font-family="ZT Nature">棋子次序</text>
按clone中每個use棋子的次序添加對應的數字,在代碼中寫明數字的横坐標和縱坐標相對於棋子中心坐標的偏移量,並分為一位數和兩位數,使我在測試中可以修改。
之後在clone的.etboard中添加:
```
<style>
@font-face {
font-family: 'ZT Nature';
src: url(...) format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
</style>
```
我會自行補充url的內容。
六,當動畫勾選時,數字將不能勾選,二者相斥。
動畫也會在clone的.etboard中添加同樣的style。
動畫的步驟為:
1.為.etdrop增加屬性opacity=0,同時在其內部最前面添加動畫「<animate id="tr0" begin="0;trend.end" attributeName="opacity" values="0;0;1" fill="freeze" dur="2s"/>」。
2.每個use都有transform="translate(x,y)",取到它們的屬性坐標(x,y),在所有的<use>內部添加動畫「<animateTransform attributeName="transform" attributeType="XML" type="translate" from="屬性坐標" to="視圖外不遠的坐標,我方在下面,對方在上面" dur="1s" fill="freeze" begin="tr0.begin"/>」。
3.按次序為每一個use內部添加動畫「<animateTransform id="tr(n)" attributeName="transform" attributeType="XML" type="translate" from="視圖外坐標" to="屬性坐標" dur="1s" fill="freeze" begin="tr(n-1).end"/>」。例如第一個use內加的是「<animateTransform id="tr1" attributeName="transform" attributeType="XML" type="translate" from="視圖外坐標" to="屬性坐標" dur="1s" fill="freeze" begin="tr0.end"/>」。
4.當最後一個use棋子添加動畫(假設id="tr36")完畢後,挑出對於我方得分無用的棋子(算法是去掉一個棋子,如果分數不變則無用),這些棋子將以朝向當前視圖中心的反方向,添加往視圖外移動再移回來的動畫「<animateTransform id="trme" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖外坐標;視圖外坐標;視圖外坐標;屬性坐標" dur="4s" fill="freeze" begin="tr36.end+1s"/>」。
5.同時在.etboard內部的最後添加一個text標籤「<text id="trmetxt" x="視圖外距右邊界12" y="視圖內距下邊界12" stroke="none" fill="black" font-size="60" font-family="ZT Nature">我方得分</text>」,
動畫「<animateTransform id="trme" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖外坐標;視圖外坐標;視圖外坐標;屬性坐標" dur="4s" fill="freeze" begin="tr36.end+1s"/>」https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
當點擊「下載」時,程式會直接下載當前的svg棋盤。現在改為彈出一個窗口,其具體要求描述如下:
代码: 全选
點擊下載功能的改進
當點擊「下載」時,程式會直接下載當前的svg棋盤。現在改為彈出一個窗口,其具體要求描述如下:
一,窗口右上角有叉形關閉按鈕,其內部有svg顯示區,下方選項區,以及下載按鈕。所有的元素都要展示出來。
二,svg顯示區將等比例顯示所要下載的svg圖像文件,其內容為svg棋盤的clone。
三,選項區有「是否顯示網格,是否顯示數字,是否下載動畫」,做成現代美觀的切換按鈕,其左方文字為「網格、數字、動畫」。把三者排在一行,平分間隔。
四,網格默認是勾選的,當取消時,將把clone中的網格去掉。
五,數字默認不勾選,當勾選時,將在ejdrop內的最後加上text標籤:
<text x="棋子横坐標" y="棋子縱坐標" stroke="none" fill="black" font-size="24" font-family="ZT Nature">棋子次序</text>
按clone中每個use棋子的次序添加對應的數字,在代碼中寫明數字的横坐標和縱坐標相對於棋子中心坐標的偏移量,並分為一位數和兩位數,使我在測試中可以修改。
之後在clone的.etboard中添加:
```
<style>
@font-face {
font-family: 'ZT Nature';
src: url(...) format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
</style>
```
我會自行補充url的內容。
六,當動畫勾選時,數字將不能勾選,二者相斥。
動畫也會在clone的.etboard中添加同樣的style。
動畫的步驟為:
1.為.etdrop增加屬性opacity=0,同時在其內部最前面添加動畫「<animate id="tr0" begin="0;trend.end" attributeName="opacity" values="0;0;1" fill="freeze" dur="2s"/>」。
2.每個use都有transform="translate(x,y)",取到它們的屬性坐標(x,y),在所有的<use>內部添加動畫「<animateTransform attributeName="transform" attributeType="XML" type="translate" from="屬性坐標" to="視圖外不遠的坐標,我方在下面,對方在上面" dur="1s" fill="freeze" begin="tr0.begin"/>」。
3.按次序為每一個use內部添加動畫「<animateTransform id="tr(n)" attributeName="transform" attributeType="XML" type="translate" from="視圖外坐標" to="屬性坐標" dur="1s" fill="freeze" begin="tr(n-1).end"/>」。例如第一個use內加的是「<animateTransform id="tr1" attributeName="transform" attributeType="XML" type="translate" from="視圖外坐標" to="屬性坐標" dur="1s" fill="freeze" begin="tr0.end"/>」。
4.當最後一個use棋子添加動畫(假設id="tr36")完畢後,挑出對於我方得分無用的棋子(算法是去掉一個棋子,如果分數不變則無用),這些棋子將以朝向當前視圖中心的反方向,添加往視圖外移動再移回來的動畫「<animateTransform id="trme" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖外坐標;視圖外坐標;視圖外坐標;屬性坐標" dur="4s" fill="freeze" begin="tr36.end+1s"/>」。
5.同時在.etboard內部的最後添加一個text標籤「<text id="trmetxt" x="視圖外距右邊界12" y="視圖內距下邊界12+自身高" stroke="none" fill="black" font-size="60" font-family="ZT Nature">我方得分</text>」,在其下方添加動畫將其移到視圖內,使之x="視圖內距右邊界12+自身寬":「<animateTransform href="trmetxt" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖內坐標;視圖內坐標;視圖內坐標;屬性坐標" dur="4s" fill="freeze" begin="trme.begin"/>」。
注意「我方得分」是指一個整數,不要含有數字之外的任何字符。下方的「對方得分」也一樣。
6.之後挑出對於對方得分無用的棋子,這些棋子將添加添加往視圖外移動再移回來的動畫,朝向為當前視圖中心的反方向:「<animateTransform id="trhe" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖外坐標;視圖外坐標;視圖外坐標;屬性坐標" dur="4s" fill="freeze" begin="trme.end+1s"/>」。
7.同時在.etboard內部的最後添加一個text標籤「<text id="trhetxt" x="視圖外距右邊界12" y="視圖內距下邊界12+自身高" stroke="none" fill="black" font-size="60" font-family="ZT Nature">對方得分</text>」,在其下方添加動畫將其移到視圖內,使之x="視圖內距右邊界12+自身寬":「<animateTransform href="trhetxt" attributeName="transform" attributeType="XML" type="translate" values="屬性坐標;視圖內坐標;視圖內坐標;視圖內坐標;屬性坐標" dur="4s" fill="freeze" begin="trhe.begin"/>」。
8.最後在.etdrop內部最前面添加動畫「<animate id="trend" begin="trhe.end+1s" attributeName="opacity" values="1;1;0" fill="freeze" dur="2s"/>」。
如果取消勾選動畫,只要刪除clone內的所有animate和animateTransform標籤,以及添加的text和style。
需要明確給出所要修改的地方和所要替換的代碼。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
gemini 3.1 pro 有點聰明
代码: 全选
這是一個非常酷的升級!將原本單純的下載功能改為具有預覽、網格切換、標示棋子順序,甚至帶有完整播放與結算動畫的高級匯出器。
為了達到您的要求,我們需要修改原程式碼的三個部分:**CSS 樣式**、**HTML 結構**、以及 **JavaScript 邏輯**。
以下是具體的修改位置與替換的程式碼:
### 第一部分:新增 CSS 樣式
請在 `<head>` 區塊內的 `<style>` 標籤中(可以放在 `/* 彈出視窗 */` 的相關樣式附近),加入以下供下載視窗與切換按鈕使用的現代化樣式:
```css
/* 下載預覽視窗樣式 */
#download-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(3px);
z-index: 300;
display: none;
justify-content: center;
align-items: center;
}
.dl-modal-content {
background: #fff;
padding: 25px;
border-radius: 16px;
width: 450px;
max-width: 95%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
position: relative;
display: flex;
flex-direction: column;
gap: 15px;
}
.dl-svg-container {
width: 100%;
aspect-ratio: 1 / 1;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #ddd;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.dl-svg-container svg {
width: 100%;
height: 100%;
}
.dl-options {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
}
/* 現代化 Switch 切換開關 */
.toggle-group {
display: flex;
align-items: center;
gap: 8px;
font-weight: bold;
color: #555;
}
.switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc;
transition: .3s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .3s;
border-radius: 50%;
}
input:checked + .slider { background-color: var(--p1-color); }
input:checked + .slider:before { transform: translateX(20px); }
```
---
### 第二部分:新增 HTML 結構
請找到 `<div id="export-modal" ...>` 區塊的**結尾處**(也就是在 `</div>` 之後,`<div id="test-box">` 之前),插入以下新的下載對話框 DOM 結構:
```html
<div id="download-modal">
<div class="dl-modal-content">
<svg class="close-btn" onclick="document.getElementById('download-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 style="margin: 0; border-bottom: 2px solid #eee; padding-bottom: 10px; color: #333;">下載棋盤</h3>
<div class="dl-svg-container" id="dl-preview-container">
</div>
<div class="dl-options">
<div class="toggle-group">
<span>網格</span>
<label class="switch">
<input type="checkbox" id="toggle-grid" checked onchange="updateDownloadPreview()">
<span class="slider"></span>
</label>
</div>
<div class="toggle-group">
<span>數字</span>
<label class="switch">
<input type="checkbox" id="toggle-numbers" onchange="handleDownloadToggle('numbers')">
<span class="slider"></span>
</label>
</div>
<div class="toggle-group">
<span>動畫</span>
<label class="switch">
<input type="checkbox" id="toggle-anim" onchange="handleDownloadToggle('anim')">
<span class="slider"></span>
</label>
</div>
</div>
<button class="btn-primary" onclick="executeDownload()" style="width: 100%; margin: 0;">⬇ 確認下載 SVG</button>
</div>
</div>
```
---
### 第三部分:替換 JavaScript 邏輯
請在 `<script>` 區塊內,找到原本的 `function downloadBoardSVG()` 函數(大約在「下載與導入/導出功能」區塊中),**將原本的 `downloadBoardSVG()` 整個刪除,並替換為以下完整的全新邏輯**。
同時,我們在此處定義了數字的偏移量常數,方便您隨時修改:
```javascript
// --- 下載預覽與匯出 SVG 核心邏輯 ---
// 供測試與修改的數字偏移量 (相對於棋子中心坐標)
const OFFSET_1_DIGIT_X = -7;
const OFFSET_1_DIGIT_Y = 8;
const OFFSET_2_DIGIT_X = -14;
const OFFSET_2_DIGIT_Y = 8;
let dlPreviewSvg = null;
function downloadBoardSVG() {
document.getElementById('download-modal').style.display = 'flex';
updateDownloadPreview();
}
function handleDownloadToggle(type) {
let toggleNum = document.getElementById('toggle-numbers');
let toggleAnim = document.getElementById('toggle-anim');
// 數字與動畫互斥
if (type === 'numbers' && toggleNum.checked) toggleAnim.checked = false;
if (type === 'anim' && toggleAnim.checked) toggleNum.checked = false;
updateDownloadPreview();
}
function updateDownloadPreview() {
const originalSvg = document.getElementById('etani');
dlPreviewSvg = originalSvg.cloneNode(true);
dlPreviewSvg.removeAttribute('id');
const showGrid = document.getElementById('toggle-grid').checked;
const showNumbers = document.getElementById('toggle-numbers').checked;
const showAnim = document.getElementById('toggle-anim').checked;
const etboard = dlPreviewSvg.querySelector('.etboard');
const etdrop = dlPreviewSvg.querySelector('#etanidrop');
// 強制將預覽 SVG 的坐標置中,忽略當前使用者的拖拽與縮放
etdrop.setAttribute('transform', 'translate(240, 240) scale(1)');
// 1. 處理網格
if (!showGrid) {
const paths = etboard.querySelectorAll('path');
paths.forEach(p => p.remove());
}
// 如果啟用數字或動畫,添加自訂字型
if (showNumbers || showAnim) {
const styleNode = document.createElementNS("http://www.w3.org/2000/svg", "style");
styleNode.textContent = `
@font-face {
font-family: 'ZT Nature';
src: url('your_font_url_here.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
`;
etboard.appendChild(styleNode);
}
// 獲取場上依序落下的所有棋子
const pieces = dlPreviewSvg.querySelectorAll('.tiledropped');
// 2. 處理數字
if (showNumbers) {
pieces.forEach((piece, index) => {
let order = index + 1;
let transform = piece.getAttribute('transform');
let match = transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
if (match) {
let cx = parseFloat(match[1]);
let cy = parseFloat(match[2]);
let offsetX = order >= 10 ? OFFSET_2_DIGIT_X : OFFSET_1_DIGIT_X;
let offsetY = order >= 10 ? OFFSET_2_DIGIT_Y : OFFSET_1_DIGIT_Y;
let textNode = document.createElementNS("http://www.w3.org/2000/svg", "text");
textNode.setAttribute("x", cx + offsetX);
textNode.setAttribute("y", cy + offsetY);
textNode.setAttribute("stroke", "none");
textNode.setAttribute("fill", "black");
textNode.setAttribute("font-size", "24");
textNode.setAttribute("font-family", "ZT Nature");
textNode.textContent = order;
etdrop.appendChild(textNode);
}
});
}
// 3. 處理過場動畫
if (showAnim) {
// 初始化整體透明度
etdrop.setAttribute('opacity', '0');
const animDrop = document.createElementNS("http://www.w3.org/2000/svg", "animate");
animDrop.setAttribute("id", "tr0");
animDrop.setAttribute("begin", "0;trend.end");
animDrop.setAttribute("attributeName", "opacity");
animDrop.setAttribute("values", "0;0;1");
animDrop.setAttribute("fill", "freeze");
animDrop.setAttribute("dur", "2s");
etdrop.insertBefore(animDrop, etdrop.firstChild);
let lastAnimId = "tr0";
// 棋子依序飛入動畫
pieces.forEach((piece, index) => {
let order = index + 1;
let transform = piece.getAttribute('transform');
let match = transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
if(!match) return;
let cx = parseFloat(match[1]), cy = parseFloat(match[2]);
// 判斷玩家方向:利用 moveHistory,若無對應記錄則用 index 奇偶性兜底
let mHist = moveHistory[index];
let player = mHist ? mHist.player : (index % 2 === 0 ? 1 : 2);
let outY = player === 1 ? cy + 400 : cy - 400; // 視圖外不遠處:我方在下,對方在上
const animOut = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
animOut.setAttribute("attributeName", "transform");
animOut.setAttribute("attributeType", "XML");
animOut.setAttribute("type", "translate");
animOut.setAttribute("from", `${cx},${cy}`);
animOut.setAttribute("to", `${cx},${outY}`);
animOut.setAttribute("dur", "1s");
animOut.setAttribute("fill", "freeze");
animOut.setAttribute("begin", "tr0.begin");
piece.appendChild(animOut);
const animIn = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
animIn.setAttribute("id", `tr${order}`);
animIn.setAttribute("attributeName", "transform");
animIn.setAttribute("attributeType", "XML");
animIn.setAttribute("type", "translate");
animIn.setAttribute("from", `${cx},${outY}`);
animIn.setAttribute("to", `${cx},${cy}`);
animIn.setAttribute("dur", "1s");
animIn.setAttribute("fill", "freeze");
animIn.setAttribute("begin", `${lastAnimId}.end`);
piece.appendChild(animIn);
lastAnimId = `tr${order}`;
});
// 計算對得分無用的棋子 (虛擬拔除算法)
let p1Useless = [], p2Useless = [];
let baseScores = gameLogic.calculateScores();
moveHistory.forEach((m, idx) => {
let dummy = gameLogic.clone();
dummy.set(m.t1.idx, m.t1.N, 0);
dummy.set(m.t2.idx, m.t2.N, 0);
if (m.pid === 0) { // 藍棋特殊處理
dummy.bluePieces = dummy.bluePieces.filter(bp => !(bp.x === m.bpObj.x && bp.y === m.bpObj.y));
dummy.bpLookup[dummy._getIndex(m.bpObj.x, m.bpObj.y)] = null;
dummy.bpLookup[dummy._getIndex(m.bpObj.x+1, m.bpObj.y)] = null;
}
let newScores = dummy.calculateScores();
if (m.player === 1 && newScores.p1Score === baseScores.p1Score) p1Useless.push(idx);
if (m.player === 2 && newScores.p2Score === baseScores.p2Score) p2Useless.push(idx);
});
// 我方 (P1) 無用棋子剔除動畫
p1Useless.forEach((pieceIdx, arrayIdx) => {
let piece = pieces[pieceIdx];
if(!piece) return;
let match = piece.getAttribute('transform').match(/translate\(([^,]+),\s*([^)]+)\)/);
if(!match) return;
let cx = parseFloat(match[1]), cy = parseFloat(match[2]);
let dist = Math.hypot(cx, cy) || 1;
let outX = cx + (cx/dist)*600;
let outY = cy + (cy/dist)*600;
let animUseless = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
if (arrayIdx === 0) animUseless.setAttribute("id", "trme");
animUseless.setAttribute("attributeName", "transform");
animUseless.setAttribute("attributeType", "XML");
animUseless.setAttribute("type", "translate");
animUseless.setAttribute("values", `${cx},${cy};${outX},${outY};${outX},${outY};${outX},${outY};${cx},${cy}`);
animUseless.setAttribute("dur", "4s");
animUseless.setAttribute("fill", "freeze");
animUseless.setAttribute("begin", `${lastAnimId}.end+1s`);
piece.appendChild(animUseless);
});
// 我方 (P1) 得分飛入動畫
let textP1 = document.createElementNS("http://www.w3.org/2000/svg", "text");
textP1.setAttribute("id", "trmetxt");
textP1.setAttribute("x", "0");
textP1.setAttribute("y", "0");
textP1.setAttribute("stroke", "none");
textP1.setAttribute("fill", "black");
textP1.setAttribute("font-size", "60");
textP1.setAttribute("font-family", "ZT Nature");
textP1.textContent = baseScores.p1Score;
etboard.appendChild(textP1);
// 由於 viewBox 中心是 (0,0),右邊界約為 +240,下邊界為 +240
let tP1_outX = 252, tP1_inX = 128, tY_P1 = 228;
let animTextP1 = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
animTextP1.setAttribute("attributeName", "transform");
animTextP1.setAttribute("attributeType", "XML");
animTextP1.setAttribute("type", "translate");
animTextP1.setAttribute("values", `${tP1_outX},${tY_P1};${tP1_inX},${tY_P1};${tP1_inX},${tY_P1};${tP1_inX},${tY_P1};${tP1_outX},${tY_P1}`);
animTextP1.setAttribute("dur", "4s");
animTextP1.setAttribute("fill", "freeze");
animTextP1.setAttribute("begin", p1Useless.length > 0 ? "trme.begin" : `${lastAnimId}.end+1s`);
textP1.appendChild(animTextP1);
let trmeEndEvent = p1Useless.length > 0 ? "trme.end" : `${lastAnimId}.end+5s`;
// 對方 (P2) 無用棋子剔除動畫
p2Useless.forEach((pieceIdx, arrayIdx) => {
let piece = pieces[pieceIdx];
if(!piece) return;
let match = piece.getAttribute('transform').match(/translate\(([^,]+),\s*([^)]+)\)/);
if(!match) return;
let cx = parseFloat(match[1]), cy = parseFloat(match[2]);
let dist = Math.hypot(cx, cy) || 1;
let outX = cx + (cx/dist)*600;
let outY = cy + (cy/dist)*600;
let animUseless = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
if(arrayIdx === 0) animUseless.setAttribute("id", "trhe");
animUseless.setAttribute("attributeName", "transform");
animUseless.setAttribute("attributeType", "XML");
animUseless.setAttribute("type", "translate");
animUseless.setAttribute("values", `${cx},${cy};${outX},${outY};${outX},${outY};${outX},${outY};${cx},${cy}`);
animUseless.setAttribute("dur", "4s");
animUseless.setAttribute("fill", "freeze");
animUseless.setAttribute("begin", `${trmeEndEvent}+1s`);
piece.appendChild(animUseless);
});
// 對方 (P2) 得分飛入動畫 (放置於視圖內上方)
let textP2 = document.createElementNS("http://www.w3.org/2000/svg", "text");
textP2.setAttribute("id", "trhetxt");
textP2.setAttribute("x", "0");
textP2.setAttribute("y", "0");
textP2.setAttribute("stroke", "none");
textP2.setAttribute("fill", "black");
textP2.setAttribute("font-size", "60");
textP2.setAttribute("font-family", "ZT Nature");
textP2.textContent = baseScores.p2Score;
etboard.appendChild(textP2);
let tP2_outX = 252, tP2_inX = 128, tY_P2 = -168; // 放置於頂部區域附近
let animTextP2 = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
animTextP2.setAttribute("attributeName", "transform");
animTextP2.setAttribute("attributeType", "XML");
animTextP2.setAttribute("type", "translate");
animTextP2.setAttribute("values", `${tP2_outX},${tY_P2};${tP2_inX},${tY_P2};${tP2_inX},${tY_P2};${tP2_inX},${tY_P2};${tP2_outX},${tY_P2}`);
animTextP2.setAttribute("dur", "4s");
animTextP2.setAttribute("fill", "freeze");
let trheBeginEvent = p2Useless.length > 0 ? "trhe.begin" : `${trmeEndEvent}+1s`;
animTextP2.setAttribute("begin", trheBeginEvent);
textP2.appendChild(animTextP2);
let trheEndEvent = p2Useless.length > 0 ? "trhe.end" : `${trmeEndEvent}+5s`;
// 最後的結尾淡出動畫
const animEnd = document.createElementNS("http://www.w3.org/2000/svg", "animate");
animEnd.setAttribute("id", "trend");
animEnd.setAttribute("begin", `${trheEndEvent}+1s`);
animEnd.setAttribute("attributeName", "opacity");
animEnd.setAttribute("values", "1;1;0");
animEnd.setAttribute("fill", "freeze");
animEnd.setAttribute("dur", "2s");
etdrop.insertBefore(animEnd, etdrop.firstChild);
}
// 將處理完畢的 clone 放入預覽容器中
const container = document.getElementById('dl-preview-container');
container.innerHTML = '';
container.appendChild(dlPreviewSvg);
}
function executeDownload() {
if (!dlPreviewSvg) return;
let source = new XMLSerializer().serializeToString(dlPreviewSvg);
if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([source], { type: 'image/svg+xml;charset=utf-8' }));
a.download = 'trirhombus_board.svg';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
```
由於這段實作採用的是**每次勾選按鈕,就重新去拿原生棋盤 `clone` 並掛載對應屬性**,所以不需要撰寫任何拔除 `animate` 標籤的程式碼,只要玩家取消勾選,再次生成的 `dlPreviewSvg` 就會是純淨狀態!不要忘記將代碼中的 `'your_font_url_here.woff2'` 修改為您正確的字型網址。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 準備為三菱棋做遊戲和MCTS搜尋
在前面的基礎上,在最下方中間有下載按鈕,在按鈕下方標注文檔大小byte。
檔案名trirhombus_yyyymmdd_hhiiss.svg,如果動畫加上動畫英文,數字加上數字英文。
gemini 3.1 pro 趕快加 下載按鈕
檔案名trirhombus_yyyymmdd_hhiiss.svg,如果動畫加上動畫英文,數字加上數字英文。
gemini 3.1 pro 趕快加 下載按鈕
代码: 全选
```python
with open("trirhombus287.html", "r", encoding="utf-8") as f:
content = f.read()
# Let's find downloadBoardSVG or download button
import re
print("Length of file:", len(content))
download_matches = [m.start() for m in re.finditer("downloadBoardSVG", content)]
print("downloadBoardSVG positions:", download_matches)
for m in download_matches:
print(content[m:m+300])
```
```text
Length of file: 129896
downloadBoardSVG positions: [23866, 82856]
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.
downloadBoardSVG() {
const svg = document.getElementById('etani');
let source = new XMLSerializer().serializeToString(svg);
if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/))
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000
```
```python
def generate_filename(has_num, has_anim):
import datetime
now = datetime.datetime.now()
base = "trirhombus_" + now.strftime("%Y%m%d_%H%M%S")
if has_num:
base += "_num"
if has_anim:
base += "_anim"
return base + ".svg"
print(generate_filename(True, False))
print(generate_filename(False, True))
```
```text
trirhombus_20260710_044935_num.svg
trirhombus_20260710_044935_anim.svg
```
在前面的基礎上,我們需要對 HTML 結構和 JavaScript 邏輯進行小幅修改,來加入檔案大小(byte)的計算與顯示,並動態生成帶有時間戳與英文後綴的檔案名稱。
請按照以下兩個步驟替換程式碼:
### 第一部分:修改 HTML 結構
請在 `<div id="download-modal">` 內部,找到最下方原本的單行 `<button>` 按鈕:
```html
<button class="btn-primary" onclick="executeDownload()" style="width: 100%; margin: 0;">⬇ 確認下載 SVG</button>
```
**將其替換為以下程式碼:**
(這會將按鈕置中,並在按鈕下方加入一行顯示位元組大小的文字區域)
```html
<div style="text-align: center; margin-top: 10px;">
<button class="btn-primary" onclick="executeDownload()" style="width: 100%; margin: 0 0 5px 0;">⬇ 確認下載 SVG</button>
<div id="dl-file-size" style="font-size: 0.9rem; color: #666; font-family: monospace;">檔案大小: 計算中...</div>
</div>
```
---
### 第二部分:修改 JavaScript 邏輯
**1. 修改 `updateDownloadPreview()` 的末尾部分:**
找到 `updateDownloadPreview()` 函數**最後面**的這三行:
```javascript
// 將處理完畢的 clone 放入預覽容器中
const container = document.getElementById('dl-preview-container');
container.innerHTML = '';
container.appendChild(dlPreviewSvg);
```
**將其替換為以下程式碼:**
(每次預覽更新時,即時計算 SVG 轉字串後的 Byte 大小並顯示)
```javascript
// 將處理完畢的 clone 放入預覽容器中
const container = document.getElementById('dl-preview-container');
container.innerHTML = '';
container.appendChild(dlPreviewSvg);
// --- 新增:計算並顯示檔案大小 (bytes) ---
let source = new XMLSerializer().serializeToString(dlPreviewSvg);
if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
let byteSize = new Blob([source], { type: 'image/svg+xml;charset=utf-8' }).size;
document.getElementById('dl-file-size').innerText = `檔案大小: ${byteSize.toLocaleString()} bytes`;
```
**2. 替換 `executeDownload()` 函數:**
找到原來的 `executeDownload()` 函數,**將整個函數刪除,並替換為以下程式碼:**
(此邏輯會自動獲取當前時間格式化為 `yyyymmdd_hhiiss`,並根據 checkbox 的狀態補上 `_anim` 或 `_num` 後綴)
```javascript
function executeDownload() {
if (!dlPreviewSvg) return;
let source = new XMLSerializer().serializeToString(dlPreviewSvg);
if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
// 取得當前時間生成檔案名 yyyymmdd_hhiiss
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const ii = String(now.getMinutes()).padStart(2, '0'); // i 代表分鐘
const ss = String(now.getSeconds()).padStart(2, '0');
let filename = `trirhombus_${yyyy}${mm}${dd}_${hh}${ii}${ss}`;
// 根據勾選狀態加上英文後綴
if (document.getElementById('toggle-numbers').checked) {
filename += '_num';
} else if (document.getElementById('toggle-anim').checked) {
filename += '_anim';
}
filename += '.svg';
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([source], { type: 'image/svg+xml;charset=utf-8' }));
a.download = filename; // 套用新的動態檔名
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 9 访客