分享和讨论Javascript相关的话题
回复
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

在wordpress中,如何寫php,拿到指定的媒體庫文件的地址?即使這個文件改名改路徑也能拿到?

代码: 全选

在 WordPress 中,要確保「即便檔案改名或路徑變動」依然能拿到正確的地址,唯一的準則就是:**永遠使用 Attachment ID(附件 ID)**。

WordPress 的媒體庫文件本質上是一種名為 `attachment` 的 **Post Type**。當你在後台修改圖片的標題或透過外掛重新命名檔案時,其 ID 是永遠不變的。

以下是實現這一點的幾種最佳實踐:

---

### 1. 最核心的方法:`wp_get_attachment_url()`

只要你有該文件的 ID,無論它的實體路徑怎麼變(例如從 `/2023/01/` 移到 `/2024/05/`),這個函式都能動態從資料庫中抓取當前最新的 URL。

```php
// 假設附件 ID 為 520
$attachment_id = 520;
$url = wp_get_attachment_url($attachment_id);

if ($url) {
    echo $url;
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

如何在wordpress的媒體庫中顯示文件大小?

代码: 全选

要在 WordPress 媒體庫中顯示文件大小,最簡單且不依賴外掛的方法是透過 PHP 代碼。這可以讓你直接在媒體庫的列表視圖中看到每個檔案佔用的空間(例如 1.2 MB)。

你可以將以下代碼添加到主題的 `functions.php` 檔案中:

---

### PHP 代碼實現

```php
/**
 * 在媒體庫列表增加「大小」欄位標題
 */
add_filter('manage_media_columns', 'add_file_size_column');
function add_file_size_column($columns) {
    $columns['file_size'] = '檔案大小';
    return $columns;
}

/**
 * 抓取並顯示每個媒體文件的實際大小
 */
add_action('manage_media_custom_column', 'display_file_size_column', 10, 2);
function display_file_size_column($column_name, $post_id) {
    if ($column_name === 'file_size') {
        // 獲取檔案路徑
        $file_path = get_attached_file($post_id);
        
        if (file_exists($file_path)) {
            // 獲取位元組數並轉換為易讀格式 (KB, MB)
            $size = filesize($file_path);
            echo size_format($size); 
        } else {
            echo '未知';
        }
    }
}

/**
 * 讓「大小」欄位支援點擊排序
 */
add_filter('manage_upload_sortable_columns', 'make_file_size_column_sortable');
function make_file_size_column_sortable($columns) {
    $columns['file_size'] = 'file_size';
    return $columns;
}
```

---

### 代碼功能詳解:

1.  **`manage_media_columns`**: 這會在媒體庫的表格標題列插入一個新的「檔案大小」欄位。
2.  **`get_attached_file($post_id)`**: 這是關鍵。它會根據我們之前聊過的 **Attachment ID** 找到伺服器上的實體路徑。
3.  **`size_format()`**: 這是 WordPress 內建的函數,會自動幫你把 $1048576$ bytes 轉換成 `1 MB` 這種人類看得懂的格式,非常方便。
4.  **排序功能**: 最後一段代碼讓你可以點擊標題,按檔案大小進行升冪或降冪排列,方便找出佔用空間的大檔案。

---

### 如何查看效果?

1.  保存 `functions.php` 後,進入後台 **媒體 > 媒體庫**。
2.  **重要:** 必須切換到 **「列表視圖 (List View)」**(點擊左上角的清單圖示),而不是預設的網格圖示。
3.  你就會看到多出一欄「檔案大小」。

### 補充:如果不想寫代碼?
如果你偏好使用外掛,可以搜尋 **"Filesize in Media Library"** 或 **"Admin Columns"**,這些外掛也提供類似的功能,並支援更多自定義欄位。

你目前是想清理網站空間,還是單純想在選取檔案時有個參考?
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

寫一個wordpress的short_code。

代码: 全选

寫一個wordpress的short_code。

一,short_code的名稱是「ejcees」,用法是[ejcees mode=single/mini]ejcees_content[/ejcees]。

二,它的html內容是:
    <div class="ejcees" mode="single/mini">ejcees_content</div>

三,當頁面上存在至少一個[ejcees]時,需要引入以下js,只需引入一次:
    <script src="ejceesobj.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const containers = document.querySelectorAll('.ejcees');
        containers.forEach(container => {
          const mode = container.getAttribute('mode');
          new Ejcees(container, mode);
        });
      });
    </script>

其中「<script src="ejceesobj.js"></script>」要改為:
$attachment_id = 19088;
$url = wp_get_attachment_url($attachment_id);
wp_enqueue_script('ejcees-obj-js')

四,ejcees_content要do_short_code以使其它short_code能用。

所有的代碼和注釋都要使用英文。


gemini fast:

代码: 全选

To implement this functionality, you can add the following code to your theme's `functions.php` file or a custom plugin. 

This implementation uses `wp_enqueue_script` to handle the JS file and `wp_add_inline_script` to inject the initialization logic. This ensures the scripts are only loaded once and only when the shortcode is actually present on the page.

```php
/**
 * Register the [ejcees] shortcode
 */
function register_ejcees_shortcode($atts, $content = null) {
    // 1. Define default attributes and extract them
    $options = shortcode_atts(array(
        'mode' => 'single', // Default mode
    ), $atts);

    // 2. Enqueue the external JS file via Attachment ID
    $attachment_id = 19088;
    $url = wp_get_attachment_url($attachment_id);

    if ($url) {
        // Register and enqueue the script
        wp_enqueue_script('ejcees-obj-js', $url, array(), null, true);

        // 3. Add the initialization inline script (runs only once)
        $inline_init_script = "
            document.addEventListener('DOMContentLoaded', () => {
                const containers = document.querySelectorAll('.ejcees');
                containers.forEach(container => {
                    const mode = container.getAttribute('mode');
                    if (typeof Ejcees !== 'undefined') {
                        new Ejcees(container, mode);
                    }
                });
            });
        ";
        wp_add_inline_script('ejcees-obj-js', $inline_init_script);
    }

    // 4. Process nested shortcodes within content
    $processed_content = do_shortcode($content);

    // 5. Return the HTML output
    return sprintf(
        '<div class="ejcees" mode="%s">%s</div>',
        esc_attr($options['mode']),
        $processed_content
    );
}
add_shortcode('ejcees', 'register_ejcees_shortcode');
```

### Key Technical Details:

* **Script Management:** Using `wp_enqueue_script` ensures that even if you use the shortcode multiple times on one page, the `ejceesobj.js` file is only requested by the browser once.
* **Inline Logic:** `wp_add_inline_script` attaches your custom DOMContentLoaded logic directly after the source file, maintaining the correct execution order.
* **Security:** `esc_attr()` is used on the `mode` attribute to prevent XSS (Cross-Site Scripting) if a user enters malicious data into the shortcode attribute.
* **Nested Content:** `do_shortcode($content)` allows you to place other shortcodes (like galleries or buttons) inside `[ejcees]...[/ejcees]`.

---

### Usage Example:
In your WordPress editor, you can now use it like this:

`[ejcees mode="mini"]This is my content or [another_shortcode][/ejcees]`
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

增加兩個參數:
一,[ejcees width="360"]

在html中就是style="width: 360px"

二,[ejcees inline]或[ejcees block]

在html中就是style="display: inline-block"或style="display: block; margin: 12px auto;"

這些參數都可以組合,組合時寫進同一個style中。

當沒寫這些參數時,默認值是「width=432 block」。

代码: 全选

根據您的需求,我更新了代碼。這版代碼增加了對 `width`、`inline` 與 `block` 參數的處理,並將它們整合到同一個 `style` 屬性中。

```php
/**
 * Updated [ejcees] shortcode with width and display layout parameters
 */
function register_ejcees_shortcode($atts, $content = null) {
    // 1. Define default attributes
    // 'width' defaults to 432, and we use a custom flag for display logic
    $atts = shortcode_atts(array(
        'mode'   => 'single',
        'width'  => '432',
        'inline' => false,
        'block'  => false,
    ), $atts);

    // 2. Logic for Display Style
    // Default is block if neither 'inline' nor 'block' is explicitly provided as an attribute
    $display_style = "";
    
    // Check if 'inline' or 'block' is present in the $atts array 
    // (Shortcode boolean attributes usually appear as keys with empty values if written without ="")
    if (isset($atts['inline']) && $atts['inline'] !== false) {
        $display_style = "display: inline-block;";
    } else {
        // Default behavior or explicit 'block'
        $display_style = "display: block; margin: 12px auto;";
    }

    // 3. Construct Style Attribute
    $width_val = esc_attr($atts['width']);
    $style_attr = "style=\"width: {$width_val}px; {$display_style}\"";

    // 4. Enqueue Scripts (using Attachment ID 19088)
    $attachment_id = 19088;
    $url = wp_get_attachment_url($attachment_id);

    if ($url) {
        wp_enqueue_script('ejcees-obj-js', $url, array(), null, true);

        $inline_init_script = "
            document.addEventListener('DOMContentLoaded', () => {
                const containers = document.querySelectorAll('.ejcees');
                containers.forEach(container => {
                    const mode = container.getAttribute('mode');
                    if (typeof Ejcees !== 'undefined') {
                        new Ejcees(container, mode);
                    }
                });
            });
        ";
        wp_add_inline_script('ejcees-obj-js', $inline_init_script);
    }

    // 5. Process Content and Output HTML
    $processed_content = do_shortcode($content);

    return sprintf(
        '<div class="ejcees" mode="%s" %s>%s</div>',
        esc_attr($atts['mode']),
        $style_attr,
        $processed_content
    );
}
add_shortcode('ejcees', 'register_ejcees_shortcode');
```

### 代碼邏輯說明:

1.  **默認值處理**:在 `shortcode_atts` 中設置 `width` 默認為 `432`。如果使用者沒有輸入 `inline`,邏輯會自動進入 `else` 分支,套用 `display: block; margin: 12px auto;`。
2.  **參數組合**:所有樣式都會被合併到一個 `$style_attr` 變數中,最終輸出為一個 `style="..."` 屬性。
3.  **用法範例**:
    * **默認(width=432 block)**: `[ejcees]內容[/ejcees]`
    * **修改寬度**: `[ejcees width="360"]內容[/ejcees]`
    * **設為行內**: `[ejcees inline="true" width="200"]內容[/ejcees]` 
        *(註:WordPress 的 shortcode 在處理純關鍵字參數如 `[ejcees inline]` 時,建議寫成 `inline="1"` 或 `inline="true"` 以確保屬性被正確解析)*。
4.  **嵌套支援**:保留了 `do_shortcode` 確保內部內容可以執行其他外掛的 shortcode。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

改進:

mini自動播放時顯示分支,文字背景透明度應為0.3,剛走完的棋應顯示綠邊和白點。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

下面是對mini模式的改進:
一,把mini-branch-container的html放到ejceesctrl的下方,它的css將改成display:block,text-align:left。去掉svg分支圖標。不需要mini-branch-dropdown,直接把branch-move-link(如果有)放到mini-branch-container中。branch-move-link的display:inline-block,加虛線邊框,如果是active則是實線邊框。當不存在分支時,mini-branch-container的display:none。

二,把mini-comment-container的html放到ini-branch-container的下方,它的css將改成display:block,去掉它的背景色的設置,它的字體顏色就是#eee。當不存在評論時,mini-comment-container的display:none。

三,去掉BG Opacity、Text Opacity,換成max-height,意為mini-comment-container的最大高度,默認120px,step=10,min=60,max=360,超過scroll。

四,當點擊mini-exp-btn彈出 mini-exp-txt-textarea時,mini-branch-container和mini-comment-container將隱藏。

五,當點擊.ejcees的其它地方時,mini-settings-panel如果顯示將隱藏。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
附件
ejceesobj202604290755.js
(296.69 KiB) 已下载 42 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

gemini pro早上好:

代码: 全选

Here are the specific sections of the code that need to be modified to implement your requested changes for the `mini` mode. All code and comments are in English.

### 1. Update `constructor()`
Replace the `miniBgOpacity` and `miniTextOpacity` properties with the new `miniCommentMaxH` property.

```javascript
    // In constructor()
    // Remove these:
    // this.miniBgOpacity = 0.6;
    // this.miniTextOpacity = 1;
    
    // Add this:
    this.miniCommentMaxH = 120;
    this.miniFontSize = 14;
```

### 2. Update `injectCSS()`
Modify the CSS rules for the `mini` mode. Replace the opacity variables, update the branch and comment containers, and remove the unused dropdown styles.

```javascript
      // In injectCSS() within the `if (this.mode === 'single' || this.mode === 'mini') { ... }` block

      afterContainerCSS = `
/* Use Container Queries instead of Media Queries */
@container chess-container (max-width: 767px) {
  .ejcees-inner { flex-direction: column; }
  .ejceesctrl { flex: 1 0 272px; min-height: 272px; }
  .ejceessvg { width: 100%; height: auto; max-height: calc(100cqw * 480 / 432); }
  .ejceespb { width: 100%; height: auto; max-height: 100%; }
  .ejceestool, .ejceesstep, .ejceesoutput { padding: 0 7px; }
  .ejceesstep { gap: 7px; }
  .ejceesrecord { width: 200px; font-size: 12px; }
  .ejceesrcdstep .ejceesrcdstepcontent { margin-left: 24px; }
  .branch-marker { margin-left: 1px; }
  .exp-fmt-outer { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
  .exp-fmt-btn { padding: 5px; cursor: pointer; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

@container chess-container (min-width: 768px) {
  .ejcees-inner { flex-direction: row; }
  .ejceesctrl { flex: 1 0 414px; min-width: 414px; }
  .ejceessvg { height: 100%; width: auto; max-width: calc(100cqh * 432 / 480); }
  .ejceespb { height: 100%; width: auto; max-width: 100%; }
  .ejceestool, .ejceesstep, .ejceesoutput { padding: 0 12px; }
  .ejceesstep { gap: 12px; }
  .ejceesrecord { width: 268px; font-size: 16px; }
}

/* mini specific elements */
.ejcees-inner.mini-mode {
  --mini-fz: ${this.miniFontSize}px;
  --mini-comment-max-h: ${this.miniCommentMaxH}px;
}

.mini-output-settings {
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%237c7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>');
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: 16px 16px;
  cursor: pointer;
  align-items: start;
  overflow: visible;
  font-size: 14px;
}

.mini-settings-panel {
  position: absolute;
  top: 8px;
  right: 8px;
  background-color: rgba(0, 0, 0, 0.85); /* Hardcoded solid dark background */
  color: #fff;
  padding: 12px;
  border-radius: 6px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: var(--mini-fz);
  min-width: 240px;
}
.mini-settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.mini-settings-label {
  flex: 0 0 100px;
  white-space: nowrap;
}
.mini-settings-value {
  flex: 0 0 30px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: #aaa;
}
.mini-settings-panel input[type="range"] {
  flex: 1;
  width: 100%;
}

.mini-branch-container {
  display: none;
  text-align: left;
  padding: 8px;
  font-size: var(--mini-fz);
}

.mini-branch-container .branch-move-link {
  display: inline-block;
  color: #eee;
  padding: 4px 8px;
  margin: 2px 4px 2px 0;
  cursor: pointer;
  border: 1px dashed #ccc;
  border-radius: 4px;
}

.mini-branch-container .branch-move-link:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.mini-branch-container .branch-move-link.active {
  font-weight: bold;
  border: 1px solid #fff;
  background-color: rgba(255, 255, 255, 0.2);
}

.mini-comment {
  display: none;
  color: #eee;
  padding: 8px;
  font-size: var(--mini-fz);
  box-sizing: border-box;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: var(--mini-comment-max-h);
  overflow-y: auto;
}

/* Button styling matching the dark theme */
.mini-exp-txt {
  background-color: rgba(255, 255, 255, 0.2);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.3);
  padding: 4px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: var(--mini-fz);
  transition: background-color 0.2s;
}

.mini-exp-txt:hover {
  background-color: rgba(255, 255, 255, 0.3);
}

/* Full-screen overlay panel */
.mini-exp-txt-panel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.85);
  z-index: 200;
  display: flex;
  flex-direction: column;
}

/* Textarea styling */
.mini-exp-txt-textarea:focus,
.mini-exp-txt-textarea {
  width: 100%;
  height: 100%;
  background: transparent;
  color: #eee;
  border: none;
  padding: 40px 15px 15px 15px;
  box-sizing: border-box;
  font-size: 14px;
  font-family: monospace;
  resize: none;
  outline: none;
}

/* Close button positioning and styling */
.mini-exp-txt-close {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  cursor: pointer;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 210;
}

.mini-exp-txt-close:hover {
  color: #ff4444;
}

.ejceessvg > object {
  width: 100%;
  height: auto;
  pointer-events: none;
}`;
```

### 3. Update `renderDOM()`
Move the branch and comment containers out of `.ejceessvg` and place them after `.ejceesctrl`. Update the `.mini-settings-panel` inputs.

```javascript
    // In renderDOM(), if (this.mode === 'mini') { ... } block
    if (this.mode === 'mini') {
      this.container.innerHTML = `
<div class="ejcees-inner mini-mode" tabindex="-1" style="--mini-fz: ${this.miniFontSize}px; --mini-comment-max-h: ${this.miniCommentMaxH}px;">
  <div class="ejceessvg" style="position: relative;" tabindex="-1">
    <div id="mini-settings-panel" class="mini-settings-panel" style="display: none;">
        <div class="mini-settings-row">
            <span class="mini-settings-label">Max Height:</span>
            <span class="mini-settings-value" id="val-comment-h">${this.miniCommentMaxH}</span>
            <input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label">Font Size (px):</span>
            <span class="mini-settings-value" id="val-fz">${this.miniFontSize}</span>
            <input type="range" id="mini-fz-slider" min="12" max="24" step="1" value="${this.miniFontSize}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label">Interval (s):</span>
            <span class="mini-settings-value" id="val-interval">${this.moveInterval}</span>
            <input type="range" id="mini-interval-slider" min="1" max="4" step="0.2" value="${this.moveInterval}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label">Speed (s):</span>
            <span class="mini-settings-value" id="val-speed">${this.moveSpeed}</span>
            <input type="range" id="mini-speed-slider" min="0.1" max="2" step="0.1" value="${this.moveSpeed}">
        </div>
        <div style="text-align: center;">
            <button type="button" class="mini-exp-txt" id="mini-exp-btn">Export Text</button>
        </div>
    </div>
  </div>
  <div class="ejceesctrl" style="flex: 0 0 auto; min-height: unset; height: 48px;">
    <div class="ejceesstep">
      <div class="ejceesoutput mini-output-settings">0/0</div>
      <div class="ejceesstepminus">-</div>
      <input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
      <div class="ejceesstepplus">+</div>
      <div class="note-btn" id="btn-autoplay" title="Auto Play" style="width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; background-color: transparent;">
        <svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #ccc;"><path d="M8 5v14l11-7z"/></svg>
      </div>
    </div>
  </div>
  <div id="mini-branch-container" class="mini-branch-container"></div>
  <div id="mini-comment-container" class="mini-comment"></div>
  <input type="file" id="file-input" style="display: none" />
</div>`;
    }
```

### 4. Update `bindEvents()`
Refactor the event bindings for sliders, outside clicks, and the export text panel.

```javascript
    // In bindEvents(), if (this.mode === 'mini') { ... } block
    if (this.mode === 'mini') {
      // -- Bind Settings UI --
      const outputEl = this.container.querySelector('.ejceesoutput');
      const settingsPanel = this.container.querySelector('#mini-settings-panel');
      const innerWrapper = this.container.querySelector('.ejcees-inner');

      if (outputEl && settingsPanel) {
        outputEl.addEventListener('click', (e) => {
          e.stopPropagation(); // Stop propagation to allow outside click logic
          settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'flex' : 'none';
        });
      }
      
      // Stop clicks inside the settings panel from closing it
      if (settingsPanel) {
        settingsPanel.addEventListener('click', (e) => e.stopPropagation());
      }
      
      // Close settings panel when clicking outside
      this.container.addEventListener('click', () => {
        if (settingsPanel && settingsPanel.style.display !== 'none') {
          settingsPanel.style.display = 'none';
        }
      });

      const commentHSlider = this.container.querySelector('#mini-comment-h-slider');
      const commentHVal = this.container.querySelector('#val-comment-h');
      if (commentHSlider && commentHVal) {
        commentHSlider.addEventListener('input', e => {
          this.miniCommentMaxH = e.target.value;
          commentHVal.textContent = this.miniCommentMaxH;
          innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
        });
      }

      const fzSlider = this.container.querySelector('#mini-fz-slider');
      const fzVal = this.container.querySelector('#val-fz');
      if (fzSlider && fzVal) {
        fzSlider.addEventListener('input', e => {
          this.miniFontSize = e.target.value;
          fzVal.textContent = this.miniFontSize;
          innerWrapper.style.setProperty('--mini-fz', this.miniFontSize + 'px');
        });
      }

      const intervalSlider = this.container.querySelector('#mini-interval-slider');
      const intervalVal = this.container.querySelector('#val-interval');
      if (intervalSlider && intervalVal) {
        intervalSlider.addEventListener('input', e => {
          this.moveInterval = parseFloat(e.target.value);
          intervalVal.textContent = this.moveInterval;
        });
      }

      const speedSlider = this.container.querySelector('#mini-speed-slider');
      const speedVal = this.container.querySelector('#val-speed');
      if (speedSlider && speedVal) {
        speedSlider.addEventListener('input', e => {
          this.moveSpeed = parseFloat(e.target.value);
          speedVal.textContent = this.moveSpeed;
        });
      }
      
      const exportBtn = this.container.querySelector('#mini-exp-btn');
      if (exportBtn) {
        exportBtn.addEventListener('click', () => {
          // Hide branch and comment containers
          const branchContainer = this.container.querySelector('#mini-branch-container');
          const commentContainer = this.container.querySelector('#mini-comment-container');
          if (branchContainer) branchContainer.style.display = 'none';
          if (commentContainer) commentContainer.style.display = 'none';

          // 1. Create the panel element
          const panel = document.createElement('div');
          panel.className = 'mini-exp-txt-panel';

          // 2. Create the Close Button (SVG icon)
          const closeBtn = document.createElement('button');
          closeBtn.className = 'mini-exp-txt-close';
          closeBtn.innerHTML = `
            <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none">
                <line x1="18" y1="6" x2="6" y2="18"></line>
                <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
        `;

          // 3. Create the Textarea
          const textarea = document.createElement('textarea');
          textarea.className = 'mini-exp-txt-textarea';
          // Use textContent from the container as requested
          textarea.value = this.initialTextContent;

          // 4. Close logic: Remove the panel from DOM and restore UI
          closeBtn.onclick = () => {
            panel.remove();
            this.renderRecordUI();
            this.renderNoteUI();
          };

          // 5. Assemble and append
          panel.appendChild(closeBtn);
          panel.appendChild(textarea);
          this.container.appendChild(panel);

          // Hide settings panel
          if (settingsPanel) settingsPanel.style.display = 'none';

          // 6. Focus and Select text automatically
          textarea.focus();
          textarea.select();
        });
      }
```

### 5. Update `renderRecordUI()`
Rewrite the `this.mode === 'mini'` block to render inline `.branch-move-link` buttons and handle `.mini-exp-txt-panel` blocking state.

```javascript
  // Inside renderRecordUI()
  renderRecordUI() {
    if (this.mode === 'mini') {
      const totalDepth = this.getPathDepth(this.historyFEN, this.currentBranch);
      this.stepSlider.max = Math.max(0, totalDepth - 1);

      const branchContainer = this.container.querySelector('#mini-branch-container');
      const parentNode = this.getNodeAtStep(this.currentStepIndex - 1);

      branchContainer.innerHTML = ''; // Clear existing contents

      // Check if export panel is active
      const isExportPanelActive = this.container.querySelector('.mini-exp-txt-panel') !== null;

      if (!isExportPanelActive && parentNode && parentNode.v && parentNode.v.length > 1) {
        branchContainer.style.display = 'block';

        let forkIndex = -1;
        let tempNode = this.historyFEN;
        for (let j = 0; j < this.currentStepIndex - 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++;

        // Add inline branch links
        parentNode.v.forEach((child, idx) => {
          const moveLink = document.createElement('div');
          moveLink.className = 'branch-move-link';
          if (this.currentBranch[forkIndex] === idx) {
            moveLink.classList.add('active');
          }
          moveLink.innerText = this.NotationConverter.toChinese(child.move);
          moveLink.onclick = e => {
            e.stopPropagation();
            this.updateBranchPath(forkIndex, idx);
            this.jumpToStep(this.currentStepIndex);
            this.renderRecordUI();
            this.saveStateToUndo();
            this.updateToolHighlights();
          };
          branchContainer.appendChild(moveLink);
        });
      } else {
        branchContainer.style.display = 'none';
      }
      this.updateToolHighlights();
      return;
    }
```

### 6. Update `renderNoteUI()`
Ensure comments do not display when the export text panel is open.

```javascript
  // Inside renderNoteUI()
  renderNoteUI() {
    if (this.mode === 'mini') {
      const node = this.getNodeAtStep(this.currentStepIndex);
      const commentContainer = this.container.querySelector('#mini-comment-container');
      const isExportPanelActive = this.container.querySelector('.mini-exp-txt-panel') !== null;

      if (!isExportPanelActive && node && node.c && node.c.trim() !== '') {
        commentContainer.textContent = node.c.trim();
        commentContainer.style.display = 'block';
      } else {
        commentContainer.style.display = 'none';
      }

      const autoBtnSvg = this.container.querySelector('#btn-autoplay svg');
      if (autoBtnSvg) {
        autoBtnSvg.innerHTML = this.isAutoPlaying
          ? '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>'
          : '<path d="M8 5v14l11-7z"/>';
        autoBtnSvg.style.fill = this.isAutoPlaying ? '#fff' : '#ccc';
      }
      return;
    }
```

### 7. Delete `showMiniBranchMenu()` method entirely
Because the branches are now rendered directly inside `renderRecordUI()`, you must delete the entire `showMiniBranchMenu` function from the `Ejcees` class block.
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

改進:
當點擊#val-comment-h或它前面的span時,將彈出一個輸入框,輸入一個數字後,將其四捨五入至10的倍數,如果這個數字大於當前的#mini-comment-h-slider的max屬性,將把它的max和value都改為這個數值。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。

代码: 全选

Here are the specific sections of the code that need to be modified to implement the prompt feature for the Max Height setting.

### 1. Update `renderDOM()`
Add an `id` and `cursor: pointer` to the label span so it is easily selectable and indicates to the user that it is clickable.

```javascript
    // In renderDOM(), within the `if (this.mode === 'mini') { ... }` block
    // Replace the first .mini-settings-row for Max Height with the following:

        <div class="mini-settings-row">
            <span class="mini-settings-label" id="label-comment-h" style="cursor: pointer;" title="Click to input specific height">Max Height:</span>
            <span class="mini-settings-value" id="val-comment-h" style="cursor: pointer;" title="Click to input specific height">${this.miniCommentMaxH}</span>
            <input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
        </div>
```

### 2. Update `bindEvents()`
Locate the existing `#mini-comment-h-slider` logic and add the new click event listeners for the label and value spans.

```javascript
      // In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
      // Replace the existing `commentHSlider` binding section with this:

      const commentHSlider = this.container.querySelector('#mini-comment-h-slider');
      const commentHVal = this.container.querySelector('#val-comment-h');
      const commentHLabel = this.container.querySelector('#label-comment-h');

      if (commentHSlider && commentHVal) {
        // Original slider drag event
        commentHSlider.addEventListener('input', e => {
          this.miniCommentMaxH = e.target.value;
          commentHVal.textContent = this.miniCommentMaxH;
          innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
        });
      }

      // Handler for prompt input when clicking the label or value
      const handleCommentHeightInput = (e) => {
        e.stopPropagation();
        const userInput = prompt("Enter new max height (px):", this.miniCommentMaxH);
        
        if (userInput !== null && userInput.trim() !== '') {
          let parsedVal = parseInt(userInput, 10);
          
          if (!isNaN(parsedVal)) {
            // Round to the nearest multiple of 10
            parsedVal = Math.round(parsedVal / 10) * 10;
            
            const currentMax = parseInt(commentHSlider.max, 10);
            
            // If the entered value is greater than the current max attribute, update max
            if (parsedVal > currentMax) {
              commentHSlider.max = parsedVal;
            }
            
            // Update the slider value, internal state, text content, and CSS variable
            commentHSlider.value = parsedVal;
            this.miniCommentMaxH = parsedVal;
            commentHVal.textContent = this.miniCommentMaxH;
            innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
          }
        }
      };

      // Bind the click events
      if (commentHLabel) commentHLabel.addEventListener('click', handleCommentHeightInput);
      if (commentHVal) commentHVal.addEventListener('click', handleCommentHeightInput);
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

把這個點擊方法擴展到全部四個項目的label和val中,它們輸入後,把這個值四捨五入為step的倍數。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。

代码: 全选

改進:
這段代碼:
      const handleCommentHeightInput = e => {
        e.stopPropagation();
        const userInput = prompt('Enter new max height (px):', this.miniCommentMaxH);

        if (userInput !== null && userInput.trim() !== '') {
          let parsedVal = parseInt(userInput, 10);

          if (!isNaN(parsedVal)) {
            // Round to the nearest multiple of 10
            parsedVal = Math.round(parsedVal / 10) * 10;

            const currentMax = parseInt(commentHSlider.max, 10);

            // If the entered value is greater than the current max attribute, update max
            if (parsedVal > currentMax) {
              commentHSlider.max = parsedVal;
            }

            // Update the slider value, internal state, text content, and CSS variable
            commentHSlider.value = parsedVal;
            this.miniCommentMaxH = parsedVal;
            commentHVal.textContent = this.miniCommentMaxH;
            innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
          }
        }
      };

      // Bind the click events
      if (commentHLabel) commentHLabel.addEventListener('click', handleCommentHeightInput);
      if (commentHVal) commentHVal.addEventListener('click', handleCommentHeightInput);

意思是當點commentHLabel和commentHVal時會彈出一個輸入框。

現在把這個點擊方法擴展到全部四個項目的label和val中,它們輸入後,把這個值四捨五入為step的倍數。

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

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

帖子 ejsoon »

gemini pro剛吃飽又餓了:

代码: 全选

Here are the specific sections of the code that need to be modified to extend the click-to-prompt functionality to all four settings and round the input to the nearest `step`.

### 1. Update `renderDOM()`
Add IDs, `cursor: pointer`, and `title` attributes to the label and value spans for all four settings to make them interactive.

```javascript
    // In renderDOM(), within the `if (this.mode === 'mini') { ... }` block
    // Replace the entire #mini-settings-panel div content with the following:

    <div id="mini-settings-panel" class="mini-settings-panel" style="display: none;">
        <div class="mini-settings-row">
            <span class="mini-settings-label" id="label-comment-h" style="cursor: pointer;" title="Click to input specific value">Max Height:</span>
            <span class="mini-settings-value" id="val-comment-h" style="cursor: pointer;" title="Click to input specific value">${this.miniCommentMaxH}</span>
            <input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label" id="label-fz" style="cursor: pointer;" title="Click to input specific value">Font Size (px):</span>
            <span class="mini-settings-value" id="val-fz" style="cursor: pointer;" title="Click to input specific value">${this.miniFontSize}</span>
            <input type="range" id="mini-fz-slider" min="12" max="24" step="1" value="${this.miniFontSize}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label" id="label-interval" style="cursor: pointer;" title="Click to input specific value">Interval (s):</span>
            <span class="mini-settings-value" id="val-interval" style="cursor: pointer;" title="Click to input specific value">${this.moveInterval}</span>
            <input type="range" id="mini-interval-slider" min="1" max="4" step="0.2" value="${this.moveInterval}">
        </div>
        <div class="mini-settings-row">
            <span class="mini-settings-label" id="label-speed" style="cursor: pointer;" title="Click to input specific value">Speed (s):</span>
            <span class="mini-settings-value" id="val-speed" style="cursor: pointer;" title="Click to input specific value">${this.moveSpeed}</span>
            <input type="range" id="mini-speed-slider" min="0.1" max="2" step="0.1" value="${this.moveSpeed}">
        </div>
        <div style="text-align: center;">
            <button type="button" class="mini-exp-txt" id="mini-exp-btn">Export Text</button>
        </div>
    </div>
```

### 2. Update `bindEvents()`
Replace the scattered individual slider bindings with a unified configuration array that handles dragging, click-to-prompt, rounding to the `step` multiple, and bound updates for all four settings dynamically.

```javascript
      // In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
      // Locate and completely REMOVE the individual event listener blocks for:
      // commentHSlider, fzSlider, intervalSlider, speedSlider and handleCommentHeightInput.
      
      // REPLACE them with this unified configuration block:

      // Configuration for the four adjustable settings
      const settingsConfig = [
        { id: 'comment-h', stateKey: 'miniCommentMaxH', cssVar: '--mini-comment-max-h', suffix: 'px' },
        { id: 'fz', stateKey: 'miniFontSize', cssVar: '--mini-fz', suffix: 'px' },
        { id: 'interval', stateKey: 'moveInterval', cssVar: null, suffix: '' },
        { id: 'speed', stateKey: 'moveSpeed', cssVar: null, suffix: '' }
      ];

      settingsConfig.forEach(config => {
        const slider = this.container.querySelector(`#mini-${config.id}-slider`);
        const valSpan = this.container.querySelector(`#val-${config.id}`);
        const label = this.container.querySelector(`#label-${config.id}`);

        if (!slider || !valSpan) return;

        // 1. Standard drag slider event
        slider.addEventListener('input', e => {
          const val = parseFloat(e.target.value);
          this[config.stateKey] = val;
          valSpan.textContent = val;
          if (config.cssVar) {
            innerWrapper.style.setProperty(config.cssVar, val + config.suffix);
          }
        });

        // 2. Click-to-prompt handler for both Label and Value
        const promptHandler = (e) => {
          e.stopPropagation();
          const step = parseFloat(slider.step) || 1;
          const currentVal = this[config.stateKey];
          const userInput = prompt(`Enter new value (step: ${step}):`, currentVal);

          if (userInput !== null && userInput.trim() !== '') {
            let parsedVal = parseFloat(userInput);
            
            if (!isNaN(parsedVal)) {
              // Round to the nearest multiple of the slider's step 
              // (toFixed(5) is used to avoid JS floating-point precision issues)
              parsedVal = Number((Math.round(parsedVal / step) * step).toFixed(5));
              
              // Dynamically adjust slider max/min boundaries if exceeded
              const currentMax = parseFloat(slider.max);
              const currentMin = parseFloat(slider.min);
              if (parsedVal > currentMax) slider.max = parsedVal;
              if (parsedVal < currentMin) slider.min = parsedVal;
              
              // Apply the rounded value
              slider.value = parsedVal;
              this[config.stateKey] = parsedVal;
              valSpan.textContent = parsedVal;
              
              // Update CSS variables if mapped
              if (config.cssVar) {
                innerWrapper.style.setProperty(config.cssVar, parsedVal + config.suffix);
              }
            }
          }
        };

        if (label) label.addEventListener('click', promptHandler);
        if (valSpan) valSpan.addEventListener('click', promptHandler);
      });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

在調整mini-speed-slider的值時,它的值必須始終小於mini-interval-slider的值。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。

代码: 全选

Here is the specific section of the code that needs to be modified. This update adds a constraint check to both the slider drag event and the prompt input event to ensure `moveSpeed` is always strictly less than `moveInterval`. It also reciprocally prevents `moveInterval` from being set equal to or lower than `moveSpeed`.

### 1. Update `bindEvents()`
Replace the `settingsConfig.forEach` loop inside the `if (this.mode === 'mini') { ... }` block with this updated version:

```javascript
      // In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
      // Replace the entire settingsConfig.forEach block with the following:

      settingsConfig.forEach(config => {
        const slider = this.container.querySelector(`#mini-${config.id}-slider`);
        const valSpan = this.container.querySelector(`#val-${config.id}`);
        const label = this.container.querySelector(`#label-${config.id}`);

        if (!slider || !valSpan) return;

        // 1. Standard drag slider event
        slider.addEventListener('input', e => {
          let val = parseFloat(e.target.value);
          const step = parseFloat(slider.step) || 1;

          // Apply constraint: speed must always be strictly less than interval
          if (config.id === 'speed' && val >= this.moveInterval) {
            val = Math.max(parseFloat(slider.min), Number((this.moveInterval - step).toFixed(5)));
            slider.value = val; // Force visual slider thumb back
          } else if (config.id === 'interval' && val <= this.moveSpeed) {
            val = Math.min(parseFloat(slider.max), Number((this.moveSpeed + step).toFixed(5)));
            slider.value = val; // Force visual slider thumb back
          }

          this[config.stateKey] = val;
          valSpan.textContent = val;
          if (config.cssVar) {
            innerWrapper.style.setProperty(config.cssVar, val + config.suffix);
          }
        });

        // 2. Click-to-prompt handler for both Label and Value
        const promptHandler = (e) => {
          e.stopPropagation();
          const step = parseFloat(slider.step) || 1;
          const currentVal = this[config.stateKey];
          const userInput = prompt(`Enter new value (step: ${step}):`, currentVal);

          if (userInput !== null && userInput.trim() !== '') {
            let parsedVal = parseFloat(userInput);
            
            if (!isNaN(parsedVal)) {
              // Round to the nearest multiple of the slider's step 
              // (toFixed(5) is used to avoid JS floating-point precision issues)
              parsedVal = Number((Math.round(parsedVal / step) * step).toFixed(5));
              
              // Apply constraint: speed must always be strictly less than interval
              if (config.id === 'speed' && parsedVal >= this.moveInterval) {
                parsedVal = Math.max(parseFloat(slider.min), Number((this.moveInterval - step).toFixed(5)));
              } else if (config.id === 'interval' && parsedVal <= this.moveSpeed) {
                parsedVal = Math.min(parseFloat(slider.max), Number((this.moveSpeed + step).toFixed(5)));
              }
              
              // Dynamically adjust slider max/min boundaries if exceeded
              const currentMax = parseFloat(slider.max);
              const currentMin = parseFloat(slider.min);
              if (parsedVal > currentMax) slider.max = parsedVal;
              if (parsedVal < currentMin) slider.min = parsedVal;
              
              // Apply the rounded and constrained value
              slider.value = parsedVal;
              this[config.stateKey] = parsedVal;
              valSpan.textContent = parsedVal;
              
              // Update CSS variables if mapped
              if (config.cssVar) {
                innerWrapper.style.setProperty(config.cssVar, parsedVal + config.suffix);
              }
            }
          }
        };

        if (label) label.addEventListener('click', promptHandler);
        if (valSpan) valSpan.addEventListener('click', promptHandler);
      });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

ejceesobj202604290919

更改了mini的顯示,不再把分支和評論鋪到棋盤上,而是顯示在下方。
附件
ejceesobj202604290919.js
(297.66 KiB) 已下载 42 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

ejceesobj202604291142

解決PGN屬性設置窗口不能打開的問題,為output加上字體大小,mini數據不保存到localStorage。
附件
ejceesobj202604291142.js
(297.67 KiB) 已下载 40 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 6009
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

導入有問題:

代码: 全选

[Game "Chinese Chess"]
[Title "河南楚河汉界弈强队 汪洋 胜 北京威凯&金环建设京冀联队 蒋川"]
[Event "2023年腾讯棋牌天天象棋全国象棋甲级联赛"]
[Red "汪洋"]
[RedTeam "河南楚河汉界弈强队"]
[Black "蒋川"]
[BlackName "北京威凯&金环建设京冀联队"]
[Opening "仙人指路对卒底炮"]
[Date ""]
[Site "河北遵化"]
[Round "第03轮"]
[Format "ICCS"]
1. C3-C4 B7-C7
2. B0-A2 H7-E7
3. H0-G2 H9-G7
4. A3-A4 A6-A5
5. A2-B4 A5-A4
6. B4-C6 E7-E8
7. B2-C2 E8-C8
8. I0-H0 C9-E7
9. A0-B0 I9-I8
10. G3-G4 C8-C6
11. C2-C6 B9-D8
12. C6-G6 A9-B9
13. B0-B9 C7-C0
14. D0-E1 D8-B9
15. H2-H8 A4-B4
16. G4-G5 B4-C4
17. G6-F6 G7-E8
18. H0-H7 C0-B0
19. G5-G6 B0-B7
20. H7-H5 E8-C7
21. G2-H4 D9-E8
22. G6-G7 I6-I5
23. H4-F5 C7-D5
24. F6-F8 I8-I6
25. F5-H6 B9-C7
26. F8-F2 C4-D4
27. F2-D2 B7-B5
28. H6-G8 E9-D9
29. H5-H4 B5-B8
30. D2-D5 B8-G8
31. G7-G8 C7-D5
32. H4-D4 I6-G6
33. D4-D5 D9-E9
34. G8-F8 G6-F6
35. F8-G8 F6-F3
36. D5-I5 F3-E3
37. H8-H9 E3-H3
38. H9-I9 H3-H9
39. I5-I8  *
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

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