Re: 將製作ejcees(中國象棋打譜程式)
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
改好了resize的問題:
https://ejsoon.vip/wp-content/uploads/2 ... 99941.html
https://ejsoon.vip/wp-content/uploads/2 ... 99941.html
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
為何rotate和flip老是跳,在自動播放時。
代码: 全选
{"v":[{"m":"C2=5","v":[{"m":"n8+7","v":[{"m":"N2+3","v":[{"m":"r9=8","v":[{"m":"P7+1","v":[{"m":"p7+1","v":[{"m":"R1+1","v":[{"m":"n2+3","v":[{"m":"N8+7","v":[{"m":"c2+4"}]},{"m":"R1=4","v":[{"m":"b3+5","v":[{"m":"N8+7","v":[{"m":"a4+5","v":[{"m":"C8=9","v":[{"m":"c2+4","v":[{"m":"R9=8","v":[{"m":"r1=2","v":[{"m":"N7+6","v":[{"m":"c2=7","v":[{"m":"R8+9","v":[{"m":"n3-2"}]}]}]}]}]}]}]}]},{"m":"c2+4","v":[{"m":"C8=9","v":[{"m":"r1=2","v":[{"m":"R9=8","v":[{"m":"c8+4","v":[{"m":"N7+6","v":[{"m":"c2=7","v":[{"m":"R8+9","v":[{"m":"c7+3","v":[{"m":"A4+5","v":[{"m":"n3-2","v":[{"m":"R4=2","v":[{"m":"c7=9","v":[{"m":"N3+4","v":[{"m":"p7+1","v":[{"m":"N4+6","v":[{"m":"r8+1","v":[{"m":"C9+4","v":[{"m":"r8=4","v":[{"m":"C5=6","v":[{"m":"c8-1","v":[{"m":"C9-2","v":[{"m":"c8=4","v":[{"m":"C9=6","v":[{"m":"r4=6","v":[{"m":"R2=1","v":[{"m":"c9=8"}]}]}]}]}]},{"m":"R2=1","v":[{"m":"c9=7","v":[{"m":"R1-1","v":[{"m":"c7-3","v":[{"m":"C9-2","v":[{"m":"n2+1"},{"m":"r4=6"},{"m":"c8=4","v":[{"m":"C9=6","v":[{"m":"r4=6","v":[{"m":"N6+5","v":[{"m":"a4+5","v":[{"m":"R1=3","v":[{"m":"n2+1","v":[{"m":"-C=9","v":[{"m":"r6=8"}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]},{"m":"c8+2","v":[{"m":"N7+6","v":[{"m":"c2=7","v":[{"m":"R8+9","v":[{"m":"c7+3","v":[{"m":"A4+5","v":[{"m":"n3-2","v":[{"m":"R4=2"}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}],"fen":"rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w"}
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
moveInterval = initialHistory.moveInterval ? parseFloat(initialHistory.moveInterval) : moveInterval;
moveSpeed = initialHistory.moveSpeed ? parseFloat(initialHistory.moveSpeed) : moveSpeed;
isRotateEnabled = initialHistory.isRotateEnabled || isRotateEnabled;
isFlipEnabled = initialHistory.isFlipEnabled || isFlipEnabled;https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
刪掉各種console。
- 附件
-
ejcees20260319resize99942.7z- (35.5 KiB) 已下载 66 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
當導入文本時,如果沒有fen,將整合到currentStep當前局面,如果有則新建局面。
增加引擎坐標轉換。
svg動畫記錄rotate和flip。
增加引擎坐標轉換。
svg動畫記錄rotate和flip。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
如果historyFEN的根節點含有評論的話,ejceesrcdstart的右下角也要顯示評論圖標,跟「.ejceesrcdstep.has-comment::after」一樣。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
- 附件
-
ejcees20260321optimize99944.7z- (35.02 KiB) 已下载 63 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
導入文本時,加上「toggle」位置按鈕,切換成引擎坐標表示法,如「a0b0 b2e2」。
之前貼上文本時,如果沒有局面信息,則設為默認初始局面。現在改為如果沒有局面信息,則把後續的走子都加入當前的currentStep中。
沒有局面信息,可以接收以下三種情況:a0b0,車九平八,1. 車九平八 。如果有局面信息,則首先判斷它跟當前局面是否一樣,如果不一樣,往前洄溯父節點,直到找到一樣的。如果直至根節點都不一樣,則新建一個。
合並局面之後,分支要切換至新增的分支,當前局面跳到所增加分支的節點上。
之前貼上文本時,如果沒有局面信息,則設為默認初始局面。現在改為如果沒有局面信息,則把後續的走子都加入當前的currentStep中。
沒有局面信息,可以接收以下三種情況:a0b0,車九平八,1. 車九平八 。如果有局面信息,則首先判斷它跟當前局面是否一樣,如果不一樣,往前洄溯父節點,直到找到一樣的。如果直至根節點都不一樣,則新建一個。
合並局面之後,分支要切換至新增的分支,當前局面跳到所增加分支的節點上。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
改進:
一,如果historyFEN的根節點含有評論的話,ejceesrcdstart的右下角也要顯示評論圖標,跟「.ejceesrcdstep.has-comment::after」一樣。
二,當導出svg單圖或動畫時,應記錄當前的rotate和flip狀態,把它寫入.ejceespbanimate的屬性ejceesrotate="1"和ejceesflip="1"。
當打開svg時,在把from和to轉成坐標之前,要先根據ejceesrotate和ejceesflip來轉換。
三,導入文本時,在右邊兩個下載鏈接後面,加上「toggle」按鈕(顯示為一個72*48px的svg圖片),點擊時會把左邊的文本切換成引擎坐標表示法,格式為「
position fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w moves a0b0 b2e2」。再次點擊則切換回來。
之前貼上文本時,如果沒有fen局面信息,則設為默認初始局面。現在改為如果沒有fen局面信息,則把後續的走子都加入當前的currentStep中。
在導入文本時,可以接收:
1. 車九平八 車1平2(多行,每行兩步,行首有數字)
現在增加以下兩種情況:
a0b0(單行,空格分隔。字母表示横坐標從左到右,數字表示縱坐標從下到上,比如c0e2轉成坐標就是2,9到4,7)
車九平八(單行,空格分隔)
如果含有fen局面信息,則首先判斷它跟當前局面是否一樣,如果不一樣,往前洄溯父節點,直到找到一樣的。如果直至根節點都不一樣,則新建局面。
合並局面之後,當前分支要切換至新增的分支,當前局面跳到所增加的分支的父節點上。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
一,如果historyFEN的根節點含有評論的話,ejceesrcdstart的右下角也要顯示評論圖標,跟「.ejceesrcdstep.has-comment::after」一樣。
二,當導出svg單圖或動畫時,應記錄當前的rotate和flip狀態,把它寫入.ejceespbanimate的屬性ejceesrotate="1"和ejceesflip="1"。
當打開svg時,在把from和to轉成坐標之前,要先根據ejceesrotate和ejceesflip來轉換。
三,導入文本時,在右邊兩個下載鏈接後面,加上「toggle」按鈕(顯示為一個72*48px的svg圖片),點擊時會把左邊的文本切換成引擎坐標表示法,格式為「
position fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w moves a0b0 b2e2」。再次點擊則切換回來。
之前貼上文本時,如果沒有fen局面信息,則設為默認初始局面。現在改為如果沒有fen局面信息,則把後續的走子都加入當前的currentStep中。
在導入文本時,可以接收:
1. 車九平八 車1平2(多行,每行兩步,行首有數字)
現在增加以下兩種情況:
a0b0(單行,空格分隔。字母表示横坐標從左到右,數字表示縱坐標從下到上,比如c0e2轉成坐標就是2,9到4,7)
車九平八(單行,空格分隔)
如果含有fen局面信息,則首先判斷它跟當前局面是否一樣,如果不一樣,往前洄溯父節點,直到找到一樣的。如果直至根節點都不一樣,則新建局面。
合並局面之後,當前分支要切換至新增的分支,當前局面跳到所增加的分支的父節點上。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
grok
代码: 全选
```html
<!-- MODIFICATION 1: CSS RULE FOR ROOT COMMENT ICON -->
<style>
/* ... existing styles ... */
/* NEW: Root start banner comment indicator (same visual as steps) */
.ejceesrcdstart.has-comment::after {
content: '';
position: absolute;
bottom: 4px;
right: 4px;
width: 12px;
height: 12px;
background: url('data:image/svg+xml;utf8,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27><path fill=%27%23ffd700%27 d=%27M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z%27/></svg>') no-repeat center/contain;
pointer-events: none;
}
</style>
```
```javascript
// MODIFICATION 1: Update renderRecordUI to add .has-comment to start banner if root has comment
function renderRecordUI() {
// ... existing code until startDiv creation ...
const startDiv = document.createElement('div');
startDiv.id = 'record-start';
startDiv.classList.add('ejceesrcdstart');
startDiv.onclick = ejceesrcdstartclick;
// NEW: Check root comment
if (historyFEN.c && historyFEN.c.trim() !== '') {
startDiv.classList.add('has-comment');
}
if (historyFEN.fen === INITIAL_FEN) {
startDiv.textContent = '棋局開始,紅方先行';
} else if (historyFEN.fen.includes(' w')) {
startDiv.textContent = '特定局面,輪到紅方';
} else {
startDiv.textContent = '特定局面,輪到黑方';
}
// ... rest unchanged ...
}
```
```javascript
// MODIFICATION 2: Export SVG - add rotate/flip attributes
// Inside document.getElementById('tool-exp-svg').addEventListener('click', () => { ... }
const cloneSvg = document.querySelector('.ejceespb').cloneNode(true);
// NEW: Record current view state on the animation group
const etdropClone = cloneSvg.querySelector('.etdrop') || cloneSvg.querySelector('.ejceespbanimate');
if (etdropClone) {
etdropClone.setAttribute('ejceesrotate', isRotateEnabled ? '1' : '0');
etdropClone.setAttribute('ejceesflip', isFlipEnabled ? '1' : '0');
}
// ... rest of export code unchanged ...
```
```javascript
// MODIFICATION 2: Import SVG - read and apply rotate/flip BEFORE coordinate conversion
function importExportedSVG(svgText) {
// ... existing parser code ...
const etdropEl = svgDoc.querySelector('.etdrop') || svgDoc.querySelector('.ejceespbanimate');
const importedRotate = etdropEl && etdropEl.getAttribute('ejceesrotate') === '1';
const importedFlip = etdropEl && etdropEl.getAttribute('ejceesflip') === '1';
// NEW: Helper to convert visual coords back using imported view state
function visualToLogical(vx, vy) {
let lx = vx;
let ly = vy;
if (importedRotate) {
lx = 8 - lx;
ly = 9 - ly;
}
if (importedFlip) lx = 8 - lx;
return { x: lx, y: ly };
}
// ... later in the step loop, replace raw Math.round parsing with:
const fParts = fromStr.split(',');
const tParts = toStr.split(',');
const startVisX = Math.round((parseFloat(fParts[0]) - 24) / 48);
const startVisY = Math.round((parseFloat(fParts[1]) - 24) / 48);
const endVisX = Math.round((parseFloat(tParts[0]) - 24) / 48);
const endVisY = Math.round((parseFloat(tParts[1]) - 24) / 48);
const startLog = visualToLogical(startVisX, startVisY);
const endLog = visualToLogical(endVisX, endVisY);
// Use startLog.x / startLog.y etc. for the rest of the logic
// ... rest of import unchanged ...
}
```
```javascript
// MODIFICATION 3: Updated renderExportTextUI with toggle button (72x48 SVG)
function renderExportTextUI() {
// ... existing cnText / enText generation ...
recordContainer.innerHTML = `<textarea class="ejceescomment-edit" id="export-textarea" style="width:100%; height:100%; ...">${cnText}</textarea>`;
// NEW: Toggle button (72*48 px SVG icon)
const toggleSVG = `
<svg width="72" height="48" viewBox="0 0 72 48" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="8" width="64" height="32" rx="4" fill="#333" stroke="#fff" stroke-width="3"/>
<text x="36" y="28" text-anchor="middle" fill="#fff" font-family="monospace" font-size="11" font-weight="bold">ENGINE</text>
<path d="M12 20 L20 28 L12 36" fill="none" stroke="#fff" stroke-width="3" stroke-linejoin="round"/>
<path d="M52 20 L60 28 L52 36" fill="none" stroke="#fff" stroke-width="3" stroke-linejoin="round"/>
</svg>
`;
commentDiv.innerHTML = `
<div style="display:flex; flex-direction:column; gap:10px; padding:10px;">
<a href="${URL.createObjectURL(cnBlob)}" download="ejcees_fen_${timestamp}.txt" style="color:#66b2ff;">fen (${cnBlob.size} bytes)</a>
<a href="${URL.createObjectURL(enBlob)}" download="ejcees_en_fen_${timestamp}.txt" style="color:#66b2ff;">en_fen (${enBlob.size} bytes)</a>
<div id="toggle-engine" style="cursor:pointer; width:72px; height:48px; margin:8px auto;">${toggleSVG}</div>
</div>
`;
// ... existing confirm/cancel buttons ...
// NEW: Toggle handler
let isEngineMode = false;
document.getElementById('toggle-engine').addEventListener('click', () => {
isEngineMode = !isEngineMode;
const textarea = document.getElementById('export-textarea');
if (isEngineMode) {
textarea.value = generateEngineText();
} else {
textarea.value = cnText; // restore original Chinese
}
});
}
```
```javascript
// MODIFICATION 3: NEW helper to generate engine coordinate text
function generateEngineText() {
let path = getGamePath();
let fen = path.fen;
let movesStr = '';
let currentNode = path;
while (currentNode.v && currentNode.v.length) {
const m = currentNode.v[0].move || '';
// Convert to engine coord (a0b0 style) using deriveCoordsFromMove logic
const dc = deriveCoordsFromMove(currentNode.fen, m);
if (dc) {
const startFile = String.fromCharCode(97 + dc.startX); // a-i
const startRank = (9 - dc.startY).toString(); // 0-9 bottom-up
const endFile = String.fromCharCode(97 + dc.endX);
const endRank = (9 - dc.endY).toString();
movesStr += ` ${startFile}${startRank}${endFile}${endRank}`;
}
currentNode = currentNode.v[0];
}
return `position fen ${fen} moves${movesStr}`;
}
```
```javascript
// MODIFICATION 3: Enhanced importExportedText (handles new formats + FEN matching)
function importExportedText(text) {
text = text.trim();
if (!text) return;
// NEW: Detect engine coord lines (single line with a0b0 style)
const lines = text.split('\n').map(l => l.trim()).filter(l => l);
let baseFen = INITIAL_FEN;
let moveList = [];
// Try to extract FEN if present
const fenMatch = text.match(/fen\s+([a-zA-Z0-9/]+\s+[wb])/i);
if (fenMatch) {
baseFen = fenMatch[1].trim();
}
// NEW: FEN matching logic - find common ancestor or create new branch
let targetNode = historyFEN;
let foundMatch = false;
let parentPath = [];
let current = historyFEN;
while (current) {
if (current.fen === baseFen) {
targetNode = current;
foundMatch = true;
break;
}
if (current.v && current.v.length) {
parentPath.push(current);
current = current.v[currentBranch[parentPath.length - 1] || 0];
} else {
break;
}
}
if (!foundMatch) {
// Create new root branch
targetNode = { fen: baseFen, move: null, lastMove: null, c: "", v: [] };
historyFEN.v.push(targetNode); // attach as new branch from root
currentBranch = [historyFEN.v.length - 1];
} else {
// Use matched node as insertion point
currentBranch = [...currentBranch.slice(0, parentPath.length)];
}
// Parse moves (support Chinese, engine coord, and mixed)
for (let line of lines) {
if (/^\d+\./.test(line)) {
// Traditional multi-line Chinese
const tokens = line.replace(/^\d+\.\s*/, '').trim().split(/\s+/);
for (let token of tokens) {
if (token === '...' || !token) continue;
let enMove = NotationConverter.toEnglish(token, baseFen.includes(' w'));
moveList.push(enMove);
}
} else if (/^[a-i][0-9][a-i][0-9](?:\s+[a-i][0-9][a-i][0-9])*$/.test(line)) {
// NEW: Engine coord format (a0b0 c2d4 ...)
const coords = line.match(/[a-i][0-9][a-i][0-9]/g) || [];
for (let pair of coords) {
const sx = pair.charCodeAt(0) - 97;
const sy = 9 - parseInt(pair[1]);
const ex = pair.charCodeAt(2) - 97;
const ey = 9 - parseInt(pair[3]);
// Convert to internal move string (will be normalized later)
moveList.push(`${String.fromCharCode(97 + sx)}${9 - sy}${String.fromCharCode(97 + ex)}${9 - ey}`);
}
} else if (/[\u4e00-\u9fa5]/.test(line)) {
// Chinese single-line
const tokens = line.split(/\s+/);
for (let token of tokens) {
if (NotationConverter.enPiece[token[0]] || token.length > 1) {
moveList.push(NotationConverter.toEnglish(token, baseFen.includes(' w')));
}
}
}
}
// Apply moves to targetNode (append to current branch)
let currentNode = targetNode;
let currentFen = baseFen;
for (let m of moveList) {
const dc = deriveCoordsFromMove(currentFen, m);
if (!dc) continue;
const nextFen = simulateMove(currentFen, dc);
const newNode = { fen: nextFen, move: m, lastMove: dc, c: "", v: [] };
currentNode.v.push(newNode);
currentNode = newNode;
currentFen = nextFen;
}
// Switch to the newly added branch
if (targetNode.v.length > 0) {
currentBranch.push(targetNode.v.length - 1);
}
currentStepIndex = getPathDepth(historyFEN, currentBranch) - 1;
renderRecordUI();
renderNoteUI();
jumpToStep(currentStepIndex);
saveStateToUndo();
updateToolHighlights();
}
```
**All other parts of the original file remain unchanged.** These are the exact modifications required for the three improvements.https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
gemini
https://ejsoon.vip/wp-content/uploads/2 ... Recor.html
https://ejsoon.vip/wp-content/uploads/2 ... Recor.html
代码: 全选
Conversation with Gemini
You said
改進:
一,如果historyFEN的根節點含有評論的話,ejceesrcdstart的右下角也要顯示評論圖標,跟「.ejceesrcdstep.has-comment::after」一樣。
二,當導出svg單圖或動畫時,應記錄當前的rotate和flip狀態,把它寫入.ejceespbanimate的屬性ejceesrotate="1"和ejceesflip="1"。
當打開svg時,在把from和to轉成坐標之前,要先根據ejceesrotate和ejceesflip來轉換。
三,導入文本時,在右邊兩個下載鏈接後面,加上「toggle」按鈕(顯示為一個72*48px的svg圖片),點擊時會把左邊的文本切換成引擎坐標表示法,格式為「
position fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w moves a0b0 b2e2」。再次點擊則切換回來。
之前貼上文本時,如果沒有fen局面信息,則設為默認初始局面。現在改為如果沒有fen局面信息,則把後續的走子都加入當前的currentStep中。
在導入文本時,可以接收:
1. 車九平八 車1平2(多行,每行兩步,行首有數字)
現在增加以下兩種情況:
a0b0(單行,空格分隔。字母表示横坐標從左到右,數字表示縱坐標從下到上,比如c0e2轉成坐標就是2,9到4,7)
車九平八(單行,空格分隔)
如果含有fen局面信息,則首先判斷它跟當前局面是否一樣,如果不一樣,往前洄溯父節點,直到找到一樣的。如果直至根節點都不一樣,則新建局面。
合並局面之後,當前分支要切換至新增的分支,當前局面跳到所增加的分支的父節點上。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
Gemini said
Here are the modifications to address your requirements. All instructions and code comments are in English.
1. CSS Modifications
Update the .ejceesrcdstart class to add position: relative; and add the new .ejceesrcdstart.has-comment::after rule so it can display the comment icon exactly like .ejceesrcdstep.
Find this block in your <style> section:
CSS
.ejceesrcdstart {
width: 100%;
padding: 8px;
box-sizing: border-box;
text-align: center;
font-weight: bold;
border-bottom: 1px solid #444;
color: #aaa;
font-size: 14px;
cursor: pointer;
}
Replace it with the following:
CSS
.ejceesrcdstart {
width: 100%;
padding: 8px;
box-sizing: border-box;
text-align: center;
font-weight: bold;
border-bottom: 1px solid #444;
color: #aaa;
font-size: 14px;
cursor: pointer;
position: relative; /* Added for absolute positioning of the marker */
}
.ejceesrcdstart.has-comment::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
background: url('data:image/svg+xml;utf8,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27><path fill=%27%23ffd700%27 d=%27M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z%27/></svg>') no-repeat center/contain;
pointer-events: none;
}
2. JavaScript Modifications
A. Add Comment Icon to Start Node
In the renderRecordUI() function, add logic to check if the root historyFEN has a comment and apply the class.
Find this part inside renderRecordUI():
JavaScript
if (historyFEN.fen === INITIAL_FEN) {
startDiv.textContent = '棋局開始,紅方先行';
} else if (historyFEN.fen.includes(' w')) {
startDiv.textContent = '特定局面,輪到紅方';
} else {
startDiv.textContent = '特定局面,輪到黑方';
}
recordContainer.innerHTML = '';
recordContainer.appendChild(startDiv);
Add the comment check right before appending:
JavaScript
if (historyFEN.fen === INITIAL_FEN) {
startDiv.textContent = '棋局開始,紅方先行';
} else if (historyFEN.fen.includes(' w')) {
startDiv.textContent = '特定局面,輪到紅方';
} else {
startDiv.textContent = '特定局面,輪到黑方';
}
// Check if root has comment
if (historyFEN.c && historyFEN.c.trim() !== '') {
startDiv.classList.add('has-comment');
}
recordContainer.innerHTML = '';
recordContainer.appendChild(startDiv);
B. Allow getMoveNotation to Accept a Custom Map
We need getMoveNotation to accept a currentMap parameter to evaluate abstract logical states during text import without moving actual DOM elements.
Find the definition:
JavaScript
function getMoveNotation(pieceId, startX, startY, endX, endY, capturedId) {
Replace it with:
JavaScript
function getMoveNotation(pieceId, startX, startY, endX, endY, capturedId, currentMap = tileMap) {
Inside that function, find this loop:
JavaScript
for (const [pos, id] of tileMap.entries()) {
Replace it with:
JavaScript
for (const [pos, id] of currentMap.entries()) {
C. Modify Export/Import SVG for Rotate/Flip
During SVG Export, record the ejceesrotate and ejceesflip attributes.
Inside the document.getElementById('tool-exp-svg').addEventListener('click', ...) callback, find this block:
JavaScript
// 2. Remove specific elements
if (isAnimation) {
Right before if (isAnimation) {, add these lines:
JavaScript
// Record current view states onto the SVG
cloneSvg.setAttribute('ejceesrotate', isRotateEnabled ? '1' : '0');
cloneSvg.setAttribute('ejceesflip', isFlipEnabled ? '1' : '0');
Then, update importExportedSVG to reverse these view states when parsing coordinates.
Find this function:
JavaScript
function importExportedSVG(svgText) {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgText, "image/svg+xml");
Modify the beginning of the function and coordinate extraction logic like so:
JavaScript
function importExportedSVG(svgText) {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgText, "image/svg+xml");
// Extract the saved view mode from the exported SVG
const isRotated = svgDoc.documentElement.getAttribute('ejceesrotate') === '1';
const isFlipped = svgDoc.documentElement.getAttribute('ejceesflip') === '1';
// Helper to reverse export-time visual coordinates back to logical coordinates
function parseExportVisCoords(px, py) {
let visX = Math.round((px - 24) / 48);
let visY = Math.round((py - 24) / 48);
let lX = visX; let lY = visY;
if (isRotated) { lX = 8 - lX; lY = 9 - lY; }
if (isFlipped) { lX = 8 - lX; }
return { x: lX, y: lY };
}
// 1. Get step0 for initial comment
let initialComment;
const step0 = svgDoc.getElementById('step0');
if (svgDoc.documentElement.hasAttribute('data-comment')) {
initialComment = b64DecodeUnicode(svgDoc.documentElement.getAttribute('data-comment'));
} else {
initialComment = step0 && step0.getAttribute('data-comment') ? b64DecodeUnicode(step0.getAttribute('data-comment')) : "";
}
// 2. Reconstruct Initial FEN from SVG DOM positions
const etboard = svgDoc.querySelector('.etboard');
const uses = etboard.querySelectorAll('use');
const boardMap = Array(10).fill(null).map(() => Array(9).fill(''));
let firstMovePieceId = null;
const step1 = svgDoc.getElementById('step1');
if (step1 && step1.parentElement) {
firstMovePieceId = step1.parentElement.getAttribute('id');
}
uses.forEach(use => {
const href = use.getAttribute('href') || use.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) {
const transform = use.getAttribute('transform');
const match = transform && transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
if (match) {
const px = parseFloat(match[1]);
const py = parseFloat(match[2]);
// Apply reversal for initial piece layout
const pt = parseExportVisCoords(px, py);
const x = pt.x;
const y = pt.y;
const id = use.getAttribute('id');
if (id && !id.endsWith('f') && x >= 0 && x < 9 && y >= 0 && y < 10) {
boardMap[y][x] = id[0];
}
}
}
});
let initialFenBoard = "";
for (let y = 0; y < 10; y++) {
let emptyCount = 0;
for (let x = 0; x < 9; x++) {
if (boardMap[y][x] === '') {
emptyCount++;
} else {
if (emptyCount > 0) {
initialFenBoard += emptyCount;
emptyCount = 0;
}
initialFenBoard += boardMap[y][x];
}
}
if (emptyCount > 0) initialFenBoard += emptyCount;
if (y < 9) initialFenBoard += "/";
}
let initialTurn = 'w';
if (firstMovePieceId && firstMovePieceId[0] === firstMovePieceId[0].toLowerCase()) {
initialTurn = 'b';
}
const baseFen = initialFenBoard + " " + initialTurn;
let newHistoryFEN = {
fen: baseFen,
move: null,
lastMove: null,
c: initialComment,
v: []
};
let currentNode = newHistoryFEN;
let currentFen = baseFen;
let stepIndex = 1;
loadFEN(baseFen);
// 3. Reconstruct moves by analyzing animateTransform values
while (true) {
const stepAnim = svgDoc.getElementById(`step${stepIndex}`);
if (!stepAnim) break;
const commentStr = stepAnim.getAttribute('data-comment');
const comment = commentStr ? b64DecodeUnicode(commentStr) : "";
const fromStr = stepAnim.getAttribute('from');
const toStr = stepAnim.getAttribute('to');
const pId = stepAnim.parentElement.getAttribute('id');
const fParts = fromStr.split(',');
const tParts = toStr.split(',');
// Apply reversal for animations
const startPt = parseExportVisCoords(parseFloat(fParts[0]), parseFloat(fParts[1]));
const endPt = parseExportVisCoords(parseFloat(tParts[0]), parseFloat(tParts[1]));
const startX = startPt.x;
const startY = startPt.y;
const endX = endPt.x;
const endY = endPt.y;
const capturedId = tileMap.get(`${endX},${endY}`) || null;
const moveNotation = getMoveNotation(pId, startX, startY, endX, endY, capturedId);
const dc = { startX, startY, endX, endY };
const nextFen = simulateMove(currentFen, dc);
tileMap.delete(`${startX},${startY}`);
tileMap.set(`${endX},${endY}`, pId);
let newNode = {
fen: nextFen,
move: moveNotation,
lastMove: dc,
c: comment,
v: []
};
currentNode.v.push(newNode);
currentNode = newNode;
currentFen = nextFen;
stepIndex++;
}
historyFEN = newHistoryFEN;
initBranch();
currentStepIndex = 0;
renderRecordUI();
renderNoteUI();
jumpToStep(0);
saveStateToUndo();
updateToolHighlights();
}
D. Enhanced Text Export UI & Format Toggle
Update renderExportTextUI() to include the engine format logic and the toggle button SVG.
Find function renderExportTextUI() { and replace it entirely with:
JavaScript
function renderExportTextUI() {
const recordContainer = document.querySelector('.ejceesrecord');
const commentDiv = document.querySelector('.ejceescomment');
const btnDiv = document.querySelector('.ejceestextbtn');
const cnText = generateExportText(false);
const enText = generateExportText(true);
// Generate UCCI Engine Format String
let path = getGamePath();
let engineText = "position fen " + path.fen + " moves";
let currNode = path;
while(currNode && currNode.v && currNode.v.length > 0) {
currNode = currNode.v[0];
let dc = currNode.lastMove;
if(dc) {
// Convert to coordinates like a0b0
engineText += " " + String.fromCharCode(97 + dc.startX) + (9 - dc.startY) + String.fromCharCode(97 + dc.endX) + (9 - dc.endY);
}
}
let isEngineFormat = false;
recordContainer.innerHTML = `<textarea class="ejceescomment-edit" id="export-textarea" style="width:100%; height:100%; resize:none; border:none; outline:none; background:#2a2a2a; color:#fff; padding:8px; font-family:monospace; font-size:14px; white-space:pre-wrap; overflow:auto;">${cnText}</textarea>`;
const cnBlob = new Blob([cnText], { type: 'text/plain' });
const enBlob = new Blob([enText], { type: 'text/plain' });
const now = new Date();
const timestamp =
now.getFullYear().toString().padStart(4, '0') +
(now.getMonth() + 1).toString().padStart(2, '0') +
now.getDate().toString().padStart(2, '0') +
now.getHours().toString().padStart(2, '0') +
now.getMinutes().toString().padStart(2, '0') +
now.getSeconds().toString().padStart(2, '0');
// Insert Toggle Button alongside the links
commentDiv.innerHTML = `<div style="display:flex; flex-direction:column; gap:10px; padding:10px;">
<a href="${URL.createObjectURL(cnBlob)}" download="ejcees_fen_${timestamp}.txt" style="color:#66b2ff; text-decoration:none;">fen (${cnBlob.size} bytes)</a>
<a href="${URL.createObjectURL(enBlob)}" download="ejcees_en_fen_${timestamp}.txt" style="color:#66b2ff; text-decoration:none;">en_fen (${enBlob.size} bytes)</a>
<div id="exp-toggle" title="Toggle Format" style="width: 72px; height: 48px; border-radius: 4px; display: flex; justify-content: center; align-items: center; cursor: pointer; background-color: #555; margin-top: 10px;">
<svg viewBox="0 0 24 24" style="width:32px;height:32px;fill:#fff;"><path d="M12 4l-4 4h3v7h2V8h3l-4-4zm0 16l4-4h-3V9h-2v7H8l4 4z"/></svg>
</div>
</div>`;
btnDiv.innerHTML = `
<div class="ejceestextbtninner">
<div class="note-btn btn-confirm" id="exp-confirm" title="Confirm">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
</div>
<div class="note-btn btn-cancel" id="exp-cancel" title="Cancel">
<svg 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>
</div>
</div>
`;
document.getElementById('exp-cancel').addEventListener('click', () => {
isExportTextMode = false;
renderRecordUI();
renderNoteUI();
updateToolHighlights();
});
document.getElementById('exp-confirm').addEventListener('click', () => {
const text = document.getElementById('export-textarea').value;
isExportTextMode = false;
importExportedText(text);
});
document.getElementById('exp-toggle').addEventListener('click', () => {
isEngineFormat = !isEngineFormat;
document.getElementById('export-textarea').value = isEngineFormat ? engineText : cnText;
});
}
E. New Virtual Map Helper and Enhanced Text Parsing Logic
Add a helper function to create logic maps during FEN manipulation, and rewrite importExportedText() to fulfill the branch matching/attachment rules and engine format parsing.
Find function importExportedText(text) { and replace it and its contents entirely with:
JavaScript
// Helper to evaluate notations without touching the actual DOM layout
function buildVirtualMap(fen) {
const map = new Map();
const counts = {};
const rows = fen.split(' ')[0].split('/');
rows.forEach((row, y) => {
let x = 0;
for (let i = 0; i < row.length; i++) {
const char = row[i];
if (/[0-9]/.test(char)) x += parseInt(char, 10);
else {
counts[char] = counts[char] || 0;
map.set(`${x},${y}`, `${char}${counts[char]++}`);
x++;
}
}
});
return map;
}
function importExportedText(text) {
text = text.trim();
if (!text) {
renderRecordUI();
renderNoteUI();
updateToolHighlights();
return;
}
// Normalize Chinese notations
text = text.replace(/车/g, '車').replace(/马/g, '馬').replace(/帅/g, '帥').replace(/将/g, '將').replace(/后/g, '後').replace(/进/g, '進');
text = text.replace(/ r(\n|\s|$)/g, ' w$1');
// Handle Direct JSON structure
if (text.startsWith('{')) {
try {
const data = JSON.parse(text);
function expand(node, parentFen) {
let dc, simfen, newNode;
if (node.m) {
dc = deriveCoordsFromMove(parentFen, node.m);
simfen = simulateMove(parentFen, dc);
newNode = { fen: simfen, move: node.m, lastMove: dc, c: node.c || "", v: [] };
} else {
newNode = { fen: parentFen, c: node.c || "", v: [] };
}
if (node.v) { newNode.v = node.v.map(child => expand(child, newNode.fen)); }
return newNode;
}
historyFEN = expand(data, data.fen);
initBranch();
currentStepIndex = 0;
renderRecordUI();
renderNoteUI();
jumpToStep(0);
saveStateToUndo();
updateToolHighlights();
return;
} catch (e) {
console.log("JSON parse failed, processing as text notation.");
}
}
// 1. Locate explicitly provided FEN if present
let importedFen = null;
const fenMatch = text.match(/(?:position fen )?([a-zA-Z0-9/]+ [wb])/);
let movesStr = text;
if (fenMatch) {
importedFen = fenMatch[1];
// Strip the FEN string declaration so it isn't parsed as moves later
movesStr = movesStr.replace(/position fen [a-zA-Z0-9/]+ [wb](\s+moves)?/, '');
if (movesStr === text) {
movesStr = movesStr.replace(fenMatch[1], '');
}
}
// 2. Identify the Attach Point (Parent Node)
let attachIndex = currentStepIndex;
let attachNode = null;
if (importedFen) {
// If FEN exists, trace backwards to find a matching state
for (let i = currentStepIndex; i >= 0; i--) {
const node = getNodeAtStep(i);
if (node && node.fen === importedFen) {
attachIndex = i;
attachNode = node;
break;
}
}
// No match down to root -> Overwrite everything
if (!attachNode) {
attachIndex = 0;
attachNode = { fen: importedFen, move: null, lastMove: null, c: "", v: [] };
historyFEN = attachNode;
currentBranch = [];
}
} else {
// No FEN -> Default attach point is the current active step
attachNode = getNodeAtStep(attachIndex);
importedFen = attachNode.fen;
}
// 3. Process move sequence text
// Remove line numbers (e.g., "1. " or " 23. ") and ellipsis "..."
movesStr = movesStr.replace(/^\d+\.\s*/gm, ' ').replace(/\s\d+\.\s/g, ' ').replace(/\.\.\./g, ' ');
let tokens = movesStr.split(/\s+/).filter(t => t.length > 0 && t !== 'moves');
let currentFen = importedFen;
let vMap = buildVirtualMap(currentFen);
let currNode = attachNode;
let attachDepth = attachIndex;
// Truncate currentBranch up to the attachIndex to prepare for the new branch selection
currentBranch.length = attachDepth;
let hasError = false;
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
let dc = null;
let moveEn = "";
let isRed = currentFen.includes(' w');
// Check for UCCI Engine Coordinate Format (e.g., "a0b0" or "h7e7")
if (/^[a-i][0-9][a-i][0-9]$/.test(token)) {
let startX = token.charCodeAt(0) - 97;
let startY = 9 - parseInt(token.charAt(1), 10);
let endX = token.charCodeAt(2) - 97;
let endY = 9 - parseInt(token.charAt(3), 10);
dc = { startX, startY, endX, endY };
let pId = vMap.get(`${startX},${startY}`);
if (!pId) { hasError = true; break; }
// Generate UI notation dynamically from coordinates
moveEn = getMoveNotation(pId, startX, startY, endX, endY, null, vMap);
} else {
// Standard English or Chinese notation
let isEnglish = /^[a-zA-Z]/.test(token) || /^[+\-=1-9][a-zA-Z0-9]/.test(token);
moveEn = isEnglish ? token : NotationConverter.toEnglish(token, isRed);
dc = deriveCoordsFromMove(currentFen, moveEn, isRed);
if (!dc) { hasError = true; break; }
}
let nextFen = simulateMove(currentFen, dc);
// Check if this move already exists as a branch from the current node
let childIdx = currNode.v.findIndex(c => c.move === moveEn && c.fen === nextFen);
if (childIdx === -1) {
// Create new branch
let newNode = { fen: nextFen, move: moveEn, lastMove: dc, c: "", v: [] };
currNode.v.push(newNode);
childIdx = currNode.v.length - 1;
}
// Force selection onto this path
currentBranch[attachDepth] = childIdx;
currNode = currNode.v[childIdx];
currentFen = nextFen;
attachDepth++;
// Update virtual map for next iterations
let pId = vMap.get(`${dc.startX},${dc.startY}`);
vMap.delete(`${dc.startX},${dc.startY}`);
if (pId) vMap.set(`${dc.endX},${dc.endY}`, pId);
}
if (hasError && tokens.length > 0) {
alert("Parsed partially due to an invalid move notation.");
}
// Pad currentBranch with 0s if the newly attached path has existing single children further down
while (currNode && currNode.v && currNode.v.length > 0) {
currentBranch[attachDepth] = 0;
currNode = currNode.v[0];
attachDepth++;
}
// Jump to the parent node of the newly added branch (attach point)
currentStepIndex = attachIndex;
renderRecordUI();
renderNoteUI();
jumpToStep(currentStepIndex);
saveStateToUndo();
updateToolHighlights();
}
Gemini isn’t human. It can make mistakes, so double-check it.
- 附件
-
geminiEnhancingRecor.7z- (72.37 KiB) 已下载 61 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
Conversation with Gemini
You said
中國象棋局面fen字符串後面的- - 0 1是什麼意義?
Gemini said
在中国象棋的 FEN (Forsyth-Edwards Notation) 格式中,局面字符串通常由六个部分组成,中间用空格隔开。你提到的 - - 0 1 对应的是 FEN 字串的最后四个字段。
虽然中国象棋的 FEN 标准在不同的软件(如象棋巫师、XQBoard)中可能略有差异,但通用的含义如下:
1. 第一个 -:王著(或者特定规则下的标记)
在标准的国际象棋 FEN 中,这里是易位权限。但在中国象棋中:
由于中国象棋没有“易位”,这个位置通常用 - 占位。
在某些变体规则中,它可能用来记录某些特殊权限,但 99% 的情况下你看到的都是 -。
2. 第二个 -:吃过路兵位置
这也是从国际象棋 FEN 格式继承过来的字段。
中国象棋没有“吃过路兵”的规则,所以这里固定为 -。
3. 数字 0:半回合数 (Half-move Clock)
这个数字记录了自上一次“吃子”或“进兵”以来,双方合计走了多少步(每个单步算 1)。
用途: 主要用于判断“和棋”。例如,在某些规则下,如果连续 60 步(或双方各 60 回合,视具体规则而定)没有任何吃子或兵卒移动,可以判和。
重置: 只要场上有任何棋子被吃,或者有兵(卒)向前推进,这个计数器就会归零。
4. 数字 1:全回合数 (Full-move Number)
这个数字表示当前是第几个回合。
规则: 游戏开始时为 1。
增加: 每当黑方走完一步棋,这个数字就会增加 1(即红黑各走一招,算一个完整回合)。
总结举例
一个典型的中国象棋 FEN 可能长这样:
rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1
字段 示例内容 含义
棋盘布局 rnbak... 从第 10 路到第 1 路的棋子分布
轮到谁走 w (或 r) w 代表红方(先手),b 代表黑方
易位/特殊 - 无意义,占位符
过路兵 - 无意义,占位符
半回合数 0 距离上次吃子或动兵的单步数
全回合数 1 当前比赛进行的总回合数
如果你是在编写程序或处理棋谱,通常只需要关注 局面布局 和 轮到谁走,后面的四个参数在大多数推演中可以默认设为 0 1(如果不需要严格判断长打或和棋规则的话)。
需要我帮你解析一个具体的 FEN 字符串,或者将其转换成棋盘吗?
You said
如果易位或吃了過路兵,要怎麼記?
Gemini said
这是一个非常敏锐的问题!简单直接的回答是:在中国象棋(Xiangqi)的 FEN 标准中,由于规则本身不包含“易位”和“吃过路兵”,这两个字段永远记为 -。
如果你在处理 FEN 字符串时,想要强行记录这两项,通常是因为你在处理国际象棋(Chess),或者你正在设计一种结合了这两项规则的象棋变体。
以下是按照国际标准(主要参考国际象棋 FEN)记录这两个字段的方法:
1. 易位權限 (Castling Availability)
在国际象棋中,这个字段用来记录王和车是否还有权利进行易位。
记录规则: 使用字母缩写。
K: 白方王翼易位(短易位)
Q: 白方后翼易位(长易位)
k: 黑方王翼易位
q: 黑方后翼易位
记录方式:
如果双方都有双向易位权,记为 KQkq。
如果只有白方可以短易位,黑方可以长易位,记为 Kq。
如果没有任何易位权(即你看到的中国象棋情况),记为 -。
2. 吃過路兵位置 (En Passant Target Square)
当一方的兵(Pawn)第一次移动两格时,它“跳过”的那个格子就是“过路兵目标格”。
记录规则: 使用棋盘坐标(例如 e3 或 c6)。
记录方式:
如果上一回合白方把兵从 e2 走到了 e4,那么黑方在下一招可以吃掉它,此时 FEN 记为 e3(即兵跳过的位置)。
如果上一招没有出现兵走两格的情况,或者该规则不适用,则记为 -。
為什麼中國象棋總是 - -?
中国象棋的规则决定了这两个字段没有发挥空间:
没有易位: 帅(将)和仕(士)只能在九宫格内移动,没有类似国际象棋中王与车交换位置的动作。
兵(卒)只能走一格: 中国象棋的兵在过河前和过河后,每次都只能向前或横向走一格。既然没有“首步走两格”的规则,自然也就没有“吃过路兵”的情况。
總結
即使你使用的是支持多种棋类的引擎(如 Stockfish),当你切换到中国象棋模式时:
易位字段: 永远是 -。
过路兵字段: 永远是 -。
如果你在尝试编写一个支持这些规则的自定义象棋游戏,你可以借用国际象棋的记法。例如,若红方可以“易位”,你可以用大写字母 R 表示。但请注意,这就不再是标准的中国象棋 FEN 了。
你想了解如何将这些规则融合进象棋变体的编程实现吗?
You said
如果我把這兩個-符號改為0或1,用於記錄中國象棋當前盤面是否旋轉或鏡像,是否可行?
Gemini said
从技术实现的角度来看,这完全可行,但从标准兼容性的角度来看,这属于自定义扩展(Private Extension)。
在编程和数据存储中,你可以自由定义 FEN 字符串的含义,只要你的解析器(Parser)和渲染器(Renderer)能够理解这些规则即可。
以下是关于这种做法的详细评估和建议:
1. 为什么这在技术上是可行的?
FEN 本质上是一个以空格分隔的字符串。解析引擎通常会按空格拆分:
第 1-2 段(布局和走子方)是核心,几乎所有引擎都会读取。
第 3-6 段(包括你提到的两个 -)在很多简单的中国象棋程序中甚至会被直接忽略。
如果你将其改为 0 或 1:
记录旋转:0 代表红方在下,1 代表黑方在下(视角翻转)。
记录镜像:0 代表原始,1 代表左右镜像。
这种做法非常节省空间,比单独开一个 JSON 字段记录“是否旋转”要紧凑得多。
2. 潜在的兼容性风险(副作用)
如果你打算让你的 FEN 字符串与其他主流象棋软件(如象棋巫师、东萍棋路、或者是 Stockfish/Lichess 的象棋变体)交互,可能会遇到以下问题:
解析错误:某些严谨的解析器会检查第三、四位是否为 -。如果发现是数字,可能会报错,或者误以为这是某种奇怪的国际象棋变体。
信息丢失:如果你把带 1 的 FEN 导入到标准软件中,标准软件会忽略这个信息。当你再次从该软件导出时,你的“旋转/镜像”信息可能会被重新重置为 -。
3. 更专业的建议:使用 "Extended FEN"
如果你希望在不破坏标准的前提下记录额外信息,通常有两种更推荐的做法:
方案 A:在末尾追加(推荐)
不要修改中间的 - -,保持其占位符的功能,而是在 FEN 的最后增加第七个字段。
... w - - 0 1 <rotate_flag> <mirror_flag>
例如:rnbakabnr/... w - - 0 1 1 0(末尾的 1 0 代表旋转但不镜像)
方案 B:利用“轮到谁走”字段
中国象棋的 FEN 中,走子方通常是 w(红)或 b(黑)。有些开发者会使用大写/小写以外的标记,但这种改动较大,不建议使用。
4. 总结与实施建议
方案 优点 缺点
直接改 - 为数字 最直接,字符串长度不变。 破坏了 FEN 格式的语义(占位符变成了数据)。
在末尾添加新字段 扩展性强,不会干扰前六项的逻辑。 字符串变长,非自定义程序可能无法识别。
我的建议:
如果你这个 FEN 只是在你自己开发的系统内部流动(比如你的前端和后端之间),直接改 - 为 0/1 是非常聪明的做法,因为这利用了死字段。
你会如何定义这两个数字? 例如:
第三位:0=不旋转,1=旋转 180°
第四位:0=不镜像,1=左右镜像
需要我帮你写一段 Python 或 JavaScript 代码,用来解析这种带自定义标记的 FEN 字符串吗?
Gemini isn’t human. It can make mistakes, so double-check it.
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
導入之後,並未切換分支,其餘已經做好:
- 附件
-
ejcees20260322aabb99945.7z- (36.47 KiB) 已下载 60 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 2 访客
