將製作ejcees(中國象棋打譜程式)

分享和讨论Javascript相关的话题
回复
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

附件
ejcees20260407arrange99977752.7z
(46.25 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

更改前面的一個需求:當處於「文本導出」狀態或點擊複製按鈕時,如果不處於範圍選取狀態,只有engine的範圍是從開頭到currentStep,其它三個仍然跟之前一樣導出全部。

只需給出所要修改的地方 ,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

gemini pro:

代码: 全选

Here are the modifications to implement the requested feature. We will add a parameter to the `getGamePath` function to conditionally limit the end step, and then apply this parameter only when generating the Engine format.

### 1. Update the `getGamePath` function
Find the `getGamePath()` function definition and replace it completely with the following code. This adds the `limitToCurrentStep` parameter, defaulting to `false` for `cn`, `en`, and `url` formats to export the full depth, but allowing the Engine format to stop at `currentStepIndex`.

**Replace this block:**
```javascript
        // Extract the linear path corresponding to the currently selected branch
        function getGamePath() {
            let newStart = 0;
            // Modification: Default end is now currentStepIndex instead of full depth
            let newEnd = currentStepIndex;
            let newHistory = {
                fen: historyFEN.fen,
                move: null,
                lastMove: null,
                c: historyFEN.c,
                v: []
            };
            
            if (isRangeMode) {
                // Restore original full depth default if in range mode but end isn't picked yet
                newEnd = getPathDepth(historyFEN, currentBranch) - 1;
// ... (rest of the function)
```

**With this:**
```javascript
        // Extract the linear path corresponding to the currently selected branch
        function getGamePath(limitToCurrentStep = false) {
            let newStart = 0;
            // Modification: Default to currentStepIndex only if explicitly requested (for engine format)
            let newEnd = limitToCurrentStep ? currentStepIndex : getPathDepth(historyFEN, currentBranch) - 1;
            let newHistory = {
                fen: historyFEN.fen,
                move: null,
                lastMove: null,
                c: historyFEN.c,
                v: []
            };
            if (isRangeMode) {
                if (rangeStart === null && rangeEnd === null) {
                    newStart = currentStepIndex;
                    newEnd = currentStepIndex;
                } else if (rangeStart !== null && rangeEnd !== null && rangeStart === rangeEnd) {
                    newStart = rangeStart;
                    newEnd = rangeEnd;
                } else if (rangeStart !== null && rangeEnd === null) {
                    newStart = rangeStart;
                } else if (rangeStart !== null && rangeEnd !== null && rangeStart !== rangeEnd) {
                    newStart = Math.min(rangeStart, rangeEnd);
                    newEnd = Math.max(rangeStart, rangeEnd);
                }
                const startNode = getNodeAtStep(newStart);
                newHistory = {
                    fen: startNode.fen,
                    move: null,
                    lastMove: null,
                    c: startNode.c,
                    v: []
                };
            }
            let curr = newHistory;
            for (let i = newStart + 1; i <= newEnd; i++) {
                const node = getNodeAtStep(i);
                let newNode = {
                    fen: node.fen,
                    move: node.move,
                    lastMove: node.lastMove,
                    c: node.c,
                    v: []
                };
                curr.v.push(newNode);
                curr = newNode;
            }
            return newHistory;
        }
```

### 2. Update `performCopy` to pass `true` for Engine format
Locate the global `performCopy()` function that you added in the previous step. Find the `else if (fmt === 'engine') {` block and update the `getGamePath` call.

**Replace this line:**
```javascript
            } else if (fmt === 'engine') {
                let path = getGamePath();
```

**With this:**
```javascript
            } else if (fmt === 'engine') {
                // Modification: Pass true to limit engine format to currentStepIndex
                let path = getGamePath(true);
```

### 3. Update `renderExportTextUI` to pass `true` for Engine format
Locate the `renderExportTextUI()` function (around the area where the `urlTextToggled` is defined). Find the section where the Engine text is generated and update the `getGamePath` call.

**Replace this block:**
```javascript
            const urlText = generateUrlHash(false);
            const urlTextToggled = generateUrlHash(true);

            // Generate UCCI Engine Format String
            let path = getGamePath();
```

**With this:**
```javascript
            const urlText = generateUrlHash(false);
            const urlTextToggled = generateUrlHash(true);

            // Generate UCCI Engine Format String
            // Modification: Pass true to limit engine format to currentStepIndex
            let path = getGamePath(true);
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

增加功能:當不處於「編輯局面、編輯評論、導出文本、改動間隔及移動時間」狀態時,按下空白鍵,就等於是按下「自動播放」按鈕。

代码: 全选

Here is the modification to add the **Spacebar** as a shortcut for the **Auto Play** button.

### Update the `keydown` Event Listener
Locate the main keyboard event listener `document.addEventListener('keydown', (e) => {` (around line `1854`). Insert the logic for the Spacebar inside the main block, ensuring it only triggers when the specified states are inactive.

**Search for this section:**
```javascript
        document.addEventListener('keydown', (e) => {
            if (isExportTextMode || isEditingComment || isEditMode || isEditingSettings) return;
```

**Replace that specific check and add the Spacebar logic like this:**
```javascript
        document.addEventListener('keydown', (e) => {
            // Check if any modal or edit mode is active
            const isExportModalOpen = document.querySelector('.ejcees-export-modal') !== null;
            const isRestrictedMode = isExportTextMode || isEditingComment || isEditMode || isEditingSettings || isExportModalOpen;

            // Feature: Spacebar for Auto Play
            if (e.code === 'Space' || e.key === ' ') {
                if (!isRestrictedMode) {
                    e.preventDefault(); // Prevent page scrolling
                    const autoPlayBtn = document.getElementById('btn-auto-play');
                    if (autoPlayBtn) {
                        autoPlayBtn.click();
                    }
                    return;
                }
            }

            if (isRestrictedMode) return;

            if (e.ctrlKey) {
                // ... (existing Ctrl shortcut logic)
```

### Explanation of the change:
* **`e.code === 'Space'`**: This reliably detects the spacebar across different keyboard layouts.
* **`e.preventDefault()`**: This is crucial because the default browser behavior for the spacebar is to scroll the page down; this line prevents that.
* **State Check**: It uses the same logic as your other shortcuts to ensure you don't accidentally toggle auto-play while typing a comment or editing settings.
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

附件
ejcees20260407engineStep99977753.7z
(46.37 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5002
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 173 次
被圈友点赞: 202 次
联系:

Re: 將製作ejcees(中國象棋打譜程式)

帖子 ejsoon »

我有一個html+css+js程式,現在我想把它做成一個js,當頁面存在至少一個.ejcees時,它將把css放進去,然後每一個.ejcees都用js來操作dom以及賦予事件。每個.ejcees都互不影響,包括undo-redo。

應該要怎麼做?是否要把js對象化?

代码: 全选

要將原本的 HTML/CSS/JS 程式轉換為一個可複用的組件,並支援多個實例(例如頁面上有多個 `.ejcees` 容器且互不干擾),**將 JS「對象化」(物件導向,Object-Oriented)是絕對必要的**。

以下是實現這個目標的建議架構與步驟:

### 1. 核心觀念:封裝與範圍限制
在目前的代碼中,許多變量是全域的(如 `historyFEN`, `currentStepIndex` 等),且 DOM 操作是使用 `document.querySelector`。要達成多個實例並存,必須:
* **使用 Class (類別)**:將所有邏輯封裝在一個 `class Ejcees` 中。
* **Scoped DOM**:不要用 `document` 查找,而是從實例傳入的 `container` 節點開始查找。
* **私有狀態**:每個實例擁有自己的一套 `undoStack`, `redoStack`。

### 2. 實作步驟建議

#### A. CSS 的處理
為了讓 CSS 隨 JS 自動載入,你可以將原本 `<style>` 標籤中的內容 存為一個 JS 字串。
```javascript
const EJCEES_CSS = `
  .ejceesmain { display: flex; ... } 
  /* 將原本的所有 CSS 放進這裡 */
`;

function injectCSS() {
    if (!document.getElementById('ejcees-styles')) {
        const style = document.createElement('style');
        style.id = 'ejcees-styles';
        style.textContent = EJCEES_CSS;
        document.head.appendChild(style);
    }
}
```

#### B. 將 JS 對象化 (Class 結構)
你需要將 `DOMContentLoaded` 中的邏輯 搬移到 Class 的 `constructor` 或 `init` 方法中。

```javascript
class Ejcees {
    constructor(element) {
        this.container = element; // 這是對應的 .ejcees 元素
        this.historyFEN = { fen: INITIAL_FEN, v: [] }; // 獨立的狀態
        this.undoStack = []; // 獨立的撤銷棧
        this.currentStepIndex = 0;
        
        this.init();
    }

    init() {
        // 1. 生成內部 HTML 結構 (原本 body 裡的 ejceesmain 內容)
        this.container.innerHTML = `...原本的 HTML 結構...`;
        
        // 2. 獲取內部元素(使用 this.container.querySelector)
        this.svgElement = this.container.querySelector('.ejceespb');
        this.outputElement = this.container.querySelector('.ejceesoutput');
        
        // 3. 綁定事件
        this.bindEvents();
        
        // 4. 初始化棋盤狀態
        this.loadFEN(this.historyFEN.fen);
    }

    bindEvents() {
        // 使用箭頭函數確保 this 指向實例
        this.svgElement.addEventListener('click', (e) => this.handleBoardClick(e));
        this.container.querySelector('#tool-undo').addEventListener('click', () => this.undo());
        // ...其他按鈕事件
    }

    undo() {
        if (this.undoStack.length <= 1) return;
        // 實作該實例專屬的 undo 邏輯
    }

    // 將原本的所有 function (如 loadFEN, renderRecordUI) 變成 class 的 method
    loadFEN(fenStr) { ... }
}
```

#### C. 主控入口
在 JS 檔案的最下方,檢查頁面上是否存在目標元素並實例化:

```javascript
function initAllEjcees() {
    const elements = document.querySelectorAll('.ejcees');
    if (elements.length > 0) {
        injectCSS(); // 載入 CSS
        elements.forEach(el => new Ejcees(el));
    }
}

// 頁面載入後執行
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initAllEjcees);
} else {
    initAllEjcees();
}
```

### 3. 注意事項
1.  **ID 衝突**:原本 HTML 中使用了許多 `id`(如 `id="tool-undo"`)。在多個實例中,`id` 必須唯一。建議將 HTML 中的 `id` 改為 `class`,並在 JS 中透過 `this.container.querySelector('.tool-undo')` 來選取。
2.  **LocalStorage**:原本代碼會將狀態存入 `localStorage.setItem('ejcees_saved_state', ...)`。如果頁面有多個棋盤,它們會互相覆蓋。建議給每個實例一個唯一的 ID(例如 `data-id`),存儲時鍵名改為 `ejcees_state_${id}`。
3.  **Global Event Listeners**:代碼中有對 `document` 綁定 `keydown` 事件。這需要小心處理,否則按下鍵盤時所有棋盤都會同時動作。建議檢查 `document.activeElement` 是否在 `this.container` 內部,才觸發該實例的邏輯。

透過這種「類別封裝」的方式,你可以輕鬆在同一個網頁中放入多個象棋編輯器,且彼此的棋譜、編輯模式與撤銷紀錄都會完全獨立。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 5 访客