Clojure勉強日記(その24 SnakeGameをClojureで書いてみる(その3
こんにちは。いよいよ大詰め。
今まではコマンドライン上でしか動作しなかったゲームをGUI上で表示してみます。
■snake.clj:Graphicの塗りつぶし処理
; Fill-Points (defn fill-point [graphic pt color] (let [[x y width heignt] (point-to-screen-rect pt)] (.setColor graphic color) (.fillRect graphic x y width heignt))) ; Paint Multi Method (defmulti paint (fn [graphic object & _] (:type object))) (defmethod paint :apple [graphic {:keys [location color]}] (fill-point graphic location color)) (defmethod paint :snake [graphic {:keys [body color]}] (doseq [point body] (fill-point graphic point color)))
defmultiは引数の型によって処理を分ける「マルチメソッド」を定義するものです。
Javaでいうオーバーロードにあてはまるようですが、動的型付けのClojureでも定義は可能なようです。
上記のメソッド群でappleとsnake用に四角を描画する処理が記述されています。
■snake.clj:パネル生成
; Create Game Panel (defn game-panel [frame snake apple] (proxy [JPanel ActionListener KeyListener] [] (paintComponent [graphic] (proxy-super paintComponent graphic) (paint graphic @snake) (paint graphic @apple)) (actionPerformed [event] (update-position snake apple) (when (lose? @snake) (initialize-game snake apple) (JOptionPane/showMessageDialog frame "You lose!")) (when (game-win? @snake) (initialize-game snake apple) (JOptionPane/showMessageDialog frame "You win!")) (.repaint this)) (keyPressed [event] (update-direction snake (dirs (.getKeyCode event)))) (getPreferredSize [] (new Dimension (* (inc width) point-size) (* (inc height) point-size))) (keyReleased [event]) (keyTyped [event])))
若干長く見えるかもしれませんが、実際はJPanel ActionListener KeyListenerを継承して
各メソッドを数行で書いているのみです。
内容自体はコンポーネントを記述して、イベントごとに方向と場所を更新して勝利/敗北判定を続ける・・・
という非常に分かりやすい内容になっています。
■snake.clj:初期化
; Start Game (defn game [] (let [snake (ref (create-snake)) apple (ref (create-apple)) frame (new JFrame "Snake Game") panel (game-panel frame snake apple) timer (new Timer turn-millis panel)] (doto panel (.setFocusable true) (.addKeyListener panel)) (doto frame (.add panel) (.pack) (.setVisible true)) (.start timer) [snake, apple, timer]))
これで初期化も完了です。
ちなみに、最後の返り値として[snake, apple, timer]を返す必要は実際には無いのですが、
その方がREPL側から確認しやすいため返り値を返しています。
■snake.clj:実行
というわけで、下記のコマンドを入力して実行してみますと・・・
user=> (load-file "src/examples/import_static.clj") #'examples.import-static/import-static user=> (load-file "src/reader/snake.clj") #'reader.snake/game user=> (load 'reader.snake) ClassCastException clojure.lang.Symbol cannot be cast to java.lang.String clojure.core/load (core.clj:5521) user=> (use 'reader.snake) nil user=> (game) [#<Ref@19a7b6e3: {:body ([1 1]), :dir [1 0], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}> #<Ref@3f58cda2: {:location [27 5], :color #<Color java.awt.Color[r=210,g=50,b=90]>, :type :apple}> #<Timer javax.swing.Timer@3c1cd99>]
以下のようにゲームの画面を表示することができました。
SwingのゲームなんてJavaで作ると小規模なものであってもやたらとソースが多くなりますが、
Clojureだと意外に少なく作ることができるんですね。