將製作ejcees(中國象棋打譜程式)
Re: 將製作ejcees(中國象棋打譜程式)
解決pgn格式對於C4-C3的導入問題,解決Ecs不能退出兩個「pgn設置窗口」和「導出svg窗口」的問題。
- 附件
-
ejceesobj202604291303.js- (298.43 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
這裡有WXF格式示例:
代码: 全选
[Game "Chinese Chess"]
[FEN "5k3/9/3P2P2/3P2P2/3P5/9/9/9/9/3K5 b - - 0 1"]
[Format "WXF"]
1. K6.5
2. Pc+1 K5.6
3. Pa+1 K6.5
4. Pd.7 K5.6
5. P7.6 K6.5
6. Pd.5 K5.6
7. Pd.5 K6.5
8. Pd.6 K5.6
9. P5.6 K6.5
10. Pc.7 K5.6
11. P7.6 K6.5
12. Pd+1 K5.6
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
改進:
1.「this.historyFEN」的move將改為「engine format」,即把「C2=5」改為「h2e2」。去掉lastMove,因為move已經是在描述棋子移動的起始和終點坐標。
2.為「this.historyFEN」的根節點加上r和f,表示rotate和flip,值為0或1。當this.isRotateEnabled和this.isFlipEnabled發生改變時,將同時改變「this.historyFEN」的根節點的r和f值。取消改變fen後面的「- -」。
3.在保存JSON格式時,如果是選取範圍,則需要為這個範圍的根節點加上r和f屬性。
4.在導出文本時,把PGN格式移至第一位,跟DP格式交換位置。如果this.isRotateEnabled或this.isFlipEnabled為true,則加上標籤[rotate "true"]或[flip "true"]。
5.導出文本的核心邏輯將要重寫。
把engine format轉換為「PGN 中文」的邏輯改為:
如果有兩個兵在同一列,其餘的兵都不在同一列,則這兩個兵將稱為「前兵」和「後兵」;
如果有三個兵在同一列,其餘的兵都不在同一列,則這三個兵將稱為「前兵」、「中兵」和「後兵」;
如果有四五個兵在同一列,則從前到後稱它們為「一兵、二兵、三兵、四兵、五兵」;
如果有兩三個兵在同一列,另兩三個兵也同在另一列,則從最右列的前面開始數,數完再到左列,稱它們為「一兵、二兵、三兵、四兵、五兵」。
「PGN WXF」的邏輯將改為:
在中文的基礎上,把「車馬炮相士帥兵」都轉為英文字母「RNCBAKP」,紅方和黑方都是大寫字母。「進平退」轉成「+.-」。如果有「前馬、前炮、前兵」等,代表棋子的字母始終在第一位,把「前中後」換成「+.-」,比如「前車進一」轉成「R++1」。當存在「一兵、二兵、三兵、四兵、五兵」時,將把「一二三四五」轉成「abcde」,P始終在第一位,比如「三兵平一」轉成「Pc.1」。
「PGN ICCS」的邏輯不變,即把engine format的字母變大寫,再在起始和終點坐標之間加上「-」連接,比如「h2e2」轉成「H2-e2」。
其餘三種格式的邏輯不變,但是注意現在是engine format為this.historyFEN的核心,所以邏輯都要重寫。
6.在ejceesrecord中顯示的著法將是PGN中文,而不再是DP format。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。- 附件
-
ejceesobj202604300954.js- (299.52 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
Here are the required modifications to implement the new engine format, rotate/flip root logic, text export features, and the updated PGN Chinese/WXF notations.
### 1. Update `constructor` (Initialization and FEN format)
Modify `this.INITIAL_FEN` and add the default export mode:
```javascript
// In constructor()
this.INITIAL_FEN = 'rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w 0 1'; // Removed "- -"
this.pieceToHrefMap = { ... }; // Unchanged
// ...
this.historyFEN = {
fen: this.INITIAL_FEN,
move: null,
r: 0,
f: 0,
c: '',
v: []
};
// Update default export format to PGN
this.lastExportFormat = 'pgn';
```
### 2. Add New Coordinate Helper Methods & Notation Engine
Add these new methods to the `Ejcees` class. You can completely delete the old `getMoveNotation` method, as `getNotation` replaces it:
```javascript
engineToCoords(engineMove) {
if (!engineMove || engineMove.length !== 4) return null;
return {
startX: engineMove.charCodeAt(0) - 97,
startY: 9 - parseInt(engineMove.charAt(1), 10),
endX: engineMove.charCodeAt(2) - 97,
endY: 9 - parseInt(engineMove.charAt(3), 10)
};
}
coordsToEngine(startX, startY, endX, endY) {
return String.fromCharCode(97 + startX) + (9 - startY) +
String.fromCharCode(97 + endX) + (9 - endY);
}
getNotation(fen, engineMove, format) {
if (format === 'iccs') {
return engineMove.substring(0, 2).toUpperCase() + '-' + engineMove.substring(2, 4).toLowerCase();
}
let coords = this.engineToCoords(engineMove);
if (!coords) return engineMove;
let { startX, startY, endX, endY } = coords;
let vMap = this.buildVirtualMap(fen);
let pId = vMap.get(`${startX},${startY}`);
if (!pId) return engineMove;
let char = pId[0];
let isRed = char === char.toUpperCase();
let isPawn = char.toLowerCase() === 'p';
let samePieces = [];
for (let [pos, id] of vMap.entries()) {
if (id[0] === char) {
let [x, y] = pos.split(',').map(Number);
samePieces.push({x, y});
}
}
let prefixIndex = -1; // -1: none, 0: Front, 1: Middle, 2: Back, 11-15: 1st-5th
let colPieces = samePieces.filter(p => p.x === startX);
let multiCols = [];
if (isPawn) {
for (let c = 0; c <= 8; c++) {
if (samePieces.filter(p => p.x === c).length > 1) multiCols.push(c);
}
if (multiCols.length > 1) {
let allMultiPawns = [];
for (let c of multiCols) allMultiPawns.push(...samePieces.filter(p => p.x === c));
allMultiPawns.sort((a, b) => {
if (isRed) {
if (b.x !== a.x) return b.x - a.x;
return a.y - b.y;
} else {
if (a.x !== b.x) return a.x - b.x;
return b.y - a.y;
}
});
prefixIndex = 11 + allMultiPawns.findIndex(p => p.x === startX && p.y === startY);
} else if (colPieces.length >= 4) {
colPieces.sort((a, b) => isRed ? a.y - b.y : b.y - a.y);
prefixIndex = 11 + colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 3) {
colPieces.sort((a, b) => isRed ? a.y - b.y : b.y - a.y);
prefixIndex = colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 2) {
colPieces.sort((a, b) => isRed ? a.y - b.y : b.y - a.y);
prefixIndex = colPieces.findIndex(p => p.y === startY) === 0 ? 0 : 2;
}
} else if (colPieces.length > 1 && !['a','A','b','B'].includes(char)) {
colPieces.sort((a, b) => isRed ? a.y - b.y : b.y - a.y);
if (colPieces.length === 3) {
prefixIndex = colPieces.findIndex(p => p.y === startY);
} else {
prefixIndex = colPieces.findIndex(p => p.y === startY) === 0 ? 0 : 2;
}
}
let isAdvancing = isRed ? startY > endY : startY < endY;
let isWXF = format === 'wxf';
let actionStr = startY === endY ? (isWXF ? '.' : '平') : (isAdvancing ? (isWXF ? '+' : '進') : (isWXF ? '-' : '退'));
let startFile = isRed ? 9 - startX : startX + 1;
let endFile = isRed ? 9 - endX : endX + 1;
let valStr;
if (startY === endY) {
valStr = isWXF ? endFile.toString() : this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][endFile];
} else {
let isStraight = !['n','N','b','B','a','A'].includes(char);
let valNum = isStraight ? Math.abs(startY - endY) : endFile;
valStr = isWXF ? valNum.toString() : this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][valNum];
}
let pieceStr = isWXF ? char.toUpperCase() : this.NotationConverter[isRed ? 'cnPieceRed' : 'cnPieceBlk'][char.toUpperCase()];
if (isWXF) {
let wxfPrefix = '';
if (prefixIndex === 0) wxfPrefix = '+';
else if (prefixIndex === 1) wxfPrefix = '=';
else if (prefixIndex === 2) wxfPrefix = '-';
else if (prefixIndex >= 11) wxfPrefix = String.fromCharCode(97 + prefixIndex - 11);
if (wxfPrefix) return pieceStr + wxfPrefix + actionStr + valStr;
return pieceStr + startFile + actionStr + valStr;
} else {
let cnPrefix = '';
if (prefixIndex === 0) cnPrefix = '前';
else if (prefixIndex === 1) cnPrefix = '中';
else if (prefixIndex === 2) cnPrefix = '後';
else if (prefixIndex >= 11) cnPrefix = ["一","二","三","四","五"][prefixIndex - 11];
if (cnPrefix) return cnPrefix + (prefixIndex >= 11 ? "兵" : pieceStr) + actionStr + valStr;
return pieceStr + this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][startFile] + actionStr + valStr;
}
}
```
### 3. Update `boardToFEN` & History Pushing
Modify `boardToFEN` to remove `- -` logic, and update `pushHistory` to use the engine format.
```javascript
boardToFEN() {
let fen = '';
for (let y = 0; y <= 9; y++) {
let emptyCount = 0;
for (let x = 0; x <= 8; x++) {
const pieceId = this.tileMap.get(`${x},${y}`);
const pieceEl = this.container.querySelector('#' + pieceId);
if (pieceId && pieceEl) {
const href = pieceEl.getAttribute('href') || pieceEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) {
if (emptyCount > 0) {
fen += emptyCount;
emptyCount = 0;
}
fen += pieceId[0];
} else {
emptyCount++;
}
} else {
emptyCount++;
}
}
if (emptyCount > 0) fen += emptyCount;
if (y < 9) fen += '/';
}
fen += ` ${this.currentTurn}`;
fen += ` ${this.halfMoveClock}`;
fen += ` ${this.fullMoveNumber}`;
return fen;
}
pushHistory(engineMove) {
if (!engineMove) return;
const newFEN = this.boardToFEN();
const parentNode = this.getNodeAtStep(this.currentStepIndex);
if (parentNode) {
let branchIndex = parentNode.v.findIndex(child => child.move === engineMove && child.fen === newFEN);
if (branchIndex === -1) {
parentNode.v.push({
fen: newFEN,
move: engineMove,
c: '',
v: []
});
branchIndex = parentNode.v.length - 1;
}
if (parentNode.v.length > 1) {
let forkCount = 0;
let curr = this.historyFEN;
for (let i = 0; i < this.currentStepIndex; i++) {
if (curr.v && curr.v.length > 1) forkCount++;
const choice = curr.v.length > 1 ? this.currentBranch[forkCount - 1] || 0 : 0;
curr = curr.v[choice];
}
this.updateBranchPath(forkCount, branchIndex);
}
}
this.currentStepIndex++;
const totalDepth = this.getPathDepth(this.historyFEN, this.currentBranch);
this.stepSlider.max = Math.max(0, totalDepth - 1);
this.stepSlider.value = this.currentStepIndex;
this.renderRecordUI();
const cnMove = this.getNotation(parentNode.fen, engineMove, 'cn');
const outputElement = this.container.querySelector('.ejceesoutput');
outputElement.textContent += ' | ' + cnMove;
this.renderNoteUI();
this.saveStateToUndo();
}
```
Update the final callback inside `movePieceWithAnimation` (replace `const moveText = this.getMoveNotation(...)` logic):
```javascript
// Inside movePieceWithAnimation completion block...
// Update logical data only
const moveData = {
startX: startLogical.x,
startY: startLogical.y,
endX: targetLogicalX,
endY: targetLogicalY
};
const engineMove = this.coordsToEngine(startLogical.x, startLogical.y, targetLogicalX, targetLogicalY);
this.tileMap.delete(`${startLogical.x},${startLogical.y}`);
this.tileMap.set(`${targetLogicalX},${targetLogicalY}`, pieceId);
this.piecePos.set(pieceId, { x: targetLogicalX, y: targetLogicalY });
// ...
if (onComplete) {
onComplete();
} else {
if (eatenId) {
this.halfMoveClock = 0;
} else {
this.halfMoveClock++;
}
if (this.currentTurn === 'b') {
this.fullMoveNumber++;
}
this.currentTurn = this.currentTurn === 'w' ? 'b' : 'w';
this.pushHistory(engineMove);
this.applyLastMoveVisuals(moveData);
this.restoreOutputText();
// ... Checkmate detection ...
```
### 4. Replace `lastMove` dependencies
Update `applyLastMoveVisuals`, `restoreOutputText`, and loop logic:
```javascript
// Inside restoreOutputText()
const node = this.getNodeAtStep(this.currentStepIndex);
if (node && node.move) {
promptText = ' ' + node.move;
if (this.isAutoPlaying) {
promptText = '(Auto-playing) ' + promptText;
}
} else {
// ...
// Update applyLastMoveVisuals signature to parse node.move
applyLastMoveVisuals(moveDataOrEngineMove) {
// Determine if it's already coordinate data or an engine string
let lastMove = moveDataOrEngineMove;
if (typeof lastMove === 'string') {
lastMove = this.engineToCoords(lastMove);
}
// ...
// In getGamePath(limitToCurrentStep = false)
let newHistory = {
fen: this.historyFEN.fen,
move: null,
r: this.historyFEN.r,
f: this.historyFEN.f,
c: this.historyFEN.c,
v: []
};
// ...
```
### 5. Rotation/Flip Actions & Save Logic
```javascript
// In bindEvents() update tool-rotate
this.container.querySelector('#tool-rotate').addEventListener('click', () => {
if (this.isExportTextMode || this.isAutoPlaying) return;
this.isRotateEnabled = !this.isRotateEnabled;
if (this.historyFEN) this.historyFEN.r = this.isRotateEnabled ? 1 : 0;
this.reapplyVisualPositions();
if (this.isEditMode) this.updateMiniBoard();
this.saveStateToUndo();
this.updateToolHighlights();
});
// In bindEvents() update tool-flip
this.container.querySelector('#tool-flip').addEventListener('click', () => {
if (this.isExportTextMode || this.isAutoPlaying) return;
this.isFlipEnabled = !this.isFlipEnabled;
if (this.historyFEN) this.historyFEN.f = this.isFlipEnabled ? 1 : 0;
this.reapplyVisualPositions();
this.saveStateToUndo();
this.updateToolHighlights();
});
// In tool-save logic
let copy;
if (this.isRangeMode) {
let rangeHistory = this.getGamePath();
copy = simplify(rangeHistory);
copy.fen = rangeHistory.fen;
copy.r = this.isRotateEnabled ? 1 : 0;
copy.f = this.isFlipEnabled ? 1 : 0;
// ...
} else {
copy = simplify(this.historyFEN);
copy.fen = this.historyFEN.fen;
copy.r = this.historyFEN.r || 0;
copy.f = this.historyFEN.f || 0;
}
```
### 6. Replace Export UI & Formatting Logic
Update `renderExportTextUI`:
```javascript
// In renderExportTextUI
commentDiv.innerHTML = `<div class="exp-fmt-outer">
<div class="exp-fmt-btn ${this.lastExportFormat === 'pgn' ? 'active' : ''}" data-fmt="pgn" data-odd="${this.formatOddStates['pgn']}">PGN Format</div>
<div class="exp-fmt-btn ${this.lastExportFormat === 'cn' ? 'active' : ''}" data-fmt="cn" data-odd="${this.formatOddStates['cn']}">DP Format</div>
<div class="exp-fmt-btn ${this.lastExportFormat === 'engine' ? 'active' : ''}" data-fmt="engine" data-odd="${this.formatOddStates['engine']}">Engine Format</div>
<div class="exp-fmt-btn ${this.lastExportFormat === 'url' ? 'active' : ''}" data-fmt="url" data-odd="${this.formatOddStates['url']}">URL Parameter</div>
</div>`;
```
Rewrite Text Generators to use `getNotation`:
```javascript
generateTextWithComment(isEnglish = false) {
const pathRoot = this.getGamePath();
const fen = pathRoot.fen;
let text = pathRoot.fen !== this.INITIAL_FEN ? pathRoot.fen + '\n' : '';
if (pathRoot.c && pathRoot.c.trim() !== '') text += pathRoot.c.trim() + '\n';
let isRedTurn = fen.split(' ')[1] === 'w';
let moveNum = 1;
let curr = pathRoot;
let needsEllipsis = !isRedTurn;
let currentFen = fen;
while (curr.v && curr.v.length > 0) {
let nextNode = curr.v[0];
const moveStr = this.getNotation(currentFen, nextNode.move, 'cn');
if (isRedTurn) {
text += `${moveNum}. ${moveStr}`;
if (nextNode.c && nextNode.c.trim() !== '') {
text += `\n${nextNode.c.trim()}\n`;
needsEllipsis = true;
} else {
text += ' ';
needsEllipsis = false;
}
isRedTurn = false;
} else {
if (needsEllipsis) text += `${moveNum}. ... ${moveStr}\n`;
else text += `${moveStr}\n`;
if (nextNode.c && nextNode.c.trim() !== '') text += `${nextNode.c.trim()}\n`;
moveNum++;
isRedTurn = true;
needsEllipsis = false;
}
currentFen = nextNode.fen;
curr = nextNode;
}
return text.trim();
}
generateExportText(isEnglish) {
let path = this.getGamePath();
let fen = path.fen;
let text = fen !== this.INITIAL_FEN ? fen + '\n' : '';
let isRedTurn = fen.split(' ')[1] === 'w';
let moveNum = 1;
let currentFen = fen;
while (path.v.length) {
let line = `${moveNum}. `;
if (!isRedTurn) {
let nextNode = path.v[0];
line += `... ${this.getNotation(currentFen, nextNode.move, 'cn')}`;
currentFen = nextNode.fen;
path = nextNode;
isRedTurn = true;
} else {
let nextNode = path.v[0];
line += `${this.getNotation(currentFen, nextNode.move, 'cn')}`;
currentFen = nextNode.fen;
path = nextNode;
if (path.v.length) {
let blkNode = path.v[0];
line += ` ${this.getNotation(currentFen, blkNode.move, 'cn')}`;
currentFen = blkNode.fen;
path = blkNode;
}
}
text += line + '\n';
moveNum++;
}
return text.trim();
}
generateDPFormat(isEnglish = false) {
let text = this.historyFEN.fen !== this.INITIAL_FEN ? this.historyFEN.fen + '\n' : '';
if (this.historyFEN.c && this.historyFEN.c.trim() !== '') text += this.historyFEN.c.trim() + '\n';
let branchQueue = [];
let branchCounter = 1;
const getMarker = num => num <= 20 ? String.fromCharCode(0x245f + num) : `(${num})`;
const processSequence = (startNode, absDepth, moveNum, isRed, pathArr, skipFirstQueue) => {
let seqText = '';
let curr = startNode;
let lineBuffer = '';
let isFirstIteration = true;
let currentFen = curr.fen;
while (curr.v && curr.v.length > 0) {
let choiceIdx = (pathArr && pathArr[absDepth] !== undefined) ? pathArr[absDepth] : 0;
if (choiceIdx >= curr.v.length) choiceIdx = 0;
let nextNode = curr.v[choiceIdx];
let moveStr = this.getNotation(currentFen, nextNode.move, 'cn');
let markers = '';
if (!(isFirstIteration && skipFirstQueue)) {
for (let i = 0; i < curr.v.length; i++) {
if (i !== choiceIdx) {
let marker = getMarker(branchCounter++);
markers += ' ' + marker;
branchQueue.push({ marker: marker, parentNode: curr, choiceIdx: i, moveNum: moveNum, isRedTurn: isRed, absDepth: absDepth });
}
}
}
isFirstIteration = false;
if (isRed) {
if (lineBuffer === '') lineBuffer = `${moveNum}. `;
lineBuffer += `${moveStr}${markers}`;
if (nextNode.c && nextNode.c.trim() !== '') {
seqText += lineBuffer + '\n' + nextNode.c.trim() + '\n';
lineBuffer = '';
isRed = false;
} else {
isRed = false;
}
} else {
if (lineBuffer === '') lineBuffer = `${moveNum}. ………… `;
else lineBuffer += ' ';
lineBuffer += `${moveStr}${markers}`;
seqText += lineBuffer + '\n';
if (nextNode.c && nextNode.c.trim() !== '') seqText += nextNode.c.trim() + '\n';
lineBuffer = '';
isRed = true;
moveNum++;
}
currentFen = nextNode.fen;
curr = nextNode;
absDepth++;
}
if (lineBuffer !== '') seqText += lineBuffer + '\n';
return seqText.trim();
};
let mainIsRed = this.historyFEN.fen.split(' ')[1] === 'w';
text += processSequence(this.historyFEN, 0, 1, mainIsRed, this.currentBranch, false);
while (branchQueue.length > 0) {
let b = branchQueue.shift();
text += `\n\n${b.marker}\n`;
let branchPath = Array(b.absDepth).fill(0);
branchPath.push(b.choiceIdx);
text += processSequence(b.parentNode, b.absDepth, b.moveNum, b.isRedTurn, branchPath, true);
}
return text;
}
generatePGNFormat(oddState) {
let text = '[Game "Chinese Chess"]\n';
let userResult = '';
if (this.historyFEN.r === 1 || this.isRotateEnabled) text += `[Rotate "true"]\n`;
if (this.historyFEN.f === 1 || this.isFlipEnabled) text += `[Flip "true"]\n`;
this.pgnMetadata.forEach(meta => {
let val = meta.value ? meta.value.trim() : '';
let isResultEmpty = meta.name === 'Result' && val === '*';
if (val !== '' && !isResultEmpty) {
text += `[${meta.name} "${val}"]\n`;
if (meta.name === 'Result') userResult = val;
}
});
if (this.historyFEN.fen.split(' ')[0] !== this.INITIAL_FEN.split(' ')[0]) {
text += `[FEN "${this.historyFEN.fen}"]\n`;
}
const formatMap = { 0: 'Chinese', 1: 'ICCS', 2: 'WXF' };
text += `[Format "${formatMap[oddState]}"]\n\n`;
if (this.historyFEN.c && this.historyFEN.c.trim() !== '') text += `{${this.historyFEN.c.trim()}}\n`;
let finalTerminator = userResult !== '' ? userResult : '*';
const getMoveStr = (node, parentFen) => {
if (oddState === 0) return this.getNotation(parentFen, node.move, 'cn');
if (oddState === 2) return this.getNotation(parentFen, node.move, 'wxf');
if (oddState === 1) return this.getNotation(parentFen, node.move, 'iccs');
return node.move;
};
const processPGNSequence = (startNode, moveNum, isRed) => {
let seqText = '';
let curr = startNode;
let currentFen = curr.fen;
while (curr.v && curr.v.length > 0) {
let nextNode = curr.v[0];
let moveStr = getMoveStr(nextNode, currentFen);
let currentMoveText = isRed ? `${moveNum}. ${moveStr}` : (curr === startNode ? `${moveNum}. ... ${moveStr}\n` : `${moveStr}\n`);
seqText += (seqText.length > 0 && !seqText.endsWith('\n') && !isRed ? ' ' : '') + currentMoveText;
if (nextNode.c && nextNode.c.trim() !== '') seqText += `{${nextNode.c.trim()}}\n`;
if (curr.v.length > 1) {
for (let i = 1; i < curr.v.length; i++) {
let bNode = curr.v[i];
let bMoveStr = getMoveStr(bNode, currentFen);
let innerText = isRed ? `${moveNum}. ${bMoveStr}` : `${moveNum}. ... ${bMoveStr}`;
if (bNode.c && bNode.c.trim() !== '') innerText += `\n{${bNode.c.trim()}}`;
let subBranch = processPGNSequence(bNode, isRed ? moveNum : moveNum + 1, !isRed);
if (subBranch) innerText += (innerText.endsWith('}') ? '\n' : ' ') + subBranch;
seqText += ` (${innerText.trim()})`;
}
if (!seqText.endsWith('\n')) seqText += '\n';
}
currentFen = nextNode.fen;
curr = nextNode;
if (!isRed) moveNum++;
isRed = !isRed;
}
return seqText.trim();
};
let startIsRed = this.historyFEN.fen.split(' ')[1] === 'w';
let initMoveNum = parseInt(this.historyFEN.fen.split(' ')[5], 10) || 1;
text += processPGNSequence(this.historyFEN, initMoveNum, startIsRed);
if (!text.endsWith('\n')) text += '\n';
return text + finalTerminator;
}
```
### 7. Record UI Update
Ensure the sidebar history renders strictly using the `cn` format parsed correctly from `getNotation`:
```javascript
addMoveToRecordUI(engineMove, index) {
const recordContainer = this.container.querySelector('.ejceesrecord');
const node = this.getNodeAtStep(index);
if (!node) return;
const parentNode = this.getNodeAtStep(index - 1);
// Convert engineMove to Chinese PGN for display
const cnMove = this.getNotation(parentNode.fen, engineMove, 'cn');
const thisFen = node.fen;
const turnAfterMove = thisFen.split(' ')[1];
const whoMoved = turnAfterMove === 'b' ? 'w' : 'b';
let lastOuter = recordContainer.lastElementChild;
if (!lastOuter || !lastOuter.classList.contains('ejceesrcdstepouter')) {
lastOuter = null;
}
let outer = lastOuter;
if (whoMoved === 'w') {
outer = document.createElement('div');
outer.className = 'ejceesrcdstepouter';
recordContainer.appendChild(outer);
} else {
if (!outer || outer.children.length >= 2) {
outer = document.createElement('div');
outer.className = 'ejceesrcdstepouter';
recordContainer.appendChild(outer);
const spacer = document.createElement('div');
spacer.className = 'ejceesrcdstep';
spacer.style.pointerEvents = 'none';
outer.appendChild(spacer);
}
}
const stepDiv = document.createElement('div');
stepDiv.className = 'ejceesrcdstep';
stepDiv.id = `step-record-${index}`;
stepDiv.dataset.index = index;
if (node.c && node.c.trim() !== '') stepDiv.classList.add('has-comment');
const marker = document.createElement('div');
marker.className = 'branch-marker';
if (parentNode && parentNode.v.length > 1) {
let forkIndex = -1;
let tempNode = this.historyFEN;
for (let j = 0; j < index - 1; j++) {
if (tempNode.v && tempNode.v.length > 1) forkIndex++;
const choice = tempNode.v && tempNode.v.length > 1 ? this.currentBranch[forkIndex] || 0 : 0;
tempNode = tempNode.v[choice];
}
forkIndex++;
const branchMark = document.createElement('span');
branchMark.className = 'branch-marker';
const currentChoice = this.currentBranch[forkIndex] !== undefined ? this.currentBranch[forkIndex] : 0;
branchMark.innerText = `${currentChoice + 1}/${parentNode.v.length}`;
branchMark.onclick = e => {
e.stopPropagation();
this.showBranchMenu(branchMark, parentNode, forkIndex, index);
};
stepDiv.appendChild(branchMark);
}
const textSpan = document.createElement('span');
textSpan.className = 'ejceesrcdstepcontent';
textSpan.textContent = cnMove; // Updated to PGN Chinese notation logic
stepDiv.appendChild(textSpan);
stepDiv.addEventListener('click', () => {
if (this.isEditingComment || this.isExportTextMode || this.isEditMode || this.isAutoPlaying) return;
this.jumpToStep(index);
this.saveStateToUndo();
this.updateToolHighlights();
});
outer.appendChild(stepDiv);
recordContainer.scrollTop = recordContainer.scrollHeight;
this.highlightActiveStep(index);
}
```
### 8. Import JSON fixes
Update `importExportedText` and file reading to restore `r` and `f` root properties properly:
```javascript
// Inside #file-input event listener
this.historyFEN = expand(data, data.fen);
this.historyFEN.r = data.r || 0;
this.historyFEN.f = data.f || 0;
this.isRotateEnabled = this.historyFEN.r === 1;
this.isFlipEnabled = this.historyFEN.f === 1;
this.reapplyVisualPositions();
// In importExportedText, JSON parse
this.historyFEN = expand(data, data.fen);
this.historyFEN.r = data.r || 0;
this.historyFEN.f = data.f || 0;
this.isRotateEnabled = this.historyFEN.r === 1;
this.isFlipEnabled = this.historyFEN.f === 1;
this.reapplyVisualPositions();
```
*(Note: Completely remove `this.updateHistoryFENFlags` function, as modifying FEN properties natively solves this more elegantly at the root).*https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
gemini pro:
代码: 全选
This happens because the `this.NotationConverter.cnPieceBlk` object uses lowercase letters as keys (e.g., `'c': '砲'`), but the previous code forced the character to uppercase (`char.toUpperCase()`) before looking it up. This caused it to search for `'C'` in the black pieces list, which returned `undefined`.
Here is the fix for the `getNotation` method.
### Update `getNotation`
Find this specific line inside your `getNotation` method:
```javascript
let pieceStr = isWXF ? char.toUpperCase() : this.NotationConverter[isRed ? 'cnPieceRed' : 'cnPieceBlk'][char.toUpperCase()];
```
And change it to this:
```javascript
let pieceStr = isWXF ? char.toUpperCase() : this.NotationConverter[isRed ? 'cnPieceRed' : 'cnPieceBlk'][char];
```
By simply passing `char` (which is naturally uppercase for Red and lowercase for Black based on `pId[0]`), it will correctly look up `'C'` in `cnPieceRed` and `'c'` in `cnPieceBlk`.https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
can't access property "startX", child.lastMove is undefined
encodeUrlParam file:///home/ejsoon/Documents/work/ejcees/obj/ejceesobj.js:6597
generateUrlHash file:///home/ejsoon/Documents/work/ejcees/obj/ejceesobj.js:6621
renderExportTextUI file:///home/ejsoon/Documents/work/ejcees/obj/ejceesobj.js:6763
bindEvents file:///home/ejsoon/Documents/work/ejcees/obj/ejceesobj.js:3099代码: 全选
這是一個典型的 JavaScript 運行時錯誤,通常發生在 Ejcees 類別(Chess 棋盤組件)的 `renderExportTextUI` 函數試圖處理數據時。
### 錯誤原因分析
錯誤訊息 `can't access property "startX", child.lastMove is undefined` 指出:
當你點擊 **tool-exp-txt** (導出文本) 時,程式碼試圖讀取 `child.lastMove.startX`。然而,此時的 `child` 對象中並沒有 `lastMove` 這個屬性(它是 `undefined`),導致程式崩潰。
這通常發生在以下情境:
1. **棋譜處於起始狀態**:如果你在棋盤剛初始化、尚未下任何棋步時點擊導出,歷史紀錄中可能只有根節點而沒有「最後一步」。
2. **變著(Branch)數據不完整**:如果棋譜中包含變著,而導出邏輯在遞迴遍歷分支時,遇到了沒有移動紀錄的空節點。
---
### 解決方案
你需要檢查 `ejcees.js` 中 `renderExportTextUI` 函數或其調用的 `generateUrlHash` 邏輯。以下是修復建議:
#### 1. 增加安全檢查(Optional Chaining)
在報錯的程式碼位置,將 `child.lastMove.startX` 修改為安全存取模式。
**修改前:**
```javascript
let x = child.lastMove.startX; // 這裡會報錯
```
**修改後(推薦):**
```javascript
// 使用 ?. 語法,如果 lastMove 不存在則返回 undefined 而不崩潰
let x = child.lastMove?.startX;
// 或者使用傳統判斷
let x = (child.lastMove && child.lastMove.startX) ? child.lastMove.startX : 0;
```
#### 2. 修改 `generateUrlHash` 邏輯
如果是因為起始點沒有移動數據,請在生成 URL Hash 之前檢查 `historyFEN` 的狀態:
```javascript
generateUrlHash() {
// 檢查是否有移動記錄,如果沒有,應給予預設值或跳過
if (!this.historyFEN || !this.historyFEN.v || this.historyFEN.v.length === 0) {
return "";
}
// ... 原有的 encode 邏輯
}
```
#### 3. 檢查 `renderExportTextUI`
確保在切換到導出模式時,`lastExportFormat` 正確初始化。如果 `isExportTextMode` 為 `true` 但數據棧為空,應直接顯示起始 FEN 而非嘗試解析移動路徑。
---
### 如何快速測試修復?
1. 在瀏覽器開發者工具(F12)的 **Console** 面板中,輸入 `ejceesInstance.historyFEN`(假設你的實例名是這個)查看結構。
2. 手動下兩步棋後再點擊導出。如果下棋後錯誤消失,則確定是「起始空紀錄處理不當」的問題。
**修正建議總結:**
請搜尋程式碼中的 `encodeUrlParam` 區塊,找到存取 `lastMove` 的地方,確保在存取 `startX`, `startY`, `endX`, `endY` 之前,先確認 `lastMove` 對象確實存在。- 附件
-
ejceesobj202604301104.js- (296.48 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
所有的代碼和注釋都要使用英文。
gemini thinking(pro在睡覺):
代码: 全选
根據您的需求,我已經將 `deriveCoordsFromMove()` 函數完全改寫為處理「h2e2」這類機器碼(ICCS/UCI 格式)的邏輯。由於移動代碼現在直接包含坐標信息,我們不再需要解析 FEN 字符串來尋找棋子位置,這大幅簡化了代碼。
以下是改寫後的函數,代碼與注釋均使用英文:
```javascript
/**
* Derives start and end coordinates from a machine-encoded move string (UCI/ICCS format).
* Example: "h2e2" -> startX: 7, startY: 7, endX: 4, endY: 7
* * Mapping logic:
* - Files (columns) 'a'-'i' map to indices 0-8.
* - Ranks (rows) '0'-'9' map to indices 9-0 (where 0 is the top-most rank in the internal board array).
* * @param {string} fen - The FEN string of the current position (kept for signature compatibility).
* @param {string} move - The machine move code (e.g., "h2e2").
* @returns {Object|null} An object containing {startX, startY, endX, endY}, or null if the move is invalid.
*/
deriveCoordsFromMove(fen, move) {
// 1. Basic validation: ensure move is a string with at least 4 characters
if (!move || typeof move !== 'string' || move.length < 4) {
console.warn('deriveCoordsFromMove: Invalid move string provided.', move);
return null;
}
// 2. Extract and convert column/file characters ('a' to 'i') to 0-8
// charCodeAt(0) - 97: 'a' is 97, 'b' is 98, etc.
const startX = move.charCodeAt(0) - 97;
const endX = move.charCodeAt(2) - 97;
// 3. Extract and convert rank characters ('0' to '9')
// In standard ICCS for Xiangqi:
// '0' is the bottom-most rank (array index 9)
// '9' is the top-most rank (array index 0)
const startRankChar = move.charAt(1);
const endRankChar = move.charAt(3);
const startY = 9 - parseInt(startRankChar, 10);
const endY = 9 - parseInt(endRankChar, 10);
// 4. Boundary validation for a 9x10 Xiangqi board
const isOutOfBounds = (x, y) => (x < 0 || x > 8 || y < 0 || y > 9);
if (isOutOfBounds(startX, startY) || isOutOfBounds(endX, endY)) {
console.error(`deriveCoordsFromMove: Coordinates out of bounds for move "${move}"`, { startX, startY, endX, endY });
return null;
}
// 5. Return the coordinate object
return {
startX,
startY,
endX,
endY
};
}
```
### 針對您提到的錯誤訊息修正建議:
報錯 `can't access property "startX", child.lastMove is undefined` 是因為程式碼在讀取 `child.lastMove.startX` 前,沒有檢查 `lastMove` 是否存在。
建議在 `encodeUrlParam` 或 `generateUrlHash` 等調用處增加「安全存取」檢查(Optional Chaining):
```javascript
// 修改前:
let sx = child.lastMove.startX;
// 修改後 (使用 ?. 語法):
let sx = child.lastMove?.startX;
// 或者判斷是否存在:
if (child.lastMove) {
// 執行涉及 startX 的邏輯
}
```
這能確保當棋局處於起始點(沒有 `lastMove`)或解析失敗時,程式不會崩潰。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
gemini thinking:
代码: 全选
這是一個典型的數據格式不匹配問題。自從您將 `node.m`(或 `node.move`)改為像 「h2e2」 這樣的機器碼後,原本的 `NotationConverter.toChinese()` 函數因為無法解析英文字母坐標(它只能解析像 `C2=5` 這樣的 PGN 格式),所以會返回 `undefined`。
在生成分支(Branch)的 UI 代碼中,需要改用 `this.getNotation()`,因為該函數能根據棋盤狀態將機器碼轉換為正確的中文著法。
以下是需要修正的兩個主要地方:
### 1. 修正 Mini Mode 的分支顯示 (`renderNoteUI`)
在 `renderNoteUI` 函數中,遍歷分支生成連結的部分:
**修改前:**
```javascript
// In renderNoteUI() loop for branches
moveLink.innerText = this.NotationConverter.toChinese(child.move);
```
**修改後 (使用英文注釋):**
```javascript
// Use getNotation to convert machine code (e.g., "h2e2") to Chinese notation
moveLink.innerText = this.getNotation(parentNode.fen, child.move, 'cn');
```
---
### 2. 修正分支菜單的顯示 (`showBranchMenu`)
如果您在正常模式下點擊步驟右側的分支標記(如 `1/2`),彈出的菜單也需要修正:
**修改前:**
```javascript
// In showBranchMenu()
item.textContent = this.NotationConverter.toChinese(child.move);
```
**修改後 (使用英文注釋):**
```javascript
// Ensure coordinate-based moves are correctly translated using the board context
item.textContent = this.getNotation(parentNode.fen, child.move, 'cn');
```
---
### 為什麼這樣改?
* **`NotationConverter.toChinese()`**: 是一個簡單的字符串替換工具。當它看到 "h2e2" 時,它試圖在映射表中尋找 "h",但找不到對應的棋子名稱,因此返回空或 `undefined`。
* **`this.getNotation(fen, move, 'cn')`**: 這是類內部的核心方法。它會調用 `deriveCoordsFromMove` 解析 "h2e2" 得到坐標,查看該坐標上的棋子(例如「炮」),並計算其移動方向(「平」、「進」、「退」),最後生成正確的「炮二平五」。
### 額外檢查:`renderRecordUI`
請確保 `renderRecordUI` 內部獲取 `cnMove` 的邏輯也是一致的:
```javascript
// Correct logic in renderRecordUI
const cnMove = this.getNotation(parentNode.fen, node.move, 'cn');
```
這樣修改後,無論 `node.m` 是舊版的 PGN 格式還是新版的坐標機器碼,分支顯示都能恢復正常的中文。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
一,在這個程式中,有兩種中文棋譜的表示方法,一是PGN中文,二是DP中文。
這兩種表示法在多個兵處於同一縱線時有所區別,PGN中文會是「五兵進一」,而DP中文會是「後三進一」。
分別找到它們跟engine format的轉換方法,如果沒有,則需要增加。
代码: 全选
對所上傳的js程式作出解讀或修改:
一,在這個程式中,有兩種中文棋譜的表示方法,一是PGN中文,二是DP中文。
這兩種表示法在多個兵處於同一縱線時有所區別,PGN中文會是「五兵進一」,而DP中文會是「後三進一」。
分別找到它們跟engine format的轉換方法,如果沒有,則需要增加。
二,在mini模式中,對於分支的表示,應用pgn中文,但是現在還沒有調整過來,錯誤的表現為:
<div id="mini-branch-container" class="mini-branch-container" style="display: block;"><div class="branch-move-link active">n7+8</div><div class="branch-move-link">馬九退undefined</div></div>
請查找原因並更正。
三,現在點擊tool-exp-svg,動畫不會動,查找原因。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。- 附件
-
ejceesobj202604301326.js- (297.62 KiB) 已下载 6 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
Here are the reasons for the issues and the exact modifications needed to fix them. All code and explanations are provided in English.
### 1. PGN vs. DP Chinese Pawn Notation Conversion
**Reason:**
When multiple pawns are on the same file, PGN format uses the pawn's order (e.g., `5P+1` converts directly to `五兵進一`), which is naturally handled by your current code. However, DP notation completely omits the piece name (e.g., `後三進一`), which translates to `-3+1` in English. Because it lacks a piece character (`P`/`p`), the color (`isRed`) becomes ambiguous, and the engine format becomes invalid.
**Modification:**
Update both `toEnglish` and `toChinese` inside `NotationConverter` to detect the DP format, dynamically insert the Pawn character (`P`/`p`) for the engine, and hide it when converting back to Chinese. This also fixes the `undefined` bug by adding a fallback for unmatched characters.
```javascript
NotationConverter: {
// ... (Keep existing cnPieceRed, cnPieceBlk, etc. mappings) ...
toEnglish: function (cn, isRed) {
let en = '';
// Detect DP Chinese Pawn notation which omits the piece (e.g., '後三進一')
// Length is 4, starts with a positional char, and the second char is a number.
let isDPPawn = cn.length === 4 &&
(cn[0] === '前' || cn[0] === '後' || cn[0] === '中' || cn[0] === '二' || cn[0] === '四') &&
this.enNum[cn[1]] && !this.enPiece[cn[1]];
for (let i = 0; i < cn.length; i++) {
let char = cn[i];
if (this.enPiece[char]) {
en += isRed ? this.enPiece[char] : this.enPiece[char].toLowerCase();
} else if (this.enNum[char]) {
en += this.enNum[char];
} else if (this.enAct[char]) {
en += this.enAct[char];
} else {
en += char;
}
}
// Add missing pawn piece for engine format compatibility (e.g., '-3+1' -> '-P3+1')
if (isDPPawn && en.length >= 2) {
en = en[0] + (isRed ? 'P' : 'p') + en.substring(1);
}
return en;
},
toChinese: function (en) {
if (!en) return '';
let pieceMatch = en.match(/[a-zA-Z]/);
let isRed = true;
if (pieceMatch) {
let pieceChar = pieceMatch[0];
isRed = pieceChar === pieceChar.toUpperCase();
}
// Detect DP Engine format (e.g., '-P3+1' or '+p3+1')
let isDPEnglish = en.length >= 4 &&
(en[0] === '+' || en[0] === '-' || en[0] === '=') &&
en[1].toLowerCase() === 'p' && /[0-9]/.test(en[2]);
let cn = '';
for (let i = 0; i < en.length; i++) {
let char = en[i];
// Skip pawn character for DP format so '-P3+1' becomes '後三進一' instead of '後兵三進一'
if (isDPEnglish && i === 1) {
continue;
}
if (/[a-zA-Z]/.test(char)) {
let p = isRed ? this.cnPieceRed[char.toUpperCase()] : this.cnPieceBlk[char.toLowerCase()];
cn += p !== undefined ? p : char; // Fix: Fallback prevents 'undefined' string
} else if (/[0-9]/.test(char)) {
let n = isRed ? this.cnNumRed[char] : this.cnNumBlk[char];
cn += n !== undefined ? n : char; // Fix: Fallback prevents 'undefined' string
} else if (/[+\-=]/.test(char)) {
if (i === 0) {
if (char === '+') cn += '前';
else if (char === '-') cn += '後';
else if (char === '=') cn += '中';
} else {
if (char === '+') cn += '進';
else if (char === '-') cn += '退';
else if (char === '=') cn += '平';
}
} else {
cn += char;
}
}
return cn;
}
}
```
---
### 2. Mini Mode Branch Formatting Bug
**Reason:**
There are two distinct errors causing `<div class="branch-move-link active">n7+8</div><div class="branch-move-link">馬九退undefined</div>`:
1. The **active** branch remains `n7+8` because `this.NotationConverter.toChinese()` is omitted when mapping the active state's text.
2. The **inactive** branch shows `馬九退undefined` because an incomplete/malformed move string (like `N9-undefined`) was generated upstream and passed into the converter. Because English letters like 'u' do not exist in `cnPieceRed`, the array lookup returns `undefined`. (The fallback added in Task 1 will now safely convert this to `馬九退undefined` instead of crashing, but the root cause is upstream).
**Modification:**
Locate the function responsible for rendering the `#mini-branch-container` (typically `updateMiniBranches` or within `renderDOM`) and enforce the Chinese translation for *all* branches while sanitizing missing targets.
```javascript
// Look for the code block rendering the mini-branch-container and update it like this:
// Ensure the move string is valid before translating.
let safeMove = branch.move.replace(/undefined/g, '');
// ALWAYS wrap the move in toChinese(), even if it's the active branch
let translatedMove = this.NotationConverter.toChinese(safeMove);
// Example of corrected render logic:
let branchHTML = `<div class="branch-move-link ${branch.isActive ? 'active' : ''}">${translatedMove}</div>`;
```
---
### 3. SVG Animation (`tool-exp-svg`) Fails on Click
**Reason:**
When the `<svg id="etani">` element is cloned to be exported and previewed in `.ejcees-export-modal`, it creates a **duplicate ID** in the DOM. SVGs relying on SMIL animations (`<animate>`, `<use href="#tileX">`, or `<defs mode="0">`) break because the browser will only target the very first `id="etani"` it finds (the original board), freezing the copied version. Furthermore, SMIL `<animate>` tags copy their current completed state and need to be reset to `0s`.
**Modification:**
When handling the `tool-exp-svg` click event, strip or append a unique suffix to the cloned SVG's IDs before injecting it into `.ejcees-export-preview`.
```javascript
// In your bindEvents() or export handling function:
document.getElementById('tool-exp-svg').addEventListener('click', () => {
let originalSvg = document.getElementById('etani');
// 1. Clone the SVG
let clonedSvg = originalSvg.cloneNode(true);
// 2. Prevent ID collision to restore SMIL animation targeting
clonedSvg.id = 'etani-export';
// 3. Restart SMIL animations natively for the cloned node
// Using setTimeout ensures the DOM registers the node before triggering timeline reset
setTimeout(() => {
let previewSvg = document.querySelector('.ejcees-export-preview svg');
if (previewSvg && typeof previewSvg.setCurrentTime === 'function') {
previewSvg.setCurrentTime(0);
}
}, 50);
// 4. Inject into Modal
document.querySelector('.ejcees-export-preview').innerHTML = '';
document.querySelector('.ejcees-export-preview').appendChild(clonedSvg);
});
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
如果format === 'dp',則:
代码: 全选
getNotation(fen, engineMove, format) {
if (format === 'iccs') {
return engineMove.substring(0, 2).toUpperCase() + '-' + engineMove.substring(2, 4).toLowerCase();
}
let coords = this.engineToCoords(engineMove);
if (!coords) return engineMove;
let { startX, startY, endX, endY } = coords;
let vMap = this.buildVirtualMap(fen);
let pId = vMap.get(`${startX},${startY}`);
if (!pId) return engineMove;
let char = pId[0];
let isRed = char === char.toUpperCase();
let isPawn = char.toLowerCase() === 'p';
let samePieces = [];
for (let [pos, id] of vMap.entries()) {
if (id[0] === char) {
let [x, y] = pos.split(',').map(Number);
samePieces.push({ x, y });
}
}
let prefixIndex = -1; // -1: none, 0: Front, 1: Middle, 2: Back, 11-15: 1st-5th
let colPieces = samePieces.filter(p => p.x === startX);
let multiCols = [];
if (isPawn) {
for (let c = 0; c <= 8; c++) {
if (samePieces.filter(p => p.x === c).length > 1) multiCols.push(c);
}
if (multiCols.length > 1) {
let allMultiPawns = [];
for (let c of multiCols) allMultiPawns.push(...samePieces.filter(p => p.x === c));
allMultiPawns.sort((a, b) => {
if (isRed) {
if (b.x !== a.x) return b.x - a.x;
return a.y - b.y;
} else {
if (a.x !== b.x) return a.x - b.x;
return b.y - a.y;
}
});
prefixIndex = 11 + allMultiPawns.findIndex(p => p.x === startX && p.y === startY);
} else if (colPieces.length >= 4) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = 11 + colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 3) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 2) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = colPieces.findIndex(p => p.y === startY) === 0 ? 0 : 2;
}
} else if (colPieces.length > 1 && !['a', 'A', 'b', 'B'].includes(char)) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
if (colPieces.length === 3) {
prefixIndex = colPieces.findIndex(p => p.y === startY);
} else {
prefixIndex = colPieces.findIndex(p => p.y === startY) === 0 ? 0 : 2;
}
}
let isAdvancing = isRed ? startY > endY : startY < endY;
let isWXF = format === 'wxf';
let actionStr = startY === endY ? (isWXF ? '.' : '平') : isAdvancing ? (isWXF ? '+' : '進') : isWXF ? '-' : '退';
let startFile = isRed ? 9 - startX : startX + 1;
let endFile = isRed ? 9 - endX : endX + 1;
let valStr;
if (startY === endY) {
valStr = isWXF ? endFile.toString() : this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][endFile];
} else {
let isStraight = !['n', 'N', 'b', 'B', 'a', 'A'].includes(char);
let valNum = isStraight ? Math.abs(startY - endY) : endFile;
valStr = isWXF ? valNum.toString() : this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][valNum];
}
let pieceStr = isWXF ? char.toUpperCase() : this.NotationConverter[isRed ? 'cnPieceRed' : 'cnPieceBlk'][char];
if (isWXF) {
let wxfPrefix = '';
if (prefixIndex === 0) wxfPrefix = '+';
else if (prefixIndex === 1) wxfPrefix = '=';
else if (prefixIndex === 2) wxfPrefix = '-';
else if (prefixIndex >= 11) wxfPrefix = String.fromCharCode(97 + prefixIndex - 11);
if (wxfPrefix) return pieceStr + wxfPrefix + actionStr + valStr;
return pieceStr + startFile + actionStr + valStr;
} else {
let cnPrefix = '';
if (prefixIndex === 0) cnPrefix = '前';
else if (prefixIndex === 1) cnPrefix = '中';
else if (prefixIndex === 2) cnPrefix = '後';
else if (prefixIndex >= 11) cnPrefix = ['一', '二', '三', '四', '五'][prefixIndex - 11];
if (cnPrefix) return cnPrefix + (prefixIndex >= 11 ? '兵' : pieceStr) + actionStr + valStr;
return pieceStr + this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][startFile] + actionStr + valStr;
}
}
對這個方法的 if (isPawn) {...}增加判斷邏輯。
如果format === 'dp',則:
如果所移動的兵有另一個兵跟它在同一列,其餘兵不在同一列,則結果為「前兵」或「後兵」;
如果所移動的兵有另兩個兵跟它在同一列,其餘兵不在同一列,則結果為「前兵」、「中兵」或「後兵」;
如果所移動的兵有另一個兵跟它在同一列,其餘有兵同在另一列,則結果為「前N」或「後N」;
如果所移動的兵有另兩個兵跟它在同一列,其餘有兵同在另一列,則結果為「前N」、「中兵」或「後N」;
如果所移動的兵有另三個或四個兵跟它在同一列,則結果為「前兵」、「二兵」、「三兵」、「四兵」或「後兵」;
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。- 附件
-
ejceesobj202604301418.js- (297.51 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
To implement the specific "dp" notation rules for pawns in Xiangqi, you need to update the `if (isPawn)` block to categorize the pawns based on column distribution and update the final string construction logic to handle the "Front/Back + File Number" (`前N`/`後N`) case.
Here is the modified code for those sections:
### 1. Updated `if (isPawn)` logic
Replace the existing `if (isPawn) { ... }` block with the following:
```javascript
if (isPawn) {
// Count columns that contain more than one pawn
for (let c = 0; c <= 8; c++) {
if (samePieces.filter(p => p.x === c).length > 1) multiCols.push(c);
}
if (format === 'dp') {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
let idx = colPieces.findIndex(p => p.y === startY);
let numInCol = colPieces.length;
if (numInCol >= 4) {
// Rule 5: 4 or 5 pawns in the same column
if (idx === numInCol - 1) prefixIndex = 2; // "Back" (後)
else if (idx === 0) prefixIndex = 0; // "Front" (前)
else prefixIndex = 11 + idx; // "2nd", "3rd", "4th" (二, 三, 四)
} else if (multiCols.length > 1) {
// Rules 3 & 4: Multiple columns have multiple pawns
if (numInCol === 2) {
prefixIndex = (idx === 0 ? 20 : 22); // "Front N" (前N) or "Back N" (後N)
} else if (numInCol === 3) {
if (idx === 0) prefixIndex = 20; // "Front N" (前N)
else if (idx === 1) prefixIndex = 1; // "Middle" (中)
else prefixIndex = 22; // "Back N" (後N)
}
} else if (numInCol > 1) {
// Rules 1 & 2: Only one column has multiple pawns
if (numInCol === 2) {
prefixIndex = (idx === 0 ? 0 : 2); // "Front" (前) or "Back" (後)
} else if (numInCol === 3) {
prefixIndex = idx; // 0:Front, 1:Middle, 2:Back
}
}
} else {
// Standard logic for other formats
if (multiCols.length > 1) {
let allMultiPawns = [];
for (let c of multiCols) allMultiPawns.push(...samePieces.filter(p => p.x === c));
allMultiPawns.sort((a, b) => {
if (isRed) {
if (b.x !== a.x) return b.x - a.x;
return a.y - b.y;
} else {
if (a.x !== b.x) return a.x - b.x;
return b.y - a.y;
}
});
prefixIndex = 11 + allMultiPawns.findIndex(p => p.x === startX && p.y === startY);
} else if (colPieces.length >= 4) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = 11 + colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 3) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = colPieces.findIndex(p => p.y === startY);
} else if (colPieces.length === 2) {
colPieces.sort((a, b) => (isRed ? a.y - b.y : b.y - a.y));
prefixIndex = colPieces.findIndex(p => p.y === startY) === 0 ? 0 : 2;
}
}
}
```
---
### 2. Updated Output Logic
Since the "dp" format uses a file number instead of the piece name (e.g., `前三` instead of `前兵`) in certain cases, update the final `else` block (the Chinese notation part):
```javascript
} else {
let cnPrefix = '';
if (prefixIndex === 0) cnPrefix = '前';
else if (prefixIndex === 1) cnPrefix = '中';
else if (prefixIndex === 2) cnPrefix = '後';
else if (prefixIndex === 20) cnPrefix = '前'; // Front for "Front N"
else if (prefixIndex === 22) cnPrefix = '後'; // Back for "Back N"
else if (prefixIndex >= 11) cnPrefix = ['一', '二', '三', '四', '五'][prefixIndex - 11];
if (cnPrefix) {
let midPart;
if (format === 'dp' && (prefixIndex === 20 || prefixIndex === 22)) {
// For Rule 3 & 4: Prefix + File Number (e.g., 前三)
midPart = this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][startFile];
} else {
// Standard: Prefix + Piece Name (e.g., 前兵 or 二兵)
midPart = (prefixIndex >= 11 || (format === 'dp' && isPawn)) ? '兵' : pieceStr;
}
return cnPrefix + midPart + actionStr + valStr;
}
return pieceStr + this.NotationConverter[isRed ? 'cnNumRed' : 'cnNumBlk'][startFile] + actionStr + valStr;
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
以機器碼(h2e2)作為內碼驅動。區分了東萍格式和PGN格式。
- 附件
-
ejceesobj202604301913.js- (299.54 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
console.log('2 dc', dc);
if (childIdx === -1) {
let newNode = {
fen: nextFen,
move: moveEn,
c: '',
v: []
};
currNode.v.push(newNode);
childIdx = currNode.v.length - 1;
}
說明如何把dc轉換成engine format,並把上方的代碼中的moveEn通過dc轉換成engine format。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。- 附件
-
ejceesobj202604301959.js- (299.68 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
### How to Convert `dc` to Engine Format
The engine format (UCCI/ICCS) represents chess moves using a 4-character string where:
* **Columns (X-axis):** Mapped from `0-8` to letters `a-i` (using ASCII, where `97` is 'a').
* **Rows (Y-axis):** Mapped from `0-9` to numbers `9-0` (inverted, so `9 - Y`).
To convert the `dc` object (`{startX, startY, endX, endY}`) into this format, you can either use the class's built-in helper method `this.coordsToEngine(dc.startX, dc.startY, dc.endX, dc.endY)` or manually map it using `String.fromCharCode(97 + x) + (9 - y)`.
---
### Modified Code Snippet
Here is the modified code around the section you provided. We reassign `moveEn` to the engine format immediately before it is used to check for existing branches and creating the new node.
```javascript
// Convert the derived coordinates (dc) to engine format
moveEn = this.coordsToEngine(dc.startX, dc.startY, dc.endX, dc.endY);
let childIdx = currNode.v.findIndex(c => c.move === moveEn && c.fen === nextFen);
console.log('2 dc', dc);
if (childIdx === -1) {
let newNode = {
fen: nextFen,
move: moveEn, // Now safely stored as engine format (e.g., 'h2e2')
c: '',
v: []
};
currNode.v.push(newNode);
childIdx = currNode.v.length - 1;
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 5 访客
