如何加速一个滞后的Swing扫雷板?
How to accelerate a lagging Swing minesweeper board?
我最近使用 seesaw framework which is basically a nifty clojure wrapper around swing. The related code can be found here 创建了一个小扫雷器 UI。
到目前为止基本上一切正常,唯一的问题是当您选择专家级游戏时用户体验很差ui。原因是每次点击一个单元格时,整个 ui 都会重新绘制,这需要 quite 长(平均 850 毫秒)。
负责重新绘制的代码如下:
(defn- update-fields
[cell-states]
(doseq [[idx state] (map-indexed vector cell-states)
:let [field (select-field idx)]]
(config! field :icon (icons/cell-icons state))))
(defn- update-board
[snapshot face]
(do
(change-smiley face)
(update-fields (:cells snapshot))
(repaint! ui)))
图标处理代码如下所示
(ns minesweeper.icons
(:require
[clojure.java.io :as io]
[clojure.string :as str]
[seesaw.icon :as icon]))
(def ^:private cell-icons-path "minesweeper/icons/cell")
(def ^:private face-icons-path "minesweeper/icons/face")
(defn- file-name
[file]
(str/replace-first
(.getName file) #"\.[^.]+$" ""))
(def ^:private init-icons
(memoize
(fn [res]
(let [parent (rest (file-seq (io/file (io/resource res))))]
(reduce
#(assoc %1 (keyword (file-name %2)) (icon/icon %2))
{}
parent)))))
(defn cell-icons
[id]
(let [icons (init-icons cell-icons-path)]
(get icons id)))
(defn face-icons
[id]
(let [icons (init-icons face-icons-path)]
(get icons id)))
所以我的问题是,如何更有效地解决这个问题?我考虑只更新受点击影响的单元格(由 JButtons 表示),但如果自动清除打开很多相邻单元格,这也可能需要 quite 一段时间。
一般来说,使用带有按钮的 mig 布局来表示面板是否是一个合理的选择?
通过使用 clojure.core/time
,我发现您的 UI 逻辑中的瓶颈是使用 (select ui [(keyword (str "#field_" idx))])
查找您的按钮,因为跷跷板必须通过过滤所有组件来进行名称搜索每次更新板时的层次结构。
最快的解决方法是将您的 select-field
函数包装到 memoize
中,但是当您重新启动游戏时它不会起作用(将创建新按钮,以便记忆 select-field
会return 个来自上一个游戏的按钮)。
另一种可能的解决方案是将所有按钮放入一个向量中并将其保存在全局 atom
:
(def items (atom []))
(defn select-field
[idx]
(@items idx))
并更改创建图板的方式:
(defn- make-board-panel
[snapshot]
(let [bg (button-group)
[n m] (:dimension snapshot)
buttons (into [] (for [idx (range (* n m))]
(make-button idx bg)))]
(reset! items buttons)
(mig-panel
:constraints [(str "gap 0, wrap" n) "[]" "[]" ]
:items (map #(vector % "w 24px!, h 24px!") buttons))))
测试
我把你的update-board
body包裹在clojure.core/time
中,玩了10次得到了如下结果(new-game 50 50 1)
:
修复前
"Elapsed time: 7020.756206 msecs"
"Elapsed time: 6766.130362 msecs"
"Elapsed time: 6616.715565 msecs"
"Elapsed time: 6628.383521 msecs"
"Elapsed time: 6657.386279 msecs"
"Elapsed time: 6588.50692 msecs"
"Elapsed time: 6554.704587 msecs"
"Elapsed time: 6650.864132 msecs"
"Elapsed time: 6610.557065 msecs"
"Elapsed time: 6671.02469 msecs"
修复后
"Elapsed time: 92.491489 msecs"
"Elapsed time: 60.236867 msecs"
"Elapsed time: 32.254729 msecs"
"Elapsed time: 29.551383 msecs"
"Elapsed time: 29.383067 msecs"
"Elapsed time: 25.768517 msecs"
"Elapsed time: 25.724915 msecs"
"Elapsed time: 45.869723 msecs"
"Elapsed time: 25.898016 msecs"
"Elapsed time: 26.254874 msecs"
我最近使用 seesaw framework which is basically a nifty clojure wrapper around swing. The related code can be found here 创建了一个小扫雷器 UI。
到目前为止基本上一切正常,唯一的问题是当您选择专家级游戏时用户体验很差ui。原因是每次点击一个单元格时,整个 ui 都会重新绘制,这需要 quite 长(平均 850 毫秒)。
负责重新绘制的代码如下:
(defn- update-fields
[cell-states]
(doseq [[idx state] (map-indexed vector cell-states)
:let [field (select-field idx)]]
(config! field :icon (icons/cell-icons state))))
(defn- update-board
[snapshot face]
(do
(change-smiley face)
(update-fields (:cells snapshot))
(repaint! ui)))
图标处理代码如下所示
(ns minesweeper.icons
(:require
[clojure.java.io :as io]
[clojure.string :as str]
[seesaw.icon :as icon]))
(def ^:private cell-icons-path "minesweeper/icons/cell")
(def ^:private face-icons-path "minesweeper/icons/face")
(defn- file-name
[file]
(str/replace-first
(.getName file) #"\.[^.]+$" ""))
(def ^:private init-icons
(memoize
(fn [res]
(let [parent (rest (file-seq (io/file (io/resource res))))]
(reduce
#(assoc %1 (keyword (file-name %2)) (icon/icon %2))
{}
parent)))))
(defn cell-icons
[id]
(let [icons (init-icons cell-icons-path)]
(get icons id)))
(defn face-icons
[id]
(let [icons (init-icons face-icons-path)]
(get icons id)))
所以我的问题是,如何更有效地解决这个问题?我考虑只更新受点击影响的单元格(由 JButtons 表示),但如果自动清除打开很多相邻单元格,这也可能需要 quite 一段时间。
一般来说,使用带有按钮的 mig 布局来表示面板是否是一个合理的选择?
通过使用 clojure.core/time
,我发现您的 UI 逻辑中的瓶颈是使用 (select ui [(keyword (str "#field_" idx))])
查找您的按钮,因为跷跷板必须通过过滤所有组件来进行名称搜索每次更新板时的层次结构。
最快的解决方法是将您的 select-field
函数包装到 memoize
中,但是当您重新启动游戏时它不会起作用(将创建新按钮,以便记忆 select-field
会return 个来自上一个游戏的按钮)。
另一种可能的解决方案是将所有按钮放入一个向量中并将其保存在全局 atom
:
(def items (atom []))
(defn select-field
[idx]
(@items idx))
并更改创建图板的方式:
(defn- make-board-panel
[snapshot]
(let [bg (button-group)
[n m] (:dimension snapshot)
buttons (into [] (for [idx (range (* n m))]
(make-button idx bg)))]
(reset! items buttons)
(mig-panel
:constraints [(str "gap 0, wrap" n) "[]" "[]" ]
:items (map #(vector % "w 24px!, h 24px!") buttons))))
测试
我把你的update-board
body包裹在clojure.core/time
中,玩了10次得到了如下结果(new-game 50 50 1)
:
修复前
"Elapsed time: 7020.756206 msecs"
"Elapsed time: 6766.130362 msecs"
"Elapsed time: 6616.715565 msecs"
"Elapsed time: 6628.383521 msecs"
"Elapsed time: 6657.386279 msecs"
"Elapsed time: 6588.50692 msecs"
"Elapsed time: 6554.704587 msecs"
"Elapsed time: 6650.864132 msecs"
"Elapsed time: 6610.557065 msecs"
"Elapsed time: 6671.02469 msecs"
修复后
"Elapsed time: 92.491489 msecs"
"Elapsed time: 60.236867 msecs"
"Elapsed time: 32.254729 msecs"
"Elapsed time: 29.551383 msecs"
"Elapsed time: 29.383067 msecs"
"Elapsed time: 25.768517 msecs"
"Elapsed time: 25.724915 msecs"
"Elapsed time: 45.869723 msecs"
"Elapsed time: 25.898016 msecs"
"Elapsed time: 26.254874 msecs"