Skip to content

Instantly share code, notes, and snippets.

@thomcc
Created January 2, 2012 00:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomcc/1548720 to your computer and use it in GitHub Desktop.
Save thomcc/1548720 to your computer and use it in GitHub Desktop.
Snake game in clojure
(ns snake
(:import (java.awt Color Graphics Dimension Font)
(javax.swing JFrame JPanel)
(java.awt.event KeyEvent KeyAdapter)))
(def field [80 48]) ; [width height]
(def scale 10)
(def screen (Dimension. (* scale (field 0)) (* scale (field 1))))
(def on? true)
(def tick-speed 100)
(def tick-food-chance 50) ; 1/probability to generate food on a given tick
(def eat-food-chance 2) ; 1/probability to generate food after eating some
(def food-color Color/GREEN)
(def snake-color Color/RED)
(defn snake-points [{body :body head :head}] (set (conj body head)))
(defn rand-bool [chance] (= 0 (rand-int chance)))
(defn out-of-bounds? [pt] (not-every? true? (map < [-1 -1] pt field)))
(defn gen-food [occupied]
(first (filter #(not (contains? occupied %))
(repeatedly #(vec (map rand-int field))))))
(defn add-food
([world] (add-food world 1))
([{:keys [food snake] :as world} prob]
(if (rand-bool prob)
(assoc world :food (conj food (gen-food (into food (snake-points snake)))))
world)))
(defn new-world []
(let [head (vec (map #(+ (/ % 4) (rand-int (/ % 2))) field))
dir ([:n :s :e :w] (rand-int 4))]
(add-food
{:snake {:body clojure.lang.PersistentQueue/EMPTY
:head head
:dir dir
:size 3
:dead? false}
:food #{}})))
(defn next-point [[x y] dir]
(dir {:n [x (dec y)], :s [x (inc y)], :e [(inc x) y], :w [(dec x) y]}))
(defn tick-snake [{:keys [body head dir size dead?] :as s} food]
(let [size-now (count body)
new-head (next-point head dir)
on-food? (contains? food new-head)
size-next (if on-food? (inc size) size)
new-body (conj (if (>= size-now size-next) (pop body) body) head)
die? (or dead? ; don't un-die
(contains? (set new-body) new-head)
(out-of-bounds? new-head))]
[(assoc s :head new-head, :body new-body, :size size-next, :dead? die?), on-food?]))
(defn tick [{:keys [snake food] :as world}]
(if (:dead? snake) world
(let [[new-snake ate-food?] (tick-snake snake food)
new-food (if ate-food? (disj food (:head new-snake)) food)]
(add-food {:snake new-snake :food new-food}
(if ate-food? eat-food-chance tick-food-chance)))))
(defn draw-square [#^Graphics g [x y] c]
(doto g
(.setColor c)
(.fillRect (* scale x) (* scale y) scale scale)
(.setColor Color/BLACK)
(.drawRect (* scale x) (* scale y) scale scale)))
(defn draw [#^Graphics g {:keys [snake food]}]
(doto g
(.setColor Color/BLACK)
(.fillRect 0 0 (* scale (field 0)) (* scale (field 1))))
(if (:dead? snake)
(doto g
(.setColor Color/RED)
(.setFont (Font. "Serif" (. Font PLAIN) 24))
(.drawString (str "Game Over. Score: " (- (count (:body snake)) 3)) 50 50))
(do ; draw body
(doseq [f food]
(draw-square g f food-color))
(doseq [s (conj (:body snake) (:head snake))]
(draw-square g s snake-color)))))
(defn dir [#^KeyEvent key]
(condp = (.getKeyCode key)
KeyEvent/VK_UP :n
KeyEvent/VK_DOWN :s
KeyEvent/VK_LEFT :w
KeyEvent/VK_RIGHT :e
false))
(defn -main [& args]
(let [world (atom (new-world))
ka (proxy [KeyAdapter] []
(keyPressed [e]
(when-let [ndir (dir e)]
(swap! world #(assoc-in % [:snake :dir] ndir)))))
panel (doto (proxy [JPanel] [] (paint [#^Graphics g] (draw g @world)))
(.setMinimumSize screen)
(.setMaximumSize screen)
(.setPreferredSize screen))
frame (doto (JFrame. "snaaaaake!!")
(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(.add panel)
.pack
(.setResizable false)
(.setLocationRelativeTo nil)
(.setVisible true)
(.addKeyListener ka))]
(loop []
(swap! world tick)
(.repaint panel)
(. Thread (sleep tick-speed))
(recur))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment