Created
January 2, 2012 00:00
-
-
Save thomcc/1548720 to your computer and use it in GitHub Desktop.
Snake game in clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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