1
+ (ns schlang.core
2
+ (:require [play-cljs.core :as p]))
3
+
4
+ (enable-console-print! )
5
+
6
+ (declare menu-screen main-screen )
7
+
8
+ (def initial-state (let [x 12
9
+ y 12
10
+ x2 (int (/ x 2 ))]
11
+ {:width 500
12
+ :height 500
13
+ :speed 400
14
+ :game {:width x
15
+ :height y
16
+ :score 0
17
+ :food [[x2 1 ]]}
18
+ :snake {:body [[x2 (-> y dec dec dec)]
19
+ [x2 (-> y dec dec)]
20
+ [x2 (-> y dec)]]
21
+ :eaten false }
22
+ :player {:key-pressed :none
23
+ :direction :up }}))
24
+
25
+ (defonce *state (atom initial-state))
26
+
27
+ (defonce game (let [width (:width @*state)
28
+ height (:height @*state)]
29
+ (p/create-game width height)))
30
+
31
+ (defn next-direction [direction key-pressed]
32
+ (let [ctrl {:right {:up :right
33
+ :right :down
34
+ :down :left
35
+ :left :up }
36
+ :left {:up :left
37
+ :left :down
38
+ :down :right
39
+ :right :up }}]
40
+ (if (#{:up :right :down :left } key-pressed)
41
+ (-> ctrl key-pressed direction)
42
+ direction)))
43
+
44
+ (defn next-position [[x y] direction]
45
+ (let [state @*state
46
+ width (-> state :game :width )
47
+ height (-> state :game :height )
48
+ assumed-position (case direction
49
+ :up [x (dec y)]
50
+ :down [x (inc y)]
51
+ :left [(dec x) y]
52
+ :right [(inc x) y])
53
+ position-checked-x (let [[new-x new-y] assumed-position]
54
+ (cond (>= new-x width) [0 new-y]
55
+ (< new-x 0 ) [(dec width) new-y]
56
+ :default assumed-position))
57
+ position-checked-y (let [[new-x new-y] position-checked-x]
58
+ (cond (>= new-y height) [new-x 0 ]
59
+ (< new-y 0 ) [new-x (dec height)]
60
+ :default position-checked-x))]
61
+ position-checked-y))
62
+
63
+ #_(events/listen js/window " mousedown" (fn []))
64
+
65
+ (defn new-food [s]
66
+ (let [width (-> s :game :width )
67
+ height (-> s :game :height )
68
+ points (repeatedly (fn [] [(rand-int width) (rand-int height)]))
69
+ occupied (reduce into #{} [(-> s :game :food ) (-> s :snake :body )])]
70
+ (some #(if (not (occupied %))
71
+ %)
72
+ points)))
73
+
74
+ (defn check-food [s]
75
+ (let [food (into #{} (-> s :game :food ))
76
+ body (-> s :snake :body )
77
+ head (first body)
78
+ eat (food head)]
79
+ (if eat
80
+ (-> s
81
+ (update-in [:game :food ] (fn [food] (remove #(= % head) food)))
82
+ (update-in [:game :food ] #(conj % (new-food s)))
83
+ (assoc-in [:snake :eaten ] true ))
84
+ s)))
85
+
86
+ (defn step []
87
+ (let [state @*state
88
+ old-direction (-> state :player :direction )
89
+ key-pressed (-> state :player :key-pressed )
90
+ new-direction (next-direction old-direction key-pressed)
91
+ old-body (-> state :snake :body )
92
+ old-head (first old-body)
93
+ new-head (next-position old-head new-direction)
94
+ eaten (-> state :snake :eaten )
95
+ new-body (into [new-head] (if eaten
96
+ old-body
97
+ (drop-last old-body)))
98
+ lost (not (apply distinct? new-body))]
99
+ (if lost
100
+ (p/set-screen game menu-screen)
101
+ (swap! *state
102
+ (fn [state]
103
+ (if lost
104
+ initial-state
105
+ (-> state
106
+ (assoc-in [:snake :body ] new-body)
107
+ (assoc-in [:player :direction ] new-direction)
108
+ (assoc-in [:snake :eaten ] false )
109
+ (assoc-in [:player :key-pressed ] :move-executed )
110
+ check-food)))))))
111
+
112
+ ; define a screen, where all the action takes place
113
+ (def main-screen
114
+ (reify p/Screen
115
+
116
+ ; runs when the screen is first shown
117
+ (on-show [this]
118
+ (reset! *state initial-state)
119
+ (swap! *state (fn [s]
120
+ (-> s
121
+ (update-in [:timeoutid ]
122
+ (fn [_]
123
+ (js/setInterval
124
+ step
125
+ (:speed s))))))))
126
+
127
+ ; runs when the screen is hidden
128
+ (on-hide [this]
129
+ (js/clearInterval (:timeoutid @*state)))
130
+
131
+ ; runs every time a frame must be drawn (about 60 times per sec)
132
+ (on-render [this]
133
+ (p/render game
134
+ (let [state @*state
135
+ width (:width state)
136
+ height (:height state)
137
+ game-width (-> state :game :width )
138
+ game-height (-> state :game :height )
139
+ tile-width (/ width game-width)
140
+ tile-height (/ height game-height)
141
+ body (-> state :snake :body )
142
+ part-of-body (into #{} body)
143
+ food (into #{} (-> state :game :food ))]
144
+ [[:stroke {:color " gray" }
145
+ (for [x (range game-width) y (range game-height)]
146
+ [:fill {:color (cond (= [x y] (first body)) " darkgray"
147
+ (part-of-body [x y]) " gray"
148
+ (food [x y]) " lightgreen"
149
+ :default " lightblue" )}
150
+ [:rect {:x (* x tile-width)
151
+ :y (* y tile-height)
152
+ :width tile-width
153
+ :height tile-height}
154
+ (cond (= [x y] (first body))
155
+ (let [old-direction (-> state :player :direction )
156
+ key-pressed (-> state :player :key-pressed )
157
+ new-direction (next-direction old-direction key-pressed)
158
+ eye-width (/ tile-width 4 )
159
+ eye-height (/ tile-width 4 )]
160
+ [:fill {:color " red" }
161
+ [:rect {:x (case new-direction
162
+ :left 0
163
+ :right (- tile-width eye-width)
164
+ (- (/ tile-width 4 ) (/ eye-width 2 )))
165
+ :y (case new-direction
166
+ :up 0
167
+ :down (- tile-height eye-height)
168
+ (- (/ tile-height 4 ) (/ eye-height 2 )))
169
+ :height eye-height
170
+ :width eye-width}]
171
+ [:rect {:x (case new-direction
172
+ :left 0
173
+ :right (- tile-width eye-width)
174
+ (- (* 3 (/ tile-width 4 )) (/ eye-width 2 )))
175
+ :y (case new-direction
176
+ :up 0
177
+ :down (- tile-height eye-height)
178
+ (- (* 3 (/ tile-height 4 )) (/ eye-height 2 )))
179
+ :height eye-height
180
+ :width eye-width}]])
181
+ (food [x y])
182
+ (let [eye-width (/ tile-width 4 )
183
+ eye-height (/ tile-width 4 )]
184
+ [:fill {:color " green" }
185
+ [:rect {:x (+ (- (/ tile-width 4 ) (/ eye-width 2 ))
186
+ (* (rand ) (/ eye-width 4 )))
187
+ :y (+ (- (/ tile-height 4 ) (/ eye-height 2 ))
188
+ (* (rand ) (/ eye-width 4 )))
189
+ :height eye-height
190
+ :width eye-width}]
191
+ [:rect {:x (+ (- (* 3 (/ tile-width 4 )) (/ eye-width 2 ))
192
+ (* (rand ) (/ eye-width 4 )))
193
+ :y (+ (- (/ tile-height 4 ) (/ eye-height 2 ))
194
+ (* (rand ) (/ eye-height 4 )))
195
+ :height eye-height
196
+ :width eye-width}]]))]])]]))
197
+ (let [old-key (-> @*state :player :key-pressed )
198
+ pressed-keys (p/get-pressed-keys game)]
199
+ (swap! *state (fn [s]
200
+ (cond (= old-key :move-executed ) (if (empty? pressed-keys)
201
+ (assoc-in s [:player :key-pressed ] :none )
202
+ s)
203
+ (empty? pressed-keys) s
204
+ ; (pressed-keys 87) (update :text-y dec) ;up
205
+ (= pressed-keys #{68 }) (assoc-in s [:player :key-pressed ] :right )
206
+ ; (pressed-keys 83) (update :text-y inc) ;down
207
+ (= pressed-keys #{65 }) (assoc-in s [:player :key-pressed ] :left )
208
+ :default (assoc-in s [:player :key-pressed ] :none ))))))))
209
+
210
+ #_(if (= (first body) [x y])
211
+ (let [direction (-> state :player :direction )]
212
+ [:fill {:color " red" }
213
+ [:rect {:x (+ (* x tile-width)
214
+ (/ tile-width 3 ))
215
+ :y (+ (* y tile-height)
216
+ (/ tile-height 3 ))
217
+ :width tile-width
218
+ :height tile-height}]]))
219
+
220
+ #_[:fill {:color " black" }
221
+ [:text {:value (str " Hello, world!" (p/get-pressed-keys game))
222
+ :x (:text-x state)
223
+ :y (:text-y state)
224
+ :size 16
225
+ :font " Georgia"
226
+ :style :italic }]]
227
+
228
+ (def menu-screen
229
+ (reify p/Screen
230
+
231
+ ; runs when the screen is first shown
232
+ (on-show [this])
233
+
234
+ ; runs when the screen is hidden
235
+ (on-hide [this])
236
+
237
+ ; runs every time a frame must be drawn (about 60 times per sec)
238
+ (on-render [this]
239
+ (p/render game
240
+ [[:fill {:color " lightblue" }
241
+ [:rect {:x 0 :y 0 :width 500 :height 500 }]]
242
+ [:fill {:color " black" }
243
+ [:text {:value (str " press any button to start!" )
244
+ :x (/ (:width @*state) 4 )
245
+ :y (/ (:height @*state) 2 )
246
+ :size 16
247
+ :font " Georgia"
248
+ :style :italic }]]])
249
+ (let [pressed-keys (p/get-pressed-keys game)]
250
+ (if (empty? pressed-keys)
251
+ nil
252
+ (p/set-screen game main-screen))))))
253
+
254
+
255
+ ; start the game
256
+ (comment
257
+ (doto game
258
+ (p/start )
259
+ (p/set-screen menu-screen)))
0 commit comments