-
Notifications
You must be signed in to change notification settings - Fork 0
/
space-invaders.rkt
534 lines (404 loc) · 19.3 KB
/
space-invaders.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
;; The first three lines of this file were inserted by DrRacket. They record metadata
;; about the language level of this file in a form that our tools can easily process.
#reader(lib "htdp-beginner-abbr-reader.ss" "lang")((modname space-invaders-starter) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f)))
(require 2htdp/universe)
(require 2htdp/image)
;; Space Invaders
;; Constants:
(define WIDTH 300)
(define HEIGHT 500)
(define INVADER-X-SPEED 1.5) ;speeds (not velocities) in pixels per tick
(define INVADER-Y-SPEED 1.5)
(define TANK-SPEED 2)
(define MISSILE-SPEED 10)
(define HIT-RANGE 10) ; how close missile has to be to kill invader
(define INVADE-RATE 100) ; rate at which invaders appear?
(define BACKGROUND (empty-scene WIDTH HEIGHT))
(define INVADER ; invader as image
(overlay/xy (ellipse 10 15 "outline" "blue") ;cockpit cover
-5 6
(ellipse 20 10 "solid" "blue"))) ;saucer
(define TANK ; tank as image
(overlay/xy (overlay (ellipse 28 8 "solid" "black") ;tread center
(ellipse 30 10 "solid" "green")) ;tread outline
5 -14
(above (rectangle 5 10 "solid" "black") ;gun
(rectangle 20 10 "solid" "black")))) ;main body
(define TANK-HEIGHT/2 (/ (image-height TANK) 2)) ; tank height
(define TANK-HEIGHT (- HEIGHT TANK-HEIGHT/2))
(define MISSILE (ellipse 5 15 "solid" "red")) ; Missile as image
;; Data Definitions:
(define-struct game (invaders missiles tank))
;; Game is (make-game (listof Invader) (listof Missile) Tank)
;; interp. the current state of a space invaders game
;; with the current invaders, missiles and tank position
;; Game constants defined below Missile data definition
#;
(define (fn-for-game s)
(... (fn-for-loinvader (game-invaders s))
(fn-for-lom (game-missiles s))
(fn-for-tank (game-tank s))))
(define-struct tank (x dir))
;; Tank is (make-tank Number Integer[-1, 1])
;; interp. the tank location is x, HEIGHT - TANK-HEIGHT/2 in screen coordinates
;; the tank moves TANK-SPEED pixels per clock tick left if dir -1, right if dir 1
(define T0 (make-tank (/ WIDTH 2) 1)) ;center going right
(define T1 (make-tank 50 1)) ;going right
(define T2 (make-tank 50 -1)) ;going left
#;
(define (fn-for-tank t)
(... (tank-x t) (tank-dir t)))
(define-struct invader (x y dx))
;; Invader is (make-invader Number Number Number)
;; interp. the invader is at (x, y) in screen coordinates
;; the invader along x by dx pixels per clock tick
(define I1 (make-invader 150 100 12)) ;not landed, moving right
(define I2 (make-invader 150 HEIGHT -10)) ;exactly landed, moving left
(define I3 (make-invader 150 (+ HEIGHT 10) 10)) ;> landed, moving right
;; ListOfInvader is one of:
;; - empty
;; - (cons Invader ListOfInvader) = (list Invader ListOfInvader)
(define loi1 empty)
(define loi2 (list I1))
(define loi3 (list I1 I2))
#;
(define (fn-for-loi loi)
(cond [(empty? loi) (...)]
[else
(... (first loi)
(fn-for-loi (rest loi)))]))
;; template rules used:
;; - one of: 2 cases
;; - atomic distinct: empty
;; - compound: loi is (list I1)
;; - self-reference: (rest loi) is listofinvader
#;
(define (fn-for-invader invader)
(... (invader-x invader) (invader-y invader) (invader-dx invader)))
(define-struct missile (x y))
;; Missile is (make-missile Number Number)
;; interp. the missile's location is x y in screen coordinates
(define M1 (make-missile 150 300)) ;not hit U1
(define M2 (make-missile (invader-x I1) (+ (invader-y I1) 10))) ;exactly hit U1
(define M3 (make-missile (invader-x I1) (+ (invader-y I1) 5))) ;> hit U1
#;
(define (fn-for-missile m)
(... (missile-x m) (missile-y m)))
(define G0 (make-game empty empty T0))
(define G1 (make-game empty empty T1))
(define G2 (make-game (list I1) (list M1) T1))
(define G3 (make-game (list I1 I2) (list M1 M2) T1))
;; ================================================================================
;; World Definition
;; Game -> Image
;; start the world with
;; take in a game structure and create a game with it (each sub component can be list)
(define (main G)
(big-bang G
(on-tick move-game) ; game -> game
(to-draw display-game) ; game -> Img
(on-key play-game) ; Game KeyEvent -> Game
(stop-when invaded game-over))) ; Stopwhen invaded and display game-over image
;; ================================================================================
;; Functions
;; Move-Game
;; game -> game
;; takes game and produces next tick game (moved by speeds)
;; - doesn't take into account missile hitting target or invader reaching height and ending game
;; - two helper functions to define
;(define (move-game G0) G0) ;stub
(define (move-game g)
(kill (make-game (create-invader (move-invader-list (game-invaders g)))
(move-missile-list (game-missiles g))
(game-tank g))))
;; move-invader-list
;; listofinvader -> listofinvader
;; interp. move each invader by speed and delta
(check-expect (move-invader-list (list (make-invader 150 100 12) (make-invader 150 HEIGHT -10)))
(list (make-invader (+ 150 12) (+ 100 INVADER-Y-SPEED) 12)
(make-invader (+ 150 -10) (+ HEIGHT INVADER-Y-SPEED) -10)))
(check-expect (move-invader-list (list (make-invader WIDTH 100 12))) (list (make-invader (+ WIDTH -12) (+ 100 INVADER-Y-SPEED) -12)))
;(define (move-invader-list loi) loi) ;stub
(define (move-invader-list loi)
(cond [(empty? loi) loi]
[else
(cons (move-invader (first loi))
(move-invader-list (rest loi)))]))
;; move-invader
;; invader -> invader
;; moves invader by invader speed
(check-expect (move-invader (make-invader 150 100 12)) (make-invader (+ 150 12) (+ 100 INVADER-Y-SPEED) 12))
(check-expect (move-invader (make-invader WIDTH 100 12)) (make-invader (+ WIDTH -12) (+ 100 INVADER-Y-SPEED) -12))
(check-expect (move-invader (make-invader 0 100 -12)) (make-invader (+ 0 12) (+ 100 INVADER-Y-SPEED) 12))
(check-expect (move-invader (make-invader WIDTH 200 12)) (make-invader (+ WIDTH -12) (+ 200 INVADER-Y-SPEED) -12))
(define (move-invader i)
(cond [(and (< (invader-x i) WIDTH) (> (invader-x i) 0))
(make-invader (+ (invader-x i) (invader-dx i))
(+ (invader-y i) INVADER-Y-SPEED)
(invader-dx i))]
[(<= (invader-x i) 0)
(make-invader (+ (invader-x i) (- (invader-dx i)))
(+ (invader-y i) INVADER-Y-SPEED)
(- (invader-dx i)))]
[else
(make-invader (+ (invader-x i) (- (invader-dx i)))
(+ (invader-y i) INVADER-Y-SPEED)
(- (invader-dx i)))]))
;; Create Invader
;; listofinvader -> listofinavder
;; periodically add new invader in random direction
;; no check-expects because of random function
;(define (create-invader loi) loi)
(define (create-invader loi)
(cond [(< (random INVADE-RATE) 4) (cons (make-invader (random WIDTH) 0 (direction (random 12))) loi)]
[else loi]))
;;direction
;; random int -> random int with +-
;; randomly assigns direction invader will move
(define (direction n) n)
;; move-missile-list
;; listofmissle->listofmissile
;; interp. move list of missle by missile speed
(check-expect (move-missile-list (list (make-missile 150 300))) (list (make-missile 150 (- 300 MISSILE-SPEED))))
(check-expect (move-missile-list (list (make-missile 150 300) (make-missile 100 100) (make-missile 50 130)))
(list (make-missile 150 (- 300 MISSILE-SPEED)) (make-missile 100 (- 100 MISSILE-SPEED))
(make-missile 50 (- 130 MISSILE-SPEED))))
;(define (move-missile-list M1) M1) ;stub
(define (move-missile-list lom)
(cond [(empty? lom) lom]
[else
(cons (move-missile (first lom))
(move-missile-list (rest lom)))]))
;; move-missile
;; missile -> missile
;; interp. move missile by missile speed
(check-expect (move-missile (make-missile 150 300)) (make-missile 150 (- 300 MISSILE-SPEED)))
(define (move-missile m)
(make-missile (missile-x m)
(- (missile-y m) MISSILE-SPEED)))
;(define (move-missile M1) M1)
;; ============================================================ Move
;; Collision
;; kill
;; game -> game
;; takes game and produces game without any collisions
(check-expect (kill (make-game (list (make-invader 100 100 12))
(list (make-missile 100 100))
T1))
(make-game empty empty T1))
(check-expect (kill (make-game (list (make-invader 100 100 12))
(list (make-missile (+ 100 HIT-RANGE) (+ 100 HIT-RANGE)))
T1))
(make-game empty empty T1))
(check-expect (kill (make-game (list (make-invader 100 100 12))
(list (make-missile (- 100 HIT-RANGE) (- 100 HIT-RANGE)))
T1))
(make-game empty empty T1))
(check-expect (kill (make-game (list (make-invader 200 200 12) (make-invader 100 100 12))
(list (make-missile (- 100 HIT-RANGE) (- 100 HIT-RANGE)))
T1))
(make-game (list(make-invader 200 200 12)) empty T1))
;(define (kill g) g) ;stub
(define (kill g)
(make-game (scrub-collision-invader (game-invaders g) (game-missiles g))
(scrub-collision-missile (game-missiles g) (game-invaders g))
(game-tank g)))
;; scrub-collision-invader
;; ListOfInvader, ListOfMissile -> LOI
;; takes out all instances of collisions and outputs scrubbed LOI
(check-expect (scrub-collision-invader (list (make-invader 100 100 12))
(list (make-missile 100 100)))
empty)
(check-expect (scrub-collision-invader (list (make-invader 200 200 12) (make-invader 100 100 12))
(list (make-missile (- 100 HIT-RANGE) (- 100 HIT-RANGE))))
(list (make-invader 200 200 12)))
(check-expect (scrub-collision-invader (list (make-invader 100 100 12) (make-invader 200 200 12))
(list (make-missile (- 100 HIT-RANGE) (- 100 HIT-RANGE)) (make-missile 200 200)))
empty)
;(define (scrub-collision-invader loi lom) loi)
(define (scrub-collision-invader loi lom)
(cond [(empty? loi) empty]
[else
(if (collision-invader-missile? (first loi) lom)
(scrub-collision-invader (rest loi) lom)
(cons (first loi) (scrub-collision-invader (rest loi) lom)))]))
;; collision-invader-missile?
;; determines of a missile and invader are colliding
;; !!!
(check-expect (collision-invader-missile? (make-invader 100 100 12) (list (make-missile 100 100) (make-missile 200 200))) true)
(check-expect (collision-invader-missile? (make-invader 80 80 12) (list (make-missile 90 90))) true)
(check-expect (collision-invader-missile? (make-invader 100 100 12) (list (make-missile 120 120))) false)
;(define (collision-invader-missile? i lom) true)
(define (collision-invader-missile? i lom)
(cond [(empty? lom) false]
[else
(if (collision-indiv? i (first lom))
true
(collision-invader-missile? i (rest lom)))]))
;;collision-indiv?
;; invader missile -> boolean
;; determines if indiv invader/missile are colliding
;; !!!
(check-expect (collision-indiv? (make-invader 100 100 12) (make-missile 100 100)) true)
(check-expect (collision-indiv? (make-invader 90 90 12) (make-missile 100 100)) true)
(check-expect (collision-indiv? (make-invader 100 100 12) (make-missile 200 200)) false)
;(define (collision-indiv? i m) true)
(define (collision-indiv? i m)
(and (<= (abs (- (invader-x i) (missile-x m))) 10)
(<= (abs (- (invader-y i) (missile-y m))) 10)))
;; scrub-collision-missile
;; ListOfInvader, ListOfMissile -> LOM
;; takes out all instances of collisions and outputs scrubbed LOM
(define (scrub-collision-missile lom loi)
(cond [(empty? lom) empty]
[else
(if (collision-missile-invader? (first lom) loi)
(scrub-collision-missile (rest lom) loi)
(cons (first lom) (scrub-collision-missile (rest lom) loi)))]))
;; collision-missile-invader
;; determines of a missile and invader are colliding
;; !!!
(check-expect (collision-missile-invader? (make-missile 100 100) (list (make-invader 100 100 12) (make-invader 90 90 12))) true)
(check-expect (collision-missile-invader? (make-missile 100 100) (list (make-invader 90 90 12))) true)
(check-expect (collision-missile-invader? (make-missile 100 100) (list (make-invader 140 140 12))) false)
;(define (collision-invader-missile? i lom) true)
(define (collision-missile-invader? m loi)
(cond [(empty? loi) false]
[else
(if (collision-missile? m (first loi))
true
(collision-missile-invader? m (rest loi)))]))
;(define (collision-indiv? i m) true)
(define (collision-missile? m i)
(and (<= (abs (- (missile-x m) (invader-x i))) 10)
(<= (abs (- (missile-y m) (invader-y i))) 10)))
;; ============================================================ Collision
;; start of Display
;; display-game
;; game -> IMG
;; game is struct (make game (invader missile tank)
;; !!!
;; have to check 1. hits 2. game over
(check-expect (display-game G0) (place-image TANK (/ WIDTH 2) TANK-HEIGHT BACKGROUND)) ; normal case
(check-expect (display-game (make-game (list (make-invader 150 100 12))
(list (make-missile 150 300))
(make-tank 50 1)))
(place-image TANK 50 TANK-HEIGHT (place-image MISSILE 150 300 (place-image INVADER 150 100 BACKGROUND))))
(check-expect (display-game (kill (make-game (list (make-invader 150 100 12) ; Missile Hit
(make-invader 200 200 12))
(list (make-missile 150 100)
(make-missile (+ 200 HIT-RANGE) (- 200 HIT-RANGE)))
(make-tank (/ WIDTH 2) TANK-HEIGHT/2))))
(place-image TANK (/ WIDTH 2) TANK-HEIGHT BACKGROUND))
;(define (display-game G0) BACKGROUND) ;stub
(define (display-game s)
(place-loi (game-invaders s)
(place-lom (game-missiles s)
(place-tank (game-tank s)))))
;;place-loi
;; listofinvaders -> image
;; purpose. takes list of invaders and outputs correct placement (keep in mind missile collisions)
(check-expect (place-loi (list (make-invader 150 100 12) (make-invader 200 200 12)) BACKGROUND)
(place-image INVADER 200 200 (place-image INVADER 150 100 BACKGROUND)))
;(define (place-loi loi) BACKGROUND)
(define (place-loi loi img)
(cond [(empty? loi) img]
[else
(place-image INVADER (invader-x (first loi)) (invader-y (first loi))
(place-loi (rest loi) img))]))
;; place-lom
;; listofmissiles -> image
;; purpose take list of missiles and plot on image (keep in mind collisions)
(check-expect (place-lom (list (make-missile 100 200) (make-missile 50 100)) BACKGROUND)
(place-image MISSILE 100 200 (place-image MISSILE 50 100 BACKGROUND)))
(define (place-lom lom img)
(cond [(empty? lom) img]
[else
(place-image MISSILE (missile-x (first lom)) (missile-y (first lom))
(place-lom (rest lom) img))]))
;; place-tank
;; tank -> Image
(define (place-tank tank)
(place-image TANK (tank-x tank) TANK-HEIGHT BACKGROUND))
;; ============================================================ Display
;; start of KeyEvent
;;play-game
;; Game KeyEvent -> Game
;; Composite Shoot Missile with move tank
;(define (play-game ke G) G)
(define (play-game G ke)
(shoot-missile (move-tank G ke) ke))
;; move-tank
;; Game KeyEvent -> Game
(check-expect (move-tank G0 "left") (make-game empty empty (make-tank (- (/ WIDTH 2) 10) -1)))
(check-expect (move-tank G0 "right") (make-game empty empty (make-tank (+ (/ WIDTH 2) 10) 1)))
(check-expect (move-tank G1 "left") (make-game empty empty (make-tank (- 50 10) -1)))
(check-expect (move-tank G2 "left") (make-game (list I1) (list M1) (make-tank (- 50 10) -1)))
(check-expect (move-tank G3 " ") G3)
;(define (move-tank T ke) T)
(define (move-tank s ke)
(cond [(key=? ke "left")
(make-game (game-invaders s)
(game-missiles s)
(make-tank (- (tank-x (game-tank s)) 10) -1))]
[(key=? ke "right")
(make-game (game-invaders s)
(game-missiles s)
(make-tank (+ (tank-x (game-tank s)) 10) 1))]
[else
(make-game (game-invaders s)
(game-missiles s)
(game-tank s))]))
;; shoot-missile
;; Game KeyEvent -> Game
(check-expect (shoot-missile G0 " ") (make-game empty (list (make-missile (/ WIDTH 2) TANK-HEIGHT)) (game-tank G0)))
(check-expect (shoot-missile G2 " ") (make-game (list I1) (cons (make-missile 50 TANK-HEIGHT) (list M1)) (game-tank G2)))
(check-expect (shoot-missile G2 "a") G2)
(define (shoot-missile s ke)
(cond [(key=? ke " ")
(make-game (game-invaders s)
(cons (make-missile (tank-x (game-tank s)) TANK-HEIGHT) (game-missiles s))
(game-tank s))]
[else
(make-game (game-invaders s)
(game-missiles s)
(game-tank s))]))
;; ============================================================ KeyEvents
;; stop-when
;; invaded
;; Game -> Boolean
;; produce true when invader reaches zero
(check-expect (invaded (make-game (list (make-invader 100 TANK-HEIGHT 12)) empty empty)) true)
(check-expect (invaded (make-game (list (make-invader 100 100 12)) empty empty)) false)
(check-expect (invaded (make-game (list (make-invader 100 100 12) (make-invader 100 TANK-HEIGHT 12)) empty empty)) true)
;(define (invaded G) true)
(define (invaded s)
(broken? (game-invaders s)))
;; broken?
;; listofInvader -> boolean
;; produce true if any invader in list at tankheight
(check-expect (broken? (list (make-invader 100 100 12) (make-invader 100 TANK-HEIGHT 12))) true)
(check-expect (broken? (list (make-invader 100 100 12) (make-invader 100 120 12))) false)
(check-expect (broken? (list (make-invader 100 TANK-HEIGHT 12) (make-invader 100 100 12))) true)
;(define (broken? loi) true)
(define (broken? loi)
(cond [(empty? loi) false]
[else
(if (breached? (first loi))
true
(broken? (rest loi)))]))
;; breached?
;; invader -> boolean
;; produces true if invader y at tankheight
(check-expect (breached? (make-invader 100 TANK-HEIGHT 12)) true)
(check-expect (breached? (make-invader 100 100 12)) false)
(check-expect (breached? (make-invader 100 TANK-HEIGHT 12)) true)
;(define (breached? G) G)
(define (breached? i)
(>= (invader-y i) TANK-HEIGHT))
;; game-over
;; Boolean from lastworld? -> image
;; produce game-over image
(define GAME-OVER (overlay (text "Game Over" 24 "red") (rectangle 200 40 "solid" "black")))
(define (game-over b)
(place-image GAME-OVER (/ WIDTH 2) (/ HEIGHT 2) BACKGROUND))