將製作ejcees(中國象棋打譜程式)
Re: 將製作ejcees(中國象棋打譜程式)
一個小問題:如果打開的json輪到黑方,則第一行要改為「特定局面,輪到黑方」。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
// Deep clone the entire game tree for undo/redo
function cloneGameState() {
return {
historyFEN: JSON.parse(JSON.stringify(historyFEN)),
currentBranch: [...currentBranch],
currentStepIndex: currentStepIndex
};
}
function saveStateToUndo() {
undoStack.push(cloneGameState());
if (undoStack.length > 50) undoStack.shift(); // limit history size
redoStack = []; // clear redo on new action
}
function restoreState(state) {
historyFEN = state.historyFEN;
currentBranch = state.currentBranch;
currentStepIndex = state.currentStepIndex;
renderRecordUI();
renderNoteUI();
jumpToStep(currentStepIndex);
}
function undo() {
if (undoStack.length === 1) return;
const previous = undoStack.pop();
redoStack.push(previous);
restoreState(undoStack[undoStack.length - 1]);
}
function redo() {
if (redoStack.length === 0) return;
const next = redoStack.pop();
undoStack.push(next);
restoreState(next);
}
在以上代碼中,如果我走到第二步,之後走一下undo和redo,然後往後走到第四步或更多,當我undo回到第二步時,historyFEN跟當前最後一步是一樣的。
懷疑是深複製存在問題。
請查找問題原因,並說出如何修複。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
https://ejsoon.vip/wp-content/uploads/2 ... ndo98.html
undo和redo的問題已解決。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
<div class="ejceestool">
<div class="tool-btn" id="tool-new" title="New">
</div>
<div class="tool-btn" id="tool-open" title="Open">
</div>
<div class="tool-btn" id="tool-edit" title="Edit">
</div>
<div class="tool-btn" id="tool-save" title="Save">
</div>
<div class="tool-btn" id="tool-undo" title="Undo">
</div>
<div class="tool-btn" id="tool-redo" title="Redo">
</div>
<div class="tool-btn" id="tool-rotate" title="Rotate">
</div>
<div class="tool-btn" id="tool-flip" title="Flip">
</div>
<div class="tool-btn" id="tool-exp-txt" title="Export Text">
</div>
<div class="tool-btn" id="tool-exp-svg" title="Export SVG">
</div>
</div>代码: 全选
<div class="note-btn" id="btn-del-move" title="Delete Move">
</div>
<div class="note-btn" id="btn-range" title="Range Selection">
</div>
<div class="note-btn" id="btn-autoplay" title="Auto Play">
</div>
<div class="note-btn" id="btn-speed" title="Move Speed" style="width: auto; padding: 0 4px; font-weight: bold;">
0.36s
</div>
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
<div class="ejceesoutput">Click on the SVG area to get coordinates.</div>
<div class="ejceesstep">
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0">
<div class="ejceesstepplus">+</div>
</div>
代码: 全选
<div class="ejceestext">
<div class="ejceesrecord">
<div class="ejceesrcdstart" id="record-start">棋局開始,紅方先行</div>
<div class="ejceesrcdstep" id="step-record-1" data-index="1"><span class="ejceesrcdstepcontent">炮二平五</span></div>
</div>
<div class="ejceesnote">
<div class="ejceescomment"></div>
<div class="ejceestextbtn"></div>
</div>
</div>
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
一,為#tool-rotate和#tool-flip增加點擊功能,點擊之後高亮,再點擊取消高亮。
#tool-rotate使所有棋子位置中心旋轉,#tool-flip為所有棋子位置左右對稱。
這兩個功能都不會影響.ejceesrecord的文字描述即棋譜,也不會記錄到historyFEN中,亦不記錄到undo、redo中。
當點擊ejceesstep內的按鍵時,棋子位置也會因旋轉或對稱而發生變化。
二,tool-btn處於非高亮狀態時,把顏色調得更暗。
tool-new:當有至少一步棋,或初始狀態與INITIAL_FEN不同時高亮。
tool-open:一直高亮。
tool-edit:當處於非編輯狀態時高亮。
tool-save:跟tool-new相同。
tool-undo:當undoStack.length>1時高亮。
tool-redo:當redoStack.length>0時高亮。
tool-rotate:當處於rotate狀態時高亮。
tool-flip:當處於flip狀態時高亮。
tool-exp-txt:一直高亮。
tool-exp-svg:一直高亮。
建立一個切換按鍵高亮的函式,在事件觸發的時候使用。
三,只需給出要修改的地方,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
<!-- ONLY THE MODIFIED PARTS BELOW (all code/comments in English) -->
<!-- 1. CSS: Update .tool-btn rules for darker non-highlight state + explicit .enable support -->
.tool-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #333; /* darker default (non-highlighted state) */
color: #aaa;
display: flex;
justify-content: center;
align-items: center;
font-size: 11px;
cursor: pointer;
user-select: none;
}
.tool-btn.enable {
background-color: #555;
color: white;
}
.tool-btn.enable svg {
fill: #4CAF50; /* Green for active/enabled */
}
.tool-btn:hover svg {
fill: #fff;
}
<!-- 2. JavaScript: Add these variables right after the existing "let" declarations (after line with isAutoPlaying) -->
// View mode flags - rotate/flip only affect visual piece positions
// They do NOT modify .ejceesrecord text, historyFEN, undoStack, or redoStack
let isRotateEnabled = false;
let isFlipEnabled = false;
<!-- 3. JavaScript: Add these helper functions just before the comment "// --- Boot the App ---" -->
/**
* Applies current rotate and/or flip transforms to piece positions only.
* Called automatically after every loadFEN so step navigation also reflects the mode.
* Updates DOM transforms, tileMap, and piecePos. Purely visual - no history impact.
*/
function applyCurrentTransforms() {
if (!isRotateEnabled && !isFlipEnabled) {
return;
}
if (isRotateEnabled) {
rotateBoard180();
}
if (isFlipEnabled) {
flipBoardHorizontal();
}
}
/**
* Rotate all pieces 180 degrees around board center (newX = 8 - oldX, newY = 9 - oldY).
* Updates DOM, tileMap, and piecePos. Does NOT touch historyFEN, record, undo, or redo.
*/
function rotateBoard180() {
const newTileMap = new Map();
const newPiecePos = new Map();
for (const [posStr, pieceId] of tileMap.entries()) {
const [x, y] = posStr.split(',').map(Number);
const newX = 8 - x;
const newY = 9 - y;
const newPosStr = `${newX},${newY}`;
newTileMap.set(newPosStr, pieceId);
newPiecePos.set(pieceId, { x: newX, y: newY });
const el = document.getElementById(pieceId);
if (el) {
const px = newX * 48 + 24;
const py = newY * 48 + 24;
el.setAttribute('transform', `translate(${px}, ${py})`);
}
}
tileMap.clear();
piecePos.clear();
for (const [k, v] of newTileMap.entries()) tileMap.set(k, v);
for (const [k, v] of newPiecePos.entries()) piecePos.set(k, v);
}
/**
* Flip all pieces left-right (newX = 8 - oldX, newY = oldY).
* Updates DOM, tileMap, and piecePos. Does NOT touch historyFEN, record, undo, or redo.
*/
function flipBoardHorizontal() {
const newTileMap = new Map();
const newPiecePos = new Map();
for (const [posStr, pieceId] of tileMap.entries()) {
const [x, y] = posStr.split(',').map(Number);
const newX = 8 - x;
const newY = y;
const newPosStr = `${newX},${newY}`;
newTileMap.set(newPosStr, pieceId);
newPiecePos.set(pieceId, { x: newX, y: newY });
const el = document.getElementById(pieceId);
if (el) {
const px = newX * 48 + 24;
const py = newY * 48 + 24;
el.setAttribute('transform', `translate(${px}, ${py})`);
}
}
tileMap.clear();
piecePos.clear();
for (const [k, v] of newTileMap.entries()) tileMap.set(k, v);
for (const [k, v] of newPiecePos.entries()) piecePos.set(k, v);
}
/**
* Central function to toggle .enable class on all tool-btn elements based on current state.
* Called on every relevant event (new, open, edit, undo, redo, step change, etc.).
*/
function updateToolHighlights() {
const isModified = currentStepIndex > 0 || historyFEN.fen !== INITIAL_FEN;
const highlights = {
'tool-new': isModified,
'tool-open': true,
'tool-edit': !isEditingComment,
'tool-save': isModified,
'tool-undo': undoStack.length > 1,
'tool-redo': redoStack.length > 0,
'tool-rotate': isRotateEnabled,
'tool-flip': isFlipEnabled,
'tool-exp-txt': true,
'tool-exp-svg': true
};
for (const [id, shouldEnable] of Object.entries(highlights)) {
const btn = document.getElementById(id);
if (btn) {
if (shouldEnable) {
btn.classList.add('enable');
} else {
btn.classList.remove('enable');
}
}
}
}
<!-- 4. JavaScript: Modify loadFEN - add call to applyCurrentTransforms at the very end (just before the closing } of loadFEN) -->
// Apply rotate/flip transforms if enabled (purely visual; does not affect record, historyFEN, undo or redo)
applyCurrentTransforms();
}
<!-- 5. JavaScript: Add updateToolHighlights() at the end of renderRecordUI() and renderNoteUI() -->
// (inside renderRecordUI, after highlightActiveStep)
updateToolHighlights();
// (inside renderNoteUI, after the if/else block that sets btnDiv)
updateToolHighlights();
<!-- 6. JavaScript: Add the rotate and flip click listeners right after the existing tool listener block (after the tool-redo listener) -->
// New: Rotate tool - click to toggle 180° rotation of all piece positions (highlight when active)
// Clicking step controls (-, +, slider) will also reflect the rotation because loadFEN re-applies it.
// Does NOT affect .ejceesrecord text, historyFEN, undoStack or redoStack.
document.getElementById('tool-rotate').addEventListener('click', () => {
isRotateEnabled = !isRotateEnabled;
const currentNode = getNodeAtStep(currentStepIndex);
if (currentNode) {
loadFEN(currentNode.fen);
applyLastMoveVisuals(currentNode.lastMove);
} else {
applyCurrentTransforms();
}
updateToolHighlights();
});
// New: Flip tool - click to toggle left-right symmetry of all piece positions (highlight when active)
// Clicking step controls (-, +, slider) will also reflect the flip because loadFEN re-applies it.
// Does NOT affect .ejceesrecord text, historyFEN, undoStack or redoStack.
document.getElementById('tool-flip').addEventListener('click', () => {
isFlipEnabled = !isFlipEnabled;
const currentNode = getNodeAtStep(currentStepIndex);
if (currentNode) {
loadFEN(currentNode.fen);
applyLastMoveVisuals(currentNode.lastMove);
} else {
applyCurrentTransforms();
}
updateToolHighlights();
});
<!-- 7. JavaScript: Add initial call at the very end of DOMContentLoaded (right before the closing }); of the big script) -->
// Initial highlight update after boot
updateToolHighlights();https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
應補上loadFEN()。
上次由 ejsoon 在 2026年 3月 10日 23:26,总共编辑 1 次。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
棋譜不應因#tool-rotate和#tool-flip而發生變化。
例如:一個棋子從1,9移至2,7,棋譜記為N8+7(馬八進七)。#tool-rotate旋轉之後,它的坐標將變為從7,0移至6,2,但是棋譜仍應為N8+7(馬八進七)。
請修復。
代码: 全选
<!-- ONLY THE MODIFIED PARTS BELOW (all code/comments in English) -->
<!-- 1. CSS: unchanged from previous improvement (darker non-highlight + .enable support) -->
<!-- 2. JavaScript: Keep the two flag variables (after existing let declarations) -->
// View mode flags - rotate/flip are PURELY VISUAL
// They NEVER change internal coordinates, tileMap, piecePos, FEN, lastMove,
// getMoveNotation(), historyFEN, .ejceesrecord text, undoStack or redoStack
let isRotateEnabled = false;
let isFlipEnabled = false;
<!-- 3. JavaScript: REPLACE the old three functions (applyCurrentTransforms + rotateBoard180 + flipBoardHorizontal)
with this single new function (place it where the old functions were, just before "// --- Boot the App ---") -->
/**
* Applies rotate 180° and/or horizontal flip as a single SVG group transform on .etdrop.
* This is 100% visual only. Mouse clicks, move validation, notation generation,
* historyFEN, record UI text and undo/redo remain completely unaffected.
* The transform is applied to the existing <g class="etdrop"> so the whole board
* (grid + pieces + start dot) appears rotated/flipped while logical coordinates
* (used by getMoveNotation, lastMove, etc.) stay original.
*/
function applyVisualTransform() {
const etdrop = document.querySelector('.etdrop');
if (!etdrop) return;
let transformStr = '';
if (isRotateEnabled) {
transformStr += 'rotate(180 216 240) ';
}
if (isFlipEnabled) {
transformStr += 'translate(432,0) scale(-1,1) ';
}
etdrop.setAttribute('transform', transformStr.trim() || 'translate(0,0) scale(1,1)');
}
<!-- 4. JavaScript: Remove the old applyCurrentTransforms() call from loadFEN
(delete the line that was added in the previous improvement) -->
// (the following line is REMOVED - no longer needed)
// applyCurrentTransforms();
<!-- 5. JavaScript: REPLACE the two tool listener blocks (the old rotate/flip listeners)
with these new simple listeners (place them right after the existing tool-redo listener) -->
// New: Rotate tool - toggles 180° visual rotation only (highlight when active)
// Chess record text, FEN, move notation and history remain 100% unchanged.
// Clicking step controls (-, +, slider) instantly reflects the current visual mode
// because the transform is always active on .etdrop.
document.getElementById('tool-rotate').addEventListener('click', () => {
isRotateEnabled = !isRotateEnabled;
applyVisualTransform();
updateToolHighlights();
});
// New: Flip tool - toggles left-right visual symmetry only (highlight when active)
// Chess record text, FEN, move notation and history remain 100% unchanged.
document.getElementById('tool-flip').addEventListener('click', () => {
isFlipEnabled = !isFlipEnabled;
applyVisualTransform();
updateToolHighlights();
});
<!-- 6. JavaScript: Add initial call at the very end of DOMContentLoaded
(right before the closing }); of the big script block) -->
// Apply initial visual transform (defaults to identity) and update highlights
applyVisualTransform();
updateToolHighlights();https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
棋譜不應因#tool-rotate和#tool-flip而發生變化。
例如:一個棋子從1,9移至2,7,棋譜記為N8+7(馬八進七)。#tool-rotate旋轉之後,它的坐標將變為從7,0移至6,2,但是棋譜仍應為N8+7(馬八進七)。
不能用transform的方式對svg旋轉或鏡像,應改變棋子的放置邏輯。
請修復。
代码: 全选
<!-- ONLY THE MODIFIED PARTS BELOW (all code/comments in English) -->
<!-- This completely replaces the previous rotate/flip implementation. -->
<!-- We now keep ALL game data (tileMap, piecePos, FEN, historyFEN, moves) in STANDARD LOGICAL coordinates. -->
<!-- Only the visual placement (translate) of each piece uses the current view mode. -->
<!-- Click coordinates are inverse-mapped back to logical before any game logic or notation generation. -->
<!-- This guarantees that the record text (棋譜) and all stored moves NEVER change due to rotate/flip. -->
<!-- Notation is always generated from logical coordinates (e.g. N8+7 stays N8+7). -->
<!-- Piece graphics stay upright (no group transform on SVG). -->
<!-- Step navigation (-, +, slider) automatically shows the rotated/flipped view. -->
<!-- 1. CSS: No change needed (previous darker non-highlight state is kept) -->
<!-- 2. JavaScript: Replace the old rotate/flip variables and helpers with the new logical/visual system -->
<!-- Put this right after: let isAutoPlaying = false; -->
// View mode flags - rotate/flip only affect visual placement.
// All game data, FEN, historyFEN, move notation, undo/redo remain in standard logical coordinates.
let isRotateEnabled = false;
let isFlipEnabled = false;
<!-- 3. JavaScript: Add these NEW helper functions just before the comment "// --- Boot the App ---" -->
/**
* Convert logical coordinates (standard FEN / history) to visual screen coordinates
* according to current rotate/flip mode.
* @returns {{x: number, y: number}}
*/
function getVisualCoords(logX, logY) {
let vX = logX;
let vY = logY;
if (isFlipEnabled) vX = 8 - vX;
if (isRotateEnabled) {
vX = 8 - vX;
vY = 9 - vY;
}
return { x: vX, y: vY };
}
/**
* Convert visual screen coordinates back to logical coordinates.
* Used for click detection so all game logic and notation stay in standard coordinates.
* @returns {{x: number, y: number}}
*/
function getLogicalCoords(visX, visY) {
let lX = visX;
let lY = visY;
if (isRotateEnabled) {
lX = 8 - lX;
lY = 9 - lY;
}
if (isFlipEnabled) lX = 8 - lX;
return { x: lX, y: lY };
}
/**
* Re-apply visual positions to all pieces without touching any game data.
* Called when toggling rotate/flip or after step navigation.
*/
function reapplyVisualPositions() {
for (const [logPosStr, pieceId] of tileMap.entries()) {
const [logX, logY] = logPosStr.split(',').map(Number);
const vis = getVisualCoords(logX, logY);
const px = vis.x * 48 + 24;
const py = vis.y * 48 + 24;
const el = document.getElementById(pieceId);
if (el) {
el.setAttribute('transform', `translate(${px}, ${py})`);
}
}
// Refresh the "from" dot for the current move
const currentNode = getNodeAtStep(currentStepIndex);
applyLastMoveVisuals(currentNode ? currentNode.lastMove : null);
}
<!-- 4. JavaScript: Modify loadFEN - change piece placement to use visual coordinates (logical data unchanged) -->
<!-- Replace the placement block inside the FEN parsing loop (the part that sets px/py and appends useEl) -->
// Logical coords from FEN stay standard (for notation, history, validation)
const vis = getVisualCoords(x, y);
const px = vis.x * 48 + 24;
const py = vis.y * 48 + 24;
useEl.setAttribute('transform', `translate(${px}, ${py})`);
etboard.appendChild(useEl);
tileMap.set(`${x},${y}`, pieceId); // key = logical
piecePos.set(pieceId, { x, y }); // logical
x++;
<!-- 5. JavaScript: Modify the click handler - map visual click back to logical coordinates -->
<!-- Replace the grid calculation part in svgElement.addEventListener('click' ... -->
const gridX_vis = Math.floor(clickX / 48);
const gridY_vis = Math.floor(clickY / 48);
const logicalPt = getLogicalCoords(gridX_vis, gridY_vis);
const gridX = logicalPt.x;
const gridY = logicalPt.y;
<!-- 6. JavaScript: Modify movePieceWithAnimation - use visual start/end for animation but keep logical data -->
<!-- Replace the entire movePieceWithAnimation function -->
function movePieceWithAnimation(pieceId, targetLogicalX, targetLogicalY, eatenId) {
if (isAnimating) return;
isAnimating = true;
const pieceEl = document.getElementById(pieceId);
etdrop.appendChild(pieceEl);
const startLogical = piecePos.get(pieceId);
const startVis = getVisualCoords(startLogical.x, startLogical.y);
const endVis = getVisualCoords(targetLogicalX, targetLogicalY);
const startXpx = startVis.x * 48 + 24;
const startYpx = startVis.y * 48 + 24;
const endXpx = endVis.x * 48 + 24;
const endYpx = endVis.y * 48 + 24;
let startTime = null;
const duration = 360;
function animate(time) {
if (!startTime) startTime = time;
let progress = (time - startTime) / duration;
if (progress > 1) progress = 1;
const currX = startXpx + (endXpx - startXpx) * progress;
const currY = startYpx + (endYpx - startYpx) * progress;
pieceEl.setAttribute('transform', `translate(${currX}, ${currY})`);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
etboard.appendChild(pieceEl);
if (eatenId) {
const eatenEl = document.getElementById(eatenId);
const href = eatenEl.getAttribute('href') || eatenEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) {
eatenEl.setAttribute('rm', '1');
eatenEl.setAttribute('display', 'none');
piecePos.delete(eatenId);
}
}
// Update logical data only
const moveText = getMoveNotation(pieceId, startLogical.x, startLogical.y, targetLogicalX, targetLogicalY, eatenId);
const moveData = {
startX: startLogical.x,
startY: startLogical.y,
endX: targetLogicalX,
endY: targetLogicalY
};
tileMap.delete(`${startLogical.x},${startLogical.y}`);
tileMap.set(`${targetLogicalX},${targetLogicalY}`, pieceId);
piecePos.set(pieceId, { x: targetLogicalX, y: targetLogicalY });
// Final visual position
pieceEl.setAttribute('transform', `translate(${endXpx}, ${endYpx})`);
setPickup(pieceId, false);
isAnimating = false;
currentTurn = currentTurn === 'w' ? 'b' : 'w';
pushHistory(moveText, moveData);
applyLastMoveVisuals(moveData);
}
}
requestAnimationFrame(animate);
}
<!-- 7. JavaScript: Modify applyLastMoveVisuals - dot uses visual coordinates -->
<!-- Replace the dot positioning part -->
if (dot) {
if (!lastMove) {
dot.setAttribute('cx', initialDotCx);
dot.setAttribute('cy', initialDotCy);
} else {
const visStart = getVisualCoords(lastMove.startX, lastMove.startY);
dot.setAttribute('cx', visStart.x * 48 + 24);
dot.setAttribute('cy', visStart.y * 48 + 24);
}
}
<!-- 8. JavaScript: Remove the old applyCurrentTransforms, rotateBoard180, flipBoardHorizontal functions entirely -->
<!-- (They are no longer needed - delete the whole block that was added in the previous version) -->
<!-- 9. JavaScript: Replace the tool-rotate and tool-flip click listeners (right after the existing tool-redo listener) -->
// Rotate tool - toggles 180° view. Only changes visual placement; notation, historyFEN, record text unchanged.
document.getElementById('tool-rotate').addEventListener('click', () => {
isRotateEnabled = !isRotateEnabled;
reapplyVisualPositions();
updateToolHighlights();
});
// Flip tool - toggles horizontal mirror. Only changes visual placement; notation, historyFEN, record text unchanged.
document.getElementById('tool-flip').addEventListener('click', () => {
isFlipEnabled = !isFlipEnabled;
reapplyVisualPositions();
updateToolHighlights();
});
<!-- 10. JavaScript: Remove the old applyCurrentTransforms() call that was inside loadFEN (no longer needed) -->
<!-- 11. JavaScript: Initial call remains the same (updateToolHighlights at the end of DOMContentLoaded) -->
<!-- No other changes required. The record text, getMoveNotation, FEN saving/loading, undo/redo, and all validation stay exactly as before. -->
<!-- End of modifications. This fixes the issue: chess record (e.g. N8+7) never changes due to rotate/flip, even after step navigation or new moves. -->https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
https://ejsoon.vip/wp-content/uploads/2 ... te991.html
實現了rotate和flip,完善了按鈕高亮。
下一步就要做編輯局面了。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
let currentTurn = 'w';
let historyFEN = {
fen: INITIAL_FEN,
move: null,
lastMove: null,
v: []
};
let currentBranch = [];
let currentStepIndex = 0;
let isEditingComment = false;
let isAutoPlaying = false;
// View mode flags - rotate/flip only affect visual placement.
let isRotateEnabled = false;
let isFlipEnabled = false;
let initialDotCx = '0';
let initialDotCy = '0';
let undoStack = [];
let redoStack = [];
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
當點擊一個branch-menu中的.branch-list時,其將高亮。
當已有一個.branch-list高亮:
1.如果點擊高亮的.branch-list,它將取消高亮。
2.如果點擊它前面的.branch-list,則高亮所在的.branch-item將調至所點擊的.branch-list的前面,然後取消高亮。
3.如果點擊它後面的.branch-list,則高亮所在的.branch-item將調至所點擊的.branch-list的後面,然後取消高亮。
4.當item移動時,currentBranch所對應的數字應發生變化,同時記入undo-stack。
只需要給出所更改的代碼位置,所有的代碼和注釋都要是英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
<!-- ONLY THE MODIFIED PARTS BELOW (all code/comments in English) -->
<!-- 1. CSS: Add highlight style for .branch-list (place inside <style> after the existing .branch-list rule) -->
.branch-list.highlighted {
background-color: #4CAF50;
color: white;
font-weight: bold;
border-radius: 4px;
padding: 2px 6px;
}
<!-- 2. JavaScript: Completely replace the showBranchMenu function (find the original showBranchMenu definition and replace the whole function) -->
/**
* Creates and displays the branch selection menu with reordering support via .branch-list clicks.
* Clicking a .branch-list highlights it.
* If one is already highlighted:
* 1. Clicking the highlighted one cancels highlight.
* 2. Clicking a branch-list in front moves the highlighted branch-item before it.
* 3. Clicking a branch-list behind moves the highlighted branch-item after it.
* 4. After move, currentBranch index is updated (same branch remains selected), undo-stack is saved.
* Menu stays open during reordering; numbers and active state update automatically.
*/
function showBranchMenu(anchor, parentNode, forkIndex, stepIndex) {
// Remove any existing menu
const existingMenu = document.querySelector('.branch-menu');
if (existingMenu) {
const isSame = existingMenu.dataset.owner === stepIndex.toString();
existingMenu.remove();
if (isSame) return; // Toggle off if clicking the same marker
}
const menu = document.createElement('div');
menu.className = 'branch-menu';
menu.dataset.owner = stepIndex;
let highlightedIdx = -1; // Local state for this menu instance
/**
* Rebuilds menu content in place (keeps menu open, updates order/numbers/highlight).
*/
function rebuildMenuContent() {
menu.innerHTML = '';
parentNode.v.forEach((child, idx) => {
const item = document.createElement('div');
item.className = 'branch-item';
// 1. Branch number
const sortNum = document.createElement('span');
sortNum.className = 'branch-list';
sortNum.innerText = `${idx + 1}`;
if (idx === highlightedIdx) {
sortNum.classList.add('highlighted');
}
sortNum.style.cursor = 'pointer';
// Click handler for reordering / highlight
sortNum.addEventListener('click', (e) => {
e.stopPropagation();
handleNumberClick(idx);
});
// 2. Move link (same as before)
const moveLink = document.createElement('span');
moveLink.className = 'branch-move-link';
if (currentBranch[forkIndex] === idx) {
moveLink.classList.add('active');
}
const cnMove = NotationConverter.toChinese(child.move);
moveLink.innerText = `${cnMove}`;
moveLink.onclick = () => {
updateBranchPath(forkIndex, idx);
jumpToStep(stepIndex);
menu.remove();
saveStateToUndo();
};
// 3. Delete icon (unchanged)
const delBtn = document.createElement('span');
delBtn.className = 'branch-delete-icon';
delBtn.innerHTML = '<svg viewBox="0 -3 24 21" style="width:16px;height:18px;fill:currentColor;"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>';
delBtn.onclick = (e) => {
e.stopPropagation();
const deletingCurrentBranch = (currentBranch[forkIndex] === idx);
parentNode.v.splice(idx, 1);
if (deletingCurrentBranch) {
currentBranch = currentBranch.slice(0, forkIndex);
currentBranch.push(0);
} else if (currentBranch[forkIndex] > idx) {
currentBranch[forkIndex]--;
}
renderRecordUI();
saveStateToUndo();
menu.remove();
const stepDivs = document.getElementsByClassName('ejceesrcdstep');
if (stepDivs[stepIndex - 1]) {
const newAnchor = stepDivs[stepIndex - 1].querySelector('.branch-marker');
if (newAnchor) showBranchMenu(newAnchor, parentNode, forkIndex, stepIndex);
}
};
item.appendChild(sortNum);
item.appendChild(moveLink);
item.appendChild(delBtn);
menu.appendChild(item);
});
}
/**
* Handles click on .branch-list (number).
*/
function handleNumberClick(clickedIdx) {
if (highlightedIdx === -1) {
// First click: highlight this branch
highlightedIdx = clickedIdx;
} else if (highlightedIdx === clickedIdx) {
// Click highlighted again: cancel
highlightedIdx = -1;
} else {
// Reorder logic
const currentBranchObj = parentNode.v[currentBranch[forkIndex]]; // remember selected branch object
const oldPos = highlightedIdx;
let newPos = (clickedIdx < highlightedIdx) ? clickedIdx : clickedIdx + 1;
// Move the branch
const moved = parentNode.v.splice(oldPos, 1)[0];
if (newPos > oldPos) newPos--;
parentNode.v.splice(newPos, 0, moved);
// Update currentBranch so the SAME branch remains selected
const newChoice = parentNode.v.findIndex(b => b === currentBranchObj);
currentBranch[forkIndex] = newChoice;
// After move, always cancel highlight (per spec)
highlightedIdx = -1;
// Save to undo-stack
saveStateToUndo();
}
// Refresh menu (order, numbers, highlight, active state)
rebuildMenuContent();
}
// Initial build
rebuildMenuContent();
anchor.appendChild(menu);
// Positioning (unchanged)
const recordContainer = document.querySelector('.ejceesrecord');
const parentRect = recordContainer.getBoundingClientRect();
const rect = anchor.getBoundingClientRect();
const beginTurn = historyFEN.fen.split(' ')[1] === 'w';
if (stepIndex % 2 || !beginTurn) {
menu.style.left = `${parentRect.left - rect.left}px`;
} else {
menu.style.right = `${rect.right - parentRect.right}px`;
}
// Close on outside click (unchanged)
const closeMenu = (e) => {
if (!menu.contains(e.target) && e.target !== anchor) {
menu.remove();
document.removeEventListener('click', closeMenu);
}
};
setTimeout(() => document.addEventListener('click', closeMenu), 10);
}https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
代码: 全选
<!-- ONLY THE MODIFIED PARTS BELOW (all code/comments in English) -->
<!-- JavaScript: Replace ONLY the handleNumberClick function inside showBranchMenu
(find the line "function handleNumberClick(clickedIdx) {" and replace the entire function body) -->
function handleNumberClick(clickedIdx) {
let didReorder = false;
if (highlightedIdx === -1) {
// First click: highlight this branch
highlightedIdx = clickedIdx;
} else if (highlightedIdx === clickedIdx) {
// Click highlighted again: cancel
highlightedIdx = -1;
} else {
// Reorder logic
const currentBranchObj = parentNode.v[currentBranch[forkIndex]]; // remember selected branch object
const oldPos = highlightedIdx;
let newPos = (clickedIdx < highlightedIdx) ? clickedIdx : clickedIdx + 1;
// Move the branch
const moved = parentNode.v.splice(oldPos, 1)[0];
if (newPos > oldPos) newPos--;
parentNode.v.splice(newPos, 0, moved);
// Update currentBranch so the SAME branch remains selected
const newChoice = parentNode.v.findIndex(b => b === currentBranchObj);
currentBranch[forkIndex] = newChoice;
// After move, always cancel highlight
highlightedIdx = -1;
didReorder = true;
}
if (didReorder) {
// Full refresh to update external branch-markers (e.g. 2/2 becomes 1/2)
// This rebuilds all .ejceesrcdstep elements with the new order numbers
renderRecordUI();
saveStateToUndo();
menu.remove();
const stepDivs = document.getElementsByClassName('ejceesrcdstep');
if (stepDivs[stepIndex - 1]) {
const newAnchor = stepDivs[stepIndex - 1].querySelector('.branch-marker');
if (newAnchor) showBranchMenu(newAnchor, parentNode, forkIndex, stepIndex);
}
} else {
// Just highlight/cancel: keep menu open and refresh numbers inside menu
rebuildMenuContent();
}
}https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: Majestic-12 [Bot] 和 2 访客
