Below is a little (stupid) snake game I wrote using core.async and swing.
It uses channels for timer, keyboard input and repaint to make everything 
nice and sequential.
Thought it could be a nice example of the power of core.async.

I'm not an experienced clojure/lisp developer so I'd be happy if someone 
could give me some feedback on the code.
Is it clojure idiomatic?
Am I using core.async properly?
etc.

Thanks
--anders


(ns my-test
  (use [midje.sweet])
  (require [clojure.core.async :as async :refer :all])
  (import [javax.swing JFrame JButton JPanel SwingUtilities])
  (import [java.awt Color Dimension])
  (import [java.awt.event ActionListener WindowAdapter KeyListener]))


(defn map-chan [f in]
  (let [c (chan)]
    (go (loop []
          (when-let [v (f (<! in))]
            (>! c v))
          (recur)))
    c))

(defn start-timer! []
  (let [c (chan)]
    (go (while true (<! (timeout 250)) (>! c :go)))
    c))

(defn closing-channel [frame]
  (let [c (chan)]
    (.addWindowListener frame
                        (proxy [WindowAdapter] []
                          (windowClosing [e] (put! c e))))
    c))

(defn array-of [coordinates index]
  (int-array (map #(nth % index) coordinates)))

(defn points-of [coordinates]
  [(array-of coordinates 0) (array-of coordinates 1)])

(defn draw-poly-line [canvas coordinates]
  (SwingUtilities/invokeLater
   (fn []
     (let [[x-points y-points] (points-of coordinates)
           g (.getGraphics canvas)
           prev-color (.getColor g)]
      (.setColor g Color/BLACK)
      (.drawPolyline g x-points y-points (count coordinates))
      (.setColor g prev-color)))))

(def step 5)
(defmulti calc-new-pos (fn[xy prev-pos dir] [xy dir]))
(defmethod calc-new-pos [:x :right][xy prev-pos dir] (+ prev-pos step))
(defmethod calc-new-pos [:x :left][xy prev-pos dir] (- prev-pos step))
(defmethod calc-new-pos [:y :down][xy prev-pos dir] (+ prev-pos step))
(defmethod calc-new-pos [:y :up][xy prev-pos dir] (- prev-pos step))
(defmethod calc-new-pos :default [xy prev-pos dir] prev-pos)

(defn calc-snake [dir snake-obj counter]
  (let [[l-x l-y] (last snake-obj)
        old-snake (if (= (mod counter 2) 0) snake-obj (rest snake-obj))]
    (conj (vec old-snake) [(calc-new-pos :x l-x dir) (calc-new-pos :y l-y 
dir)])))

(facts "snake positions"
       (fact "snake moves and grows"
             (calc-snake :right [[1 2]] 2) => [[1 2] [6 2]]
             (calc-snake :right [[2 2]] 4) => [[2 2] [7 2]])
       (facts "snake moves"
              (calc-snake :right [[1 2]] 1) => [[6 2]]
              (calc-snake :down [[1 2]] 1) => [[1 7]]
              (calc-snake :left [[10 2]] 1) => [[5 2]]
              (calc-snake :up [[10 7]] 1) => [[10 2]]
              ))

(def key-to-dir-map {37 :left, 38 :up, 39 :right, 40 :down})

(defn key-channel [obj]
  (let [c (chan)]
    (.addKeyListener obj
                     (reify KeyListener
                       (keyTyped [_ e] )
                       (keyPressed [_ e] )
                       (keyReleased [_ e]
                         (put! c e))))
    c))

(defn create-canvas [paint-channel]
  (proxy [JButton] []
                 (getPreferredSize [] (Dimension. 300 300))
                 (paintComponent [g]
                   (go
                     (proxy-super paintComponent g)
                     (>! paint-channel :repaint)))))

(defmulti inside-window? (fn [dir canvas pos] dir))
(defmethod inside-window? :left [dir canvas [x _]] (>= x (.getX canvas)))
(defmethod inside-window? :right [dir canvas [x _]] (<= x (+ (.getX canvas) 
(.getWidth canvas))))
(defmethod inside-window? :up [dir canvas [_ y]] (>= y (.getY canvas)))
(defmethod inside-window? :down [dir canvas [_ y]] (<= y (+ (.getY canvas) 
(.getHeight canvas))))



(def initial-snake (vec  (map (fn [x] [x 10])  (take 20 (iterate (partial + 
step) 0)))))


(defn game-rules-ok? [snake dir canvas]
  (and
   (apply distinct? snake)
   (inside-window? dir canvas (last snake))))

(facts "game rules"
       (let [canvas (JButton.)]
         (.setBounds canvas 0 0 10 10)
         (facts "inside window"
                (game-rules-ok? [[0 0]] :right canvas) => truthy
                (game-rules-ok? [[11 0]] :right canvas) => falsey
                (game-rules-ok? [[11 0]] :left canvas) => truthy
                (game-rules-ok? [[11 0]] :up canvas) => truthy
                (game-rules-ok? [[11 0]] :down canvas) => truthy
                (game-rules-ok? [[11 11]] :down canvas) => falsey)
         (facts "snake eating itself"
              (game-rules-ok? [[0 0] [0 0]] :right canvas) => falsey
              (game-rules-ok? [[0 0] [1 0]] :right canvas) => true
              )))
(defn you-loose! [cc]
  (println "you loose!")
  (put! cc :close))


(defn snake [cc]
  (let [paint-channel (chan)
        timer-channel (start-timer!)
        canvas (create-canvas paint-channel)
        dir-channel (map-chan #(key-to-dir-map (.getKeyCode %)) 
(key-channel canvas))
        ]
    (go
     (loop [last-dir :right
               snake-obj initial-snake
               counter 0]
          (let [[v c] (alts! [paint-channel dir-channel timer-channel])]
            (condp = c
              timer-channel
              (do
                (put! dir-channel last-dir)
                (recur last-dir snake-obj counter))
              paint-channel
              (do
                (draw-poly-line canvas snake-obj)
                (recur last-dir snake-obj counter))
              dir-channel
              (do
                (.repaint canvas (.getBounds canvas))
                (let [new-snake (calc-snake v snake-obj counter)]
                  (if (game-rules-ok? new-snake v canvas)
                    (recur v new-snake (inc counter))
                    (you-loose! cc)
                    )))
              ))))
    canvas))

(defn frame []
  (let [f (JFrame.)
      cc (closing-channel f)]
    (.add (.getContentPane f) (snake cc))
    (.pack f)
    (.setVisible f true)
    (go
     (<! cc)
     (println "bye!")
     (.setVisible f false))
    f))
 

-- 
-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to