|
| 1 | +; Inspired by the snakes the have gone before: |
| 2 | +; Abhishek Reddy's snake: http://www.plt1.com/1070/even-smaller-snake/ |
| 3 | +; Mark Volkmann's snake: http://www.ociweb.com/mark/programming/ClojureSnake.html |
| 4 | + |
| 5 | +(ns examples.fsnake |
| 6 | + (:import (java.awt Color Dimension) |
| 7 | + (javax.swing JPanel JFrame Timer JOptionPane) |
| 8 | + (java.awt.event ActionListener KeyListener)) |
| 9 | + (:use clojure.contrib.import-static |
| 10 | + [clojure.contrib.seq-utils :only (includes?)])) |
| 11 | +(import-static java.awt.event.KeyEvent VK_LEFT VK_RIGHT VK_UP VK_DOWN) |
| 12 | + |
| 13 | +; ---------------------------------------------------------- |
| 14 | +; functional model |
| 15 | +; ---------------------------------------------------------- |
| 16 | +(def width 75) |
| 17 | +(def height 50) |
| 18 | +(def point-size 10) |
| 19 | +(def turn-millis 75) |
| 20 | +(def win-length 5) |
| 21 | +(def dirs { VK_LEFT [-1 0] |
| 22 | + VK_RIGHT [ 1 0] |
| 23 | + VK_UP [ 0 -1] |
| 24 | + VK_DOWN [ 0 1]}) |
| 25 | + |
| 26 | +(defn add-points [& pts] |
| 27 | + (vec (apply map + pts))) |
| 28 | + |
| 29 | +(defn point-to-screen-rect [pt] |
| 30 | + (map #(* point-size %) |
| 31 | + [(pt 0) (pt 1) 1 1])) |
| 32 | + |
| 33 | +(defn create-apple [] |
| 34 | + {:location [(rand-int width) (rand-int height)] |
| 35 | + :color (Color. 210 50 90) |
| 36 | + :type :apple}) |
| 37 | + |
| 38 | +(defn create-snake [] |
| 39 | + {:body (list [1 1]) |
| 40 | + :dir [1 0] |
| 41 | + :type :snake |
| 42 | + :color (Color. 15 160 70)}) |
| 43 | + |
| 44 | +(defn move [{:keys [body dir] :as snake} & grow] |
| 45 | + (assoc snake :body (cons (add-points (first body) dir) |
| 46 | + (if grow body (butlast body))))) |
| 47 | + |
| 48 | +(defn turn [snake newdir] |
| 49 | + (if newdir (assoc snake :dir newdir) snake)) |
| 50 | + |
| 51 | +(defn win? [{body :body}] |
| 52 | + (>= (count body) win-length)) |
| 53 | + |
| 54 | +(defn head-overlaps-body? [{[head & body] :body}] |
| 55 | + (includes? body head)) |
| 56 | + |
| 57 | +(def lose? head-overlaps-body?) |
| 58 | + |
| 59 | +(defn eats? [{[snake-head] :body} {apple :location}] |
| 60 | + (= snake-head apple)) |
| 61 | + |
| 62 | +(defn update-positions [{snake :snake, apple :apple, :as game}] |
| 63 | + (if (eats? snake apple) |
| 64 | + (merge game {:apple (create-apple) :snake (move snake :grow)}) |
| 65 | + (merge game {:snake (move snake)}))) |
| 66 | + |
| 67 | +(defn update-direction [{snake :snake :as game} newdir] |
| 68 | + (merge game {:snake (turn snake newdir)})) |
| 69 | + |
| 70 | +(defn reset-game [game] |
| 71 | + (merge game {:apple (create-apple) :snake (create-snake)})) |
| 72 | + |
| 73 | +; ---------------------------------------------------------- |
| 74 | +; gui |
| 75 | +; ---------------------------------------------------------- |
| 76 | +(defn fill-point [g pt color] |
| 77 | + (let [[x y width height] (point-to-screen-rect pt)] |
| 78 | + (.setColor g color) |
| 79 | + (.fillRect g x y width height))) |
| 80 | + |
| 81 | +(defmulti paint (fn [g object & _] (:type object))) |
| 82 | + |
| 83 | +(defmethod paint :apple [g {:keys [location color]}] ; <label id="code.paint.apple"/> |
| 84 | + (fill-point g location color)) |
| 85 | + |
| 86 | +(defmethod paint :snake [g {:keys [body color]}] ; <label id="code.paint.snake"/> |
| 87 | + (doseq [point body] |
| 88 | + (fill-point g point color))) |
| 89 | + |
| 90 | +(defn game-panel [frame game] |
| 91 | + (proxy [JPanel ActionListener KeyListener] [] |
| 92 | + (paintComponent [g] ; <label id="code.game-panel.paintComponent"/> |
| 93 | + (proxy-super paintComponent g) |
| 94 | + (paint g (@game :snake)) |
| 95 | + (paint g (@game :apple))) |
| 96 | + (actionPerformed [e] ; <label id="code.game-panel.actionPerformed"/> |
| 97 | + (dosync (alter game update-positions)) |
| 98 | + (when (lose? (@game :snake)) |
| 99 | + (dosync (alter game reset-game)) |
| 100 | + (JOptionPane/showMessageDialog frame "You lose!")) |
| 101 | + (when (win? (@game :snake)) |
| 102 | + (dosync (alter game reset-game)) |
| 103 | + (JOptionPane/showMessageDialog frame "You win!")) |
| 104 | + (.repaint this)) |
| 105 | + (keyPressed [e] ; <label id="code.game-panel.keyPressed"/> |
| 106 | + (dosync (alter game update-direction (dirs (.getKeyCode e))))) |
| 107 | + (getPreferredSize [] |
| 108 | + (Dimension. (* (inc width) point-size) |
| 109 | + (* (inc height) point-size))) |
| 110 | + (keyReleased [e]) |
| 111 | + (keyTyped [e]))) |
| 112 | + |
| 113 | +(defn game [] |
| 114 | + (let [game (ref (reset-game {})) |
| 115 | + frame (JFrame. "Snake") |
| 116 | + panel (game-panel frame game) |
| 117 | + timer (Timer. turn-millis panel)] |
| 118 | + (doto panel ; <label id="code.game.panel"/> |
| 119 | + (.setFocusable true) |
| 120 | + (.addKeyListener panel)) |
| 121 | + (doto frame ; <label id="code.game.frame"/> |
| 122 | + (.add panel) |
| 123 | + (.pack) |
| 124 | + (.setVisible true)) |
| 125 | + (.start timer) ; <label id="code.game.timer"/> |
| 126 | + [game, timer])) ; <label id="code.game.return"/> |
| 127 | + |
0 commit comments