arbscht

Even Smaller Snake

June 23, 2008 on 8:57 am | In Uncategorized | | arbscht

In 35 lines. (It had to be done.)

Here’s a take on Jeremy’s Snake, implemented in Clojure (a Lisp for the JVM). As you can see, I’m still using the same library classes. Only the language is different.

A major difference is that this is the whole application code, including incantations and setup. Excerpting the Snake-specific bits would make it shorter — maybe under 30 lines even.

Still, it is a little messy for the sake of brevity. With a more liberal style, it could be a neat 50-100 line Snake.

Update [Tue Dec 30 12:58:04 NZDT 2008]: The original code in this post was written to an old version of Clojure (Jun 2008), which has since been replaced by newer versions that introduce breaking changes. An updated snippet follows, which is known to work with Clojure SVN rev 1185 (Dec 2008). Thanks to Chouser and Stephen C. Gilardi for additional idiomatic updates, reducing line count.

These versions of the program are intended to be abnormally terse. For an expanded and readable presentation, check out Mark Volkmann’s edition, which is more instructive to humans.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(import '(java.awt Color) '(javax.swing JPanel JFrame Timer)
         '(java.awt.event KeyEvent ActionListener KeyListener))
 
(defn gen-apple [_] [(rand-int 750) (rand-int 550)])
(defn move [{:keys [body dir] :as snake} & grow]
   (assoc snake :body (cons (vec (map #(+ (dir %) ((first body) %)) [0  
1]))
                            (if grow body (butlast body)))))
(defn turn [snake newdir] (if newdir (assoc snake :dir newdir) snake))
(defn collision? [{[b] :body} a]
   (every? #(<= (- (a %) 10) (b %) (+ 10 (a %))) [0 1]))
(defn paint [g p c] (.setColor g c) (.fillRect g (p 0) (p 1) 10 10))
 
(def dirs {KeyEvent/VK_LEFT [-10 0] KeyEvent/VK_RIGHT [10 0]
            KeyEvent/VK_UP   [0 -10] KeyEvent/VK_DOWN  [0 10]})
(def apple (atom (gen-apple nil)))
(def snake (atom {:body (list [10 10]) :dir [10 0]}))
(def colors {:apple (Color. 210 50 90) :snake (Color. 15 160 70)})
(def panel (proxy [JPanel ActionListener KeyListener] []
              (paintComponent [g] (proxy-super paintComponent g)
                              (paint g @apple (colors :apple))
                              (doseq [p (:body @snake)]
                                (paint g p (colors :snake))))
              (actionPerformed [e] (if (collision? @snake @apple)
                                     (do (swap! apple gen-apple)
                                         (swap! snake move :grow))
                                     (swap! snake move))
                               (.repaint this))
              (keyPressed [e] (swap! snake turn (dirs (.getKeyCode e))))
              (keyReleased [e])
              (keyTyped [e])))
 
(doto panel (.setFocusable true)(.addKeyListener panel))
(doto (JFrame. "Snake")(.add panel)(.setSize 800 600)(.setVisible true))
(.start (Timer. 75 panel))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
;(import '(java.awt.event KeyEvent ActionListener KeyListener)
;        '(javax.swing JPanel JFrame Timer))
 
;(defn gen-apple [] {:x (int (* 790 (. Math (random)))) :y (int (* 590 (. Math (random))))})
 
;(def snake (ref {:body (list {:x 10 :y 10}) :dir {:x 10 :y 0}}))
;(def apple (ref (gen-apple)))
;(def dirs {(. KeyEvent VK_LEFT) {:x -10 :y 0} (. KeyEvent VK_RIGHT) {:x 10 :y 0}
;           (. KeyEvent VK_UP)   {:x 0 :y -10} (. KeyEvent VK_DOWN)  {:x 0 :y 10}})
 
;(defn move [{body :body dir :dir :as snake} & opts]
;  (merge snake {:body (cons {:x (+ (:x dir) (:x (first body)))
;                             :y (+ (:y dir) (:y (first body)))}
;                            (if (:grow (apply hash-map opts)) body (butlast body)))}))
 
;(defn turn [snake newdir] (if newdir {:body (:body snake) :dir newdir} snake))
 
;(defn collision? [{[b & bs] :body} a]
;  (and (>= (+ 10 (:x b)) (:x a)) (<= (:x b) (+ 10 (:x a)))
;       (>= (+ 10 (:y b)) (:y a)) (<= (:y b) (+ 10 (:y a)))))
 
;(defn run-snake []
;  (let [panel (proxy [JPanel ActionListener KeyListener] []
;                (paintComponent [g] (proxy-super paintComponent g)
;                                    (. g (fillRect (:x @apple) (:y @apple) 10 10))
;                                    (doseq p (:body @snake) (. g (fillRect (:x p) (:y p) 10 10)))
;                                    (if (collision? @snake @apple)
;                                      (dosync (ref-set apple (gen-apple))
;                                              (alter snake move :grow true))))
;                (actionPerformed [e] (dosync (alter snake move))
;                                     (. this (repaint)))
;                (keyPressed [e] (dosync (alter snake turn (get dirs (. e (getKeyCode)))))))]
;    (doto panel (setFocusable true) (addKeyListener panel))
;    (doto (new JFrame "Snake") (add panel) (setSize 800 600) (setVisible true))
;    (. (new Timer 75 panel) (start))))

Edit: fixed per cgrand’s comment.

5 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Dig

    Comment by RadioactivePhoenix — 10:18 am — June 23, 2008 #

  2. Dam you. I actually undustood the java version. This looks like a curry recipe.

    Comment by Zorab — 12:54 pm — June 23, 2008 #

  3. Pfft, there’s hardly any currying going on there!

    Comment by arbscht — 1:19 pm — June 23, 2008 #

  4. pretty slick. what do you need to do to compile it?
    Zorab: what sort of curry do you eat? :S Or are you referring to the yellow and red bits :P

    Comment by RadioactivePhoenix — 6:57 pm — June 23, 2008 #

  5. Nice!
    There’s an error (dir instead of newdir) in the definition of turn:
    (defn turn [snake newdir] (if newdir {:body (:body snake) :dir newdir} snake))

    Comment by cgrand — 9:23 pm — June 24, 2008 #

Leave a comment

You must be logged in to post a comment.

Powered by WordPress with Pool theme design by Borja Fernandez.
Entries and comments feeds. Valid XHTML and CSS. ^Top^