经过几个周末探索Clojure后,我想出了这个程序。它允许您在窗口中移动一个小矩形。这是代码:
(import java.awt.Color)
(import java.awt.Dimension)
(import java.awt.event.KeyListener)
(import javax.swing.JFrame)
(import javax.swing.JPanel)
(def x (ref 0))
(def y (ref 0))
(def panel
(proxy [JPanel KeyListener] []
(getPreferredSize [] (Dimension. 100 100))
(keyPressed [e]
(let [keyCode (.getKeyCode e)]
(if (== 37 keyCode) (dosync (alter x dec))
(if (== 38 keyCode) (dosync (alter y dec))
(if (== 39 keyCode) (dosync (alter x inc))
(if (== 40 keyCode) (dosync (alter y inc))
(println keyCode)))))))
(keyReleased [e])
(keyTyped [e])))
(doto panel
(.setFocusable true)
(.addKeyListener panel))
(def frame (JFrame. "Test"))
(doto frame
(.add panel)
(.pack)
(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(.setVisible true))
(defn drawRectangle [p]
(doto (.getGraphics p)
(.setColor (java.awt.Color/WHITE))
(.fillRect 0 0 100 100)
(.setColor (java.awt.Color/BLUE))
(.fillRect (* 10 (deref x)) (* 10 (deref y)) 10 10)))
(loop []
(drawRectangle panel)
(Thread/sleep 10)
(recur))
尽管我是一名经验丰富的C ++程序员,但我发现用一种语言编写一个简单的应用程序是非常具有挑战性的,这种语言使用的风格与我以前完全不同。
最重要的是,这段代码可能很糟糕。我怀疑各种价值观的全球性是一件坏事。我也不清楚在这里使用x和y值的引用是否合适。
欢迎任何改进此代码的提示。
答案 0 :(得分:12)
if
中的keyPressed
个case
可以替换为单个dosync
。此外,case
可以移到外面以包裹alter
。事实上,commute
也可以移出,所以如果你是决定将其更改为(def panel
(proxy [JPanel KeyListener] []
(getPreferredSize [] (Dimension. 100 100))
(keyPressed [e]
(let [keyCode (.getKeyCode e)]
(dosync
(apply alter
(case keyCode
37 [x dec]
38 [y dec]
39 [x inc]
40 [y inc])))
(println keyCode)))
(keyReleased [e])
(keyTyped [e])))
,只有一个地方可以进行更改。结果:
(import [java.awt Color Dimension event.ActionListener])
(import [javax.swing JFrame JPanel])
您还可以更简洁地重写导入:
drawRectangle
- 你是否想要的是风格问题。
我会将draw-rectangle
重命名为while
(这是Clojure中函数名称的惯用风格),更重要的是,将其重写为接受坐标作为显式参数的纯函数。那么你可以在它周围写一个小包装来使用你的Refs,如果你的设计确实会受益于Refs的使用。 (很难说你不知道如何使用和修改它们等。)
首选(loop [] ... (recur))
至(doc while)
(请参阅(clojure.contrib.repl-utils/source while)
和(println :foo)
)。
此外 - 这一点非常重要 - 除了顶级定义之外不要放任何东西。这是因为在编译代码时实际执行了顶级表单(尝试在顶层加载-main
的库)。那个无限循环应该包含在一个函数中; Clojure中“main”函数的标准名称是panel
;同样适用于frame
和(doto foo ...)
。当然,在REPL玩游戏时不适用,但这是一个重要的问题。
顺便说一下,(doto (proxy ...) (.setFocusable true) ...)
会返回foo,因此您只需撰写ns
。
否则,我会说代码没问题。通常你想把它放在命名空间里;然后所有导入都将以{{1}}形式出现。
HTH
答案 1 :(得分:5)
编辑,在匆忙写下面的帖子时,我忘了说你不能把常量放在常量上,例如java.awt.Color / WHITE。
除了Michał的评论:
当你使用无限循环时,用一个原子来调节它(例如@switch),这样你就能够阻止它(除非循环在repl线程上运行 - 所以它在一个分离的线程中)与“未来”)
你使用refs,所以它意味着要协调x和y的值(在你的代码中它们不是:每次更新都是独立的);因此,在drawRectangle中,您应该将读取包装在dosync中,以确保获得一致的视图 - 再次在实际代码中,它无关紧要,因为x和y总是独立更新。
HTH,
(无耻的插件:http://conj-labs.eu/)
答案 2 :(得分:3)
虽然不完全是Clojure建议 - 考虑使用KeyAdapter而不是KeyListener。这样您就不必为keyReleased和keyTyped提供空实现。当您不需要Listener接口的所有方法时,使用适配器类通常是一个很好的规则。