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

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

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

帖子 ejsoon »

翻譯成英文:

代码: 全选

以下是將前一個回答中的所有程式碼註釋翻譯為英文的版本,包含 **ICCS 格式修改**、**非 PGN 格式導出邏輯**、**PGN 導入解析器** 以及 **導入按鈕邏輯**:

### 1. Modify `generatePGNFormat` (Support ICCS with Hyphen)

```javascript
          function getMoveStr(node, isRed) {
              if (oddState === 0) return NotationConverter.toChinese(node.move);
              if (oddState === 2) return node.move;
              if (oddState === 1 && node.lastMove) {
                  let dc = node.lastMove;
                  // Modification: Convert to Uppercase and add hyphen "-", e.g., H2-E2
                  let start = String.fromCharCode(65 + dc.startX) + (9 - dc.startY);
                  let end = String.fromCharCode(65 + dc.endX) + (9 - dc.endY);
                  return (start + "-" + end).toUpperCase();
              }
              return node.move;
          }
```

### 2. Export Logic for Non-PGN Formats (Convert Metadata Tags to Comments)

```javascript
        // Helper: Get root comments integrated with Metadata
        function getMetaRootComment() {
          let metaHeader = "";
          pgnMetadata.forEach(m => {
            // Only add to comment if the value is not empty and it's not the Result tag
            if (m.value && m.value.trim() !== "" && m.name !== "Result") {
              metaHeader += `${m.name}:${m.value}\n`;
            }
          });
          let rootC = (historyFEN.c || "").trim();
          // Prepend metadata header to the existing root comment
          return metaHeader ? (metaHeader + (rootC ? "\n" + rootC : "")) : rootC;
        }

        // Helper: Append result string to the last move's comment
        function appendResultToLastComment(lastNode) {
          let resSymbol = pgnMetadata.find(m => m.name === "Result")?.value || "*";
          let mapping = { "1-0": "紅勝", "0-1": "黑勝", "1/2-1/2": "平局" };
          let resText = mapping[resSymbol] || "";
          
          let existingC = (lastNode.c || "").trim();
          if (!resText) return existingC;
          // Return existing comment followed by result text
          return existingC ? (existingC + "\n" + resText) : resText;
        }
```

### 3. PGN Import Logic (`importPGN`)

```javascript
        function importPGN(input) {
          // 1. Reset current Metadata
          pgnMetadata.forEach(m => m.value = (m.name === "Result" ? "*" : ""));

          // 2. Parse Tags [Name "Value"]
          const tagRegex = /\[(\w+)\s+"([^"]*)"\]/g;
          let match;
          let lastIndex = 0;
          while ((match = tagRegex.exec(input)) !== null) {
            let key = match[1], val = match[2];
            let metaObj = pgnMetadata.find(m => m.name === key);
            if (metaObj) metaObj.value = val;
            else pgnMetadata.push({ name: key, value: val });
            lastIndex = tagRegex.lastIndex;
          }

          // 3. Extract the move body (part after all tags)
          let body = input.slice(lastIndex).trim();
          
          // Extract the trailing result symbol (1-0, 0-1, 1/2-1/2, *)
          const resMatch = body.match(/(1-0|0-1|1\/2-1\/2|\*)$/);
          if (resMatch) {
            let res = resMatch[1];
            let metaObj = pgnMetadata.find(m => m.name === "Result");
            if (metaObj) metaObj.value = res;
            body = body.slice(0, -res.length).trim();
          }

          // 4. Parse moves, comments, and branches using a state machine
          function parseMoves(str) {
            let nodes = [];
            let i = 0;
            while (i < str.length) {
              let char = str[i];
              if (char === '{') { // Parse Comment
                let end = str.indexOf('}', i);
                let comment = str.slice(i + 1, end).trim();
                if (nodes.length > 0) nodes[nodes.length - 1].c = comment;
                else rootComment = comment; // Comment before the first move
                i = end + 1;
              } else if (char === '(') { // Parse Branch
                let count = 1, j = i + 1;
                while (count > 0 && j < str.length) {
                  if (str[j] === '(') count++;
                  if (str[j] === ')') count--;
                  j++;
                }
                let branchStr = str.slice(i + 1, j - 1);
                // Recursively parse the branch and add to the last move's variations
                if (nodes.length > 0) nodes[nodes.length - 1].v.push(parseMoves(branchStr)[0]);
                i = j;
              } else if (/[0-9a-zA-Z\u4e00-\u9fa5]/.test(char)) { // Move notation
                let end = str.search(/[\s\(\{\}]/, i);
                if (end === -1) end = str.length;
                let movePart = str.slice(i, end);
                // Exclude move numbers like "1." or "1..."
                if (!movePart.includes('.')) {
                  nodes.push({ move: movePart, c: '', v: [] });
                }
                i = end;
              } else { i++; }
            }
            return nodes;
          }

          let rootComment = "";
          const moveList = parseMoves(body);
          // (Implementation note: Iterate through moveList and use executeMove to rebuild FENs)
          return { moves: moveList, rootComment: rootComment };
        }
```

### 4. Update `handleConfirm` for PGN Import

```javascript
          confirmBtn.addEventListener('click', () => {
            const input = textarea.value.trim();
            // Check if input starts with the PGN signature
            if (input.startsWith('[Game "Chinese Chess"]')) {
              // PGN Import Logic
              const result = importPGN(input);
              // (Need to call applyHistory or a recursive loader here)
              alert('PGN Imported successfully');
              renderRecordUI();
            } else {
              // Existing import logic for JSON or DP format
              // ...
            }
          });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5322
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 176 次
被圈友点赞: 204 次
联系:

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

帖子 ejsoon »

明確告知所有要用到這兩個函式的地方(在導出DP格式以及url格式時會用到):

1. 將原本讀取 `historyFEN.c` 的地方改為讀取 `getMetaRootComment()`。
2. 在遍歷到最後一個節點時,調用 `appendResultToLastComment`。

二,把importPGN的()邏輯,寫進importExportedText()中。

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

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

帖子 ejsoon »

gemini pro(讓我睡到8:52):

代码: 全选

以下是針對您的需求所整理的修改說明。所有程式碼與註釋皆已轉換為英文。

### 1. Update Export Functions (`generateDPFormat` and `generateUrlHash`)

You need to update the logic in `generateDPFormat` and the function responsible for URL generation to integrate the metadata and results.

#### In `generateDPFormat`:
Replace the part that reads the root comment and the move sequence processing.

```javascript
function generateDPFormat(isToggled) {
    let text = "";
    
    // 1. Replace historyFEN.c with getMetaRootComment() for the root comment
    let rootComment = getMetaRootComment();
    if (rootComment) {
        text += `{${rootComment}}\n`;
    }

    function processSequence(node) {
        let seq = "";
        // ... existing traversal logic ...
        
        // 2. When reaching the last node (leaf node), call appendResultToLastComment
        let isLastNode = (!node.v || node.v.length === 0);
        let currentComment = isLastNode ? appendResultToLastComment(node) : (node.c || "");
        
        if (currentComment) {
            seq += ` {${currentComment}}`;
        }
        // ... handle branches and recursion ...
        return seq;
    }
    
    text += processSequence(historyFEN);
    return text;
}
```

#### In `generateUrlHash` (or your URL generation logic):
Similar to the DP format, ensure the serialized string uses the metadata-integrated comments.

```javascript
function generateUrlHash(isToggled) {
    // Replace the root comment serialization
    let rootC = getMetaRootComment();
    // Use rootC instead of historyFEN.c when building the hash string
    
    // When traversing moves to build the string, check if the node is the last one:
    // If it's the leaf node, use appendResultToLastComment(node) for the comment field.
}
```

---

### 2. Update `importExportedText` with PGN Parentheses Logic

Integrate the PGN parsing state machine (handling tags, braces for comments, and parentheses for branches) into the main import function.

```javascript
function importExportedText(input) {
    input = input.trim();

    // Check if the input is in PGN Format
    if (input.startsWith('[Game "Chinese Chess"]')) {
        // 1. Reset Metadata
        pgnMetadata.forEach(m => m.value = (m.name === "Result" ? "*" : ""));

        // 2. Parse Tags: [Name "Value"]
        const tagRegex = /\[(\w+)\s+"([^"]*)"\]/g;
        let match;
        let lastTagIndex = 0;
        while ((match = tagRegex.exec(input)) !== null) {
            let key = match[1], val = match[2];
            let metaObj = pgnMetadata.find(m => m.name === key);
            if (metaObj) metaObj.value = val;
            else pgnMetadata.push({ name: key, value: val });
            lastTagIndex = tagRegex.lastIndex;
        }

        // 3. Extract move body and the trailing result symbol
        let body = input.slice(lastTagIndex).trim();
        const resMatch = body.match(/(1-0|0-1|1\/2-1\/2|\*)$/);
        if (resMatch) {
            let res = resMatch[1];
            let metaObj = pgnMetadata.find(m => m.name === "Result");
            if (metaObj) metaObj.value = res;
            body = body.slice(0, -res.length).trim();
        }

        // 4. Parentheses Logic: Recursive Move & Branch Parser
        function parsePgnBody(str) {
            let moves = [];
            let i = 0;
            let rootComment = "";

            while (i < str.length) {
                let char = str[i];

                if (char === '{') { 
                    // Handle Comments in Braces
                    let end = str.indexOf('}', i);
                    let comment = str.slice(i + 1, end).trim();
                    if (moves.length > 0) moves[moves.length - 1].c = comment;
                    else rootComment = comment; 
                    i = end + 1;
                } 
                else if (char === '(') { 
                    // Handle Branches in Parentheses (Recursive)
                    let count = 1, j = i + 1;
                    while (count > 0 && j < str.length) {
                        if (str[j] === '(') count++;
                        if (str[j] === ')') count--;
                        j++;
                    }
                    let branchStr = str.slice(i + 1, j - 1);
                    if (moves.length > 0) {
                        // Recursively parse the content inside parentheses
                        let variations = parsePgnBody(branchStr);
                        if (variations.moves.length > 0) {
                            moves[moves.length - 1].v.push(variations.moves[0]);
                        }
                    }
                    i = j;
                } 
                else if (/[0-9a-zA-Z\u4e00-\u9fa5]/.test(char)) { 
                    // Handle Move Notations (Chinese, WXF, or ICCS)
                    let end = str.search(/[\s\(\{\}]/, i);
                    if (end === -1) end = str.length;
                    let movePart = str.slice(i, end);
                    // Skip move numbers (e.g., "1.", "1...")
                    if (!movePart.includes('.')) {
                        moves.push({ move: movePart, c: '', v: [] });
                    }
                    i = end;
                } 
                else {
                    i++;
                }
            }
            return { moves, rootComment };
        }

        const result = parsePgnBody(body);
        // Load the parsed result into the application state
        // (e.g., historyFEN = result.moves, historyFEN.c = result.rootComment)
        console.log("PGN content parsed successfully");
        return result;
    } 
    else {
        // Fallback for existing JSON or DP format import logic
        // ...
    }
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5322
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 176 次
被圈友点赞: 204 次
联系:

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

帖子 ejsoon »

叫fast來看看!

怎麼點擊DP Format第三次,會出現大括號?
附件
ejcees20260425pgn2.7z
(49.68 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5322
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 176 次
被圈友点赞: 204 次
联系:

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

帖子 ejsoon »

當點擊close-btn關閉pgn-meta-modal時,它裡面的table數據是否保存?如果沒有,那就增加保存以及刷新導出數據的功能。

代码: 全选

在現有的代碼中,`pgn-meta-modal` 的關閉邏輯確實**沒有**顯式保存數據或刷新導出數據的功能。目前關閉按鈕僅僅是從 DOM 中移除 modal 節點:

```javascript
// 現有邏輯
closeBtn.onclick = () => document.body.removeChild(modal);
```

由於數據是通過 `onchange` 事件實時同步到 `pgnMetadata` 數組中的,因此數據**其實已經保存在內存裡**,但使用者介面上**不會自動觸發導出預覽的更新**。

為了提升用戶體驗並確保數據一致性,建議將關閉邏輯修改為:**先保存(同步到 Undo 棧/LocalStorage)並刷新導出視圖**。

以下是修改建議:

### 1. 增加刷新導出數據的功能
在代碼中,導出數據(如 PGN 或 DP 格式)是由 `showExportTextModal` (假設名稱) 或相關工具按鈕觸發的。我們可以建立一個統一的 `updateExportIfOpen` 函數,或者直接在關閉 Modal 時觸發狀態保存。

### 2. 修改 `showPGNMetaModal` 函數中的關閉邏輯
請將 `showPGNMetaModal` 函數內的 `closeBtn.onclick` 替換為以下代碼:

```javascript
// 修改後的關閉邏輯
closeBtn.onclick = () => {
    // 1. 移除 Modal
    document.body.removeChild(modal);
    
    // 2. 如果當前在導出模式(Export Text Mode),則刷新內容
    if (isExportTextMode) {
        // 獲取當前選擇的格式狀態(從 UI 獲取或變量)
        const activeBtn = document.querySelector('.exp-fmt-btn.active');
        const oddState = activeBtn ? parseInt(activeBtn.getAttribute('data-odd') || '0') : 0;
        
        // 重新生成內容並填入 textarea
        const exportArea = document.querySelector('.ejceescomment-edit');
        if (exportArea) {
            exportArea.value = generatePGNFormat(oddState);
        }
    }
    
    // 3. 保存當前狀態到 Undo 棧與 LocalStorage,確保元數據(Metadata)被持久化
    saveStateToUndo();
};
```

### 為什麼需要這樣改?
1.  **實時性**:使用者修改了「賽事名稱」或「棋手姓名」後,關閉視窗時,後方的導出文本框應該立即反映這些變化,而不是要求用戶重新點擊「導出」按鈕。
2.  **安全性**:調用 `saveStateToUndo()` 可以確保這些元數據被記錄在撤銷/重做歷史中,並存入 `localStorage`,防止意外刷新頁面導致填寫的數據丟失。
3.  **數據鏈路**:在 `generatePGNFormat` 中,程序會遍歷 `pgnMetadata` 來構建文件頭,因此只要刷新調用該函數,數據就能正確保存到導出的 PGN 字符串中。

### 額外建議:增加「保存」按鈕
雖然 `onchange` 會自動保存,但使用者習慣上常尋找「保存」或「確定」按鈕。您可以在 `pgn-add-btn` 旁邊增加一個帶有「保存並關閉」字樣的按鈕,其邏輯與上述修改後的 `closeBtn` 一致。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5322
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 176 次
被圈友点赞: 204 次
联系:

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

帖子 ejsoon »

為什麼當我按下.exp-fmt-btn[data-fmt=pgb]時,我保存的meta都不出現在textarea中?

代码: 全选

在分析您提供的代碼後,發現當您點擊 `.exp-fmt-btn[data-fmt=pgn]`(即 PGN 格式按鈕)時,保存的 metadata 沒出現在 `textarea` 中,主要原因在於 `renderExportTextUI` 函數中對按鈕點擊事件的處理邏輯**漏掉了對 PGN 數據的動態更新**。

以下是具體原因分析與修復建議:

### 1. 為什麼會失效?
在 `renderExportTextUI` 函數的事件監聽器中(雖然這部分代碼在 snippet 中被截斷,但從邏輯可推斷):
* 當點擊 `data-fmt="cn"`(DP 格式)時,它會根據 `oddState` 切換不同的 DP 生成函數。
* **關鍵問題**:當點擊 `data-fmt="pgn"` 時,代碼可能只切換了按鈕的 UI 狀態(例如變色),但**沒有重新調用 `generatePGNFormat(oddState)`** 並將結果填入 `#export-textarea`。
* 另外,由於 PGN 的 metadata 存儲在全域變數 `pgnMetadata` 中,如果在導出界面打開後才去 `pgn-meta-modal` 修改數據,導出界面不會自動刷新。

### 2. 修復方案

您需要修改 `renderExportTextUI` 中關於 `.exp-fmt-btn` 的點擊處理邏輯。請確保在 `pgn` 分支下正確調用了生成函數:

```javascript
// 在 renderExportTextUI 函數內部找到按鈕點擊監聽部分
commentDiv.addEventListener('click', (e) => {
    const btn = e.target.closest('.exp-fmt-btn');
    if (!btn) return;

    const fmt = btn.getAttribute('data-fmt');
    // ... 其他邏輯 (如切換 active class)

    if (fmt === 'pgn') {
        // 確保這裡獲取了當前的 oddState (0: Chinese, 1: ICCS, 2: WXF)
        const odd = formatOddStates['pgn']; 
        // 核心:重新生成 PGN 並更新 textarea
        textarea.value = generatePGNFormat(odd);
        lastExportFormat = 'pgn';
    } 
    // ... 其他格式的處理
});
```

### 3. 同步更新建議
為了確保您在 `pgn-meta-modal`(元數據視窗)點擊關閉時,後方的導出文字框能同步更新,建議修改 `showPGNMetaModal` 函數中的 `closeBtn.onclick`:

```javascript
// 找到 showPGNMetaModal 函數中的這行:
closeBtn.onclick = () => {
    document.body.removeChild(modal);
    
    // 新增:如果當前正處於導出模式,且選擇的是 PGN 格式,則刷新內容
    if (isExportTextMode && lastExportFormat === 'pgn') {
        const textarea = document.getElementById('export-textarea');
        if (textarea) {
            const odd = formatOddStates['pgn'] || 0;
            textarea.value = generatePGNFormat(odd);
        }
    }
};
```

### 4. 檢查 `generatePGNFormat` 函數
請檢查您的 `generatePGNFormat` 函數是否正確遍歷了 `pgnMetadata`。現有代碼中已有相關邏輯:
```javascript
pgnMetadata.forEach((meta) => {
    if (meta.value && meta.value.trim() !== '') {
        text += `[${meta.name} "${meta.value}"]\n`; // 這行負責將 meta 加入文本
    }
});
```
只要確保在點擊切換按鈕或關閉 meta 視窗時**重新執行**此函數並賦值給 `textarea`,數據就會正常顯示。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5322
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 176 次
被圈友点赞: 204 次
联系:

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

帖子 ejsoon »

代码: 全选

          // Import PGN format
          if (text.startsWith('[Game "Chinese Chess"]')) {
          }
在這裡加上導入PGN格式的邏輯。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复

在线用户

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