|
2 | 2 | ; Abhishek Reddy's snake: http://www.plt1.com/1070/even-smaller-snake/
|
3 | 3 | ; Mark Volkmann's snake: http://www.ociweb.com/mark/programming/ClojureSnake.html
|
4 | 4 |
|
| 5 | +; The START:/END: pairs are production artifacts for the book and not |
| 6 | +; part of normal Clojure style |
| 7 | + |
5 | 8 | (ns examples.snake
|
6 | 9 | (:import (java.awt Color) (javax.swing JPanel JFrame Timer JOptionPane)
|
7 | 10 | (java.awt.event ActionListener KeyListener))
|
8 | 11 | (:use clojure.contrib.import-static
|
9 | 12 | [clojure.contrib.seq-utils :only (includes?)]))
|
10 | 13 | (import-static java.awt.event.KeyEvent VK_LEFT VK_RIGHT VK_UP VK_DOWN)
|
11 | 14 |
|
12 |
| -; Game board and coordinates. points are [x,y] vectors |
| 15 | +; ---------------------------------------------------------- |
| 16 | +; functional model |
| 17 | +; ---------------------------------------------------------- |
| 18 | +; START: constants |
13 | 19 | (def width 75)
|
14 | 20 | (def height 50)
|
15 | 21 | (def point-size 10)
|
16 | 22 | (def turn-millis 75)
|
17 | 23 | (def win-length 5)
|
| 24 | +(def dirs { VK_LEFT [-1 0] |
| 25 | + VK_RIGHT [ 1 0] |
| 26 | + VK_UP [ 0 -1] |
| 27 | + VK_DOWN [ 0 1]}) |
| 28 | +; END: constants |
18 | 29 |
|
| 30 | +; START: board math |
19 | 31 | (defn add-points [& pts]
|
20 | 32 | (vec (apply map + pts)))
|
21 | 33 |
|
22 | 34 | (defn point-to-screen-rect [pt]
|
23 | 35 | (map #(* point-size %)
|
24 | 36 | [(pt 0) (pt 1) 1 1]))
|
| 37 | +; END: board math |
25 | 38 |
|
26 |
| -(def dirs { VK_LEFT [-1 0] |
27 |
| - VK_RIGHT [ 1 0] |
28 |
| - VK_UP [ 0 -1] |
29 |
| - VK_DOWN [ 0 1]}) |
30 |
| - |
31 |
| -; apple |
32 |
| -(def *apple* (ref nil)) |
33 |
| - |
| 39 | +; START: apple |
34 | 40 | (defn create-apple []
|
35 | 41 | {:location [(rand-int width) (rand-int height)]
|
36 | 42 | :color (Color. 210 50 90)
|
37 | 43 | :type :apple})
|
| 44 | +; END: apple |
38 | 45 |
|
39 |
| -; snake |
40 |
| -(def *snake* (ref nil)) |
41 |
| - |
| 46 | +; START: snake |
42 | 47 | (defn create-snake []
|
43 | 48 | {:body (list [1 1])
|
44 | 49 | :dir [1 0]
|
45 | 50 | :type :snake
|
46 | 51 | :color (Color. 15 160 70)})
|
| 52 | +; START: snake |
47 | 53 |
|
48 |
| - |
| 54 | +; START: move |
49 | 55 | (defn move [{:keys [body dir] :as snake} & grow]
|
50 | 56 | (assoc snake :body (cons (add-points (first body) dir)
|
51 | 57 | (if grow body (butlast body)))))
|
| 58 | +; END: move |
52 | 59 |
|
| 60 | +; START: turn |
53 | 61 | (defn turn [snake newdir]
|
54 | 62 | (if newdir (assoc snake :dir newdir) snake))
|
| 63 | +; END: turn |
55 | 64 |
|
| 65 | +; START: win |
56 | 66 | (defn win? [{body :body}]
|
57 | 67 | (>= (count body) win-length))
|
| 68 | +; END: win |
58 | 69 |
|
| 70 | +; START: lose |
59 | 71 | (defn head-overlaps-body? [{[head & body] :body}]
|
60 | 72 | ; have proposed to SS that argument order be reversed:
|
61 | 73 | (includes? head body))
|
62 | 74 |
|
63 | 75 | (def lose? head-overlaps-body?)
|
| 76 | +; END: lose |
64 | 77 |
|
65 |
| -(defn collision? [{[snake-head] :body} {apple :location}] |
| 78 | +; START: eats |
| 79 | +(defn eats? [{[snake-head] :body} {apple :location}] |
66 | 80 | (= snake-head apple))
|
| 81 | +; END: eats |
67 | 82 |
|
68 |
| -; state updates |
| 83 | +; ---------------------------------------------------------- |
| 84 | +; mutable model |
| 85 | +; ---------------------------------------------------------- |
69 | 86 | (defn update-positions [snake apple]
|
70 | 87 | (dosync
|
71 |
| - (if (collision? @snake @apple) |
| 88 | + (if (eats? @snake @apple) |
72 | 89 | (do (ref-set apple (create-apple))
|
73 | 90 | (alter snake move :grow))
|
74 | 91 | (alter snake move))))
|
75 | 92 |
|
76 | 93 | (defn update-direction [snake newdir]
|
77 | 94 | (dosync (alter snake turn newdir)))
|
78 | 95 |
|
79 |
| -(defn reset-game [] |
80 |
| - (dosync (ref-set *apple* (create-apple)) |
81 |
| - (ref-set *snake* (create-snake)))) |
82 |
| - |
83 |
| -(reset-game) |
| 96 | +(defn reset-game [snake apple] |
| 97 | + (dosync (ref-set apple (create-apple)) |
| 98 | + (ref-set snake (create-snake)))) |
84 | 99 |
|
85 |
| -; drawing |
| 100 | +; ---------------------------------------------------------- |
| 101 | +; gui |
| 102 | +; ---------------------------------------------------------- |
86 | 103 | (defn fill-point [g pt color]
|
87 | 104 | (let [[x y width height] (point-to-screen-rect pt)]
|
88 | 105 | (.setColor g color)
|
|
96 | 113 |
|
97 | 114 | (defmethod paint :apple [g {:keys [location color]}]
|
98 | 115 | (fill-point g location color))
|
99 |
| - |
100 |
| -; gui elements |
101 |
| -(def frame (JFrame. "Snake")) |
102 |
| - |
103 |
| -(def panel |
104 |
| - (proxy [JPanel ActionListener KeyListener] [] |
105 |
| - (paintComponent [g] |
106 |
| - (proxy-super paintComponent g) |
107 |
| - (paint g @*snake*) |
108 |
| - (paint g @*apple*)) |
109 |
| - (actionPerformed [e] |
110 |
| - (update-positions *snake* *apple*) |
111 |
| - (when (lose? @*snake*) |
112 |
| - (reset-game) |
113 |
| - (JOptionPane/showMessageDialog frame "You lose!")) |
114 |
| - (when (win? @*snake*) |
115 |
| - (reset-game) |
116 |
| - (JOptionPane/showMessageDialog frame "You win!")) |
117 |
| - (.repaint this)) |
118 |
| - (keyPressed [e] |
119 |
| - (update-direction *snake* (dirs (.getKeyCode e)))) |
120 |
| - (keyReleased [e]) |
121 |
| - (keyTyped [e]))) |
122 |
| - |
123 |
| -(def timer (Timer. turn-millis panel)) |
124 |
| - |
125 |
| -(doto panel |
126 |
| - (.setFocusable true) |
127 |
| - (.addKeyListener panel)) |
128 |
| - |
129 |
| -(doto frame |
130 |
| - (.add panel) |
131 |
| - (.setSize (* width point-size) (* height point-size)) |
132 |
| - (.setVisible true)) |
133 |
| -(.start timer) |
| 116 | + |
| 117 | +(defn game [] |
| 118 | + (let [snake (ref (create-snake)) |
| 119 | + apple (ref (create-apple)) |
| 120 | + frame (JFrame. "Snake") |
| 121 | + panel (proxy [JPanel ActionListener KeyListener] [] |
| 122 | + (paintComponent [g] |
| 123 | + (proxy-super paintComponent g) |
| 124 | + (paint g @snake) |
| 125 | + (paint g @apple)) |
| 126 | + (actionPerformed [e] |
| 127 | + (update-positions snake apple) |
| 128 | + (when (lose? @snake) |
| 129 | + (reset-game snake apple) |
| 130 | + (JOptionPane/showMessageDialog frame "You lose!")) |
| 131 | + (when (win? @snake) |
| 132 | + (reset-game snake apple) |
| 133 | + (JOptionPane/showMessageDialog frame "You win!")) |
| 134 | + (.repaint this)) |
| 135 | + (keyPressed [e] |
| 136 | + (update-direction snake (dirs (.getKeyCode e)))) |
| 137 | + (keyReleased [e]) |
| 138 | + (keyTyped [e])) |
| 139 | + timer (Timer. turn-millis panel)] |
| 140 | + (doto panel |
| 141 | + (.setFocusable true) |
| 142 | + (.addKeyListener panel)) |
| 143 | + (doto frame |
| 144 | + (.add panel) |
| 145 | + (.setSize (* width point-size) (* height point-size)) |
| 146 | + (.setVisible true)) |
| 147 | + (.start timer) |
| 148 | + [snake, apple, timer])) |
134 | 149 |
|
135 | 150 |
|
0 commit comments