夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Clojure勉強日記(その23 SnakeGameをClojureで書いてみる(その2

こんにちは。

では、SnakeGameの状態更新部分を進めていきます。
今回、状態が更新されるタイミングは以下の3つです。

  1. ゲームを開始する。
  2. ターンごとにヘビが位置を更新する。リンゴが食べられていたらヘビの長さを伸ばし、リンゴを再配置する。
  3. ヘビの向きを変える。

その中で、実際に更新対象となる値はヘビとリンゴの状態のみになります。
そのため、3つの関数をトランザクションでヘビとリンゴの状態を更新する関数として実装する必要があります。

まずは、「ゲームを開始する」から。
初期状態設定なのでref-setを使う形になりますね。
■snake.clj:ゲームを開始する

; Initialize Game
(defn initialize-game [snake apple]
  (dosync (ref-set apple (create-apple))
          (ref-set snake (create-snake)) nil))
; refの参照を2つ渡し、そこに各々apple、snakeを初期化するよう設定
; refに対して値が初期化されることを確認
user=> (def ref-snake (ref nil))
#'user/ref-snake
user=> (def ref-apple (ref nil))
#'user/ref-apple
user=> (initialize-game ref-snake ref-apple)
nil
user=> @ref-snake
{:body ([1 1]), :dir [1 0], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}
user=> @ref-apple
{:location [15 7], :color #<Color java.awt.Color[r=210,g=50,b=90]>, :type :apple}

こういう形で部分的に確認できる辺りは便利ですね。
次は簡単なもの、ということで「ヘビの向きを変える」を。
実際にやるのは内部で関数を呼び出すだけですが。
■snake.clj:ヘビの向きを変える

; Update Snake Direction
(defn update-direction [snake direction]
  (when direction (dosync (alter snake turn-snake direction))))
; 中身のある「方向」が与えられた場合にsnake参照を方向を更新して新たに生成したsnakeで置き換える。
; refに対して方向が更新されたヘビで置き換えられることを確認
user=> @ref-snake
{:body ([1 1]), :dir [1 0], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}
user=> (update-direction ref-snake [1 1])
{:body ([1 1]), :dir [1 1], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}

で、最後が「ターンごとにヘビが位置を更新する。リンゴが食べられていたらヘビの長さを伸ばし、リンゴを再配置」です。
■snake.clj:ターンごとにヘビが位置を更新

; Update Snake Position
(defn update-position [snake apple]
  (dosync 
    (if (eats? @snake @apple)
      (do (ref-set apple (create-apple)) (alter snake move-snake :grow))
      (alter snake move-snake)) nil))
; snakeがappleを食べた場合にappleを再初期化し、ヘビの長さを伸ばす
; apple、snakeを初期化
user=> (def ref-snake (ref nil))
#'user/ref-snake
user=> (def ref-apple (ref nil))
#'user/ref-apple
; appleの位置をsnakeにあわせて更新
user=> (dosync (alter ref-apple assoc :location [1 1]))
{:location [1 1], :color #<Color java.awt.Color[r=210,g=50,b=90]>, :type :apple}
user=> @ref-apple
{:location [1 1], :color #<Color java.awt.Color[r=210,g=50,b=90]>, :type :apple}
user=> @ref-snake
{:body ([1 1]), :dir [1 0], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}
user=> (update-position ref-snake ref-apple)
nil
; snake。身体が伸びている。
user=> @ref-snake
{:body ([2 1] [1 1]), :dir [1 0], :type :snake, :color #<Color java.awt.Color[r=15,g=160,b=70]>}
; apple。再配置されている。
user=> @ref-apple
{:location [43 22], :color #<Color java.awt.Color[r=210,g=50,b=90]>, :type :apple}

状態を更新する必要があるコードは意外に少なかったです。
では、最後にこれをGUIに表示して実際のSnakeゲームとしておきましょう。