풀스택 웹🌐 개발자 지망생 🧑🏽💻
➕ 인공지능 관심 🤖
Categories
-
┣
▶ COMPUTER_SCIENCE
📂: 7 -
┣
▶ WEB
📂: 3 -
┣
▶ ETC
📂: 3-
┃
┣
ETCS
📄: 10 -
┃
┣
SUBBRAIN 개발기
📄: 5 -
┃
┗
YOS 개발기
📄: 1
-
┃
┣
-
┗
▶ AI
📂: 9-
┣
AITOOLS
📄: 3 -
┣
CV
📄: 2 -
┣
DEEP_LEARNING
📄: 1 -
┣
DATA_VIS
📄: 2 -
┣
GRAPH
📄: 1 -
┣
LIGHTWEIGHT
📄: 1 -
┣
MATH
📄: 1 -
┣
NLP
📄: 3 -
┗
STRUCTURED_DATA
📄: 2
-
┣
스페이스 인베이더-세계 설계 예시 프로젝트
어떻게 코딩할 것인가-세계 설계 예시 프로젝트
🗣️
quote
Edx 강의 How to Code 시리즈를 정리한 내용입니다.
시연
목표
간단한
스페이스 인베이터 형식
의 게임을 만드는 프로젝트였다.
완벽한 게임을 만드는 것은 아니며 기초적인 기능만 몇개 구현하도록 되어있다.
자세한 명세는 해당
How to Code
에서 확인할 수 있다.
코드(김 주의)
- 혼자서 BSL로 Dr.Racket 에디터에서 작성됨(2023.01.19)
- 대략 600줄, 상수부터 데이터, 함수, 세계 설계까지 전부 포함
- 104 개의 테스트 코드 작성
- 28 개의 함수 작성
🧾️
간단한 SPACE INVADER
(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)
(define INVADE-RATE 100)
(define BACKGROUND (place-image (above (text "ROADVIRUSHN'S" 24 "lightgreen") (text "HTC 1 Final PJT" 24 "lightgreen") (text "space = shot" 20 "gray") (text " left, right = move" 20 "gray")) (/ WIDTH 2) 100 (empty-scene WIDTH HEIGHT)))
(define INVADER
(overlay/xy (ellipse 10 15 "outline" "blue") ;cockpit cover
-5 6
(ellipse 20 10 "solid" "blue"))) ;saucer
(define TANK
(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))
(define MISSILE (ellipse 5 15 "solid" "red"))
(define TICK-PER-SEC 28)
;; Data Definitions:
(define-struct game (invaders missiles tank time))
;; Game is (make-game (listof Invader) (listof Missile) Tank Number)
;; interp. the current state of a space invaders game
;; with the current invaders, missiles, tank position, and current time.
;; Game constants defined below Missile data definition
#;
(define (fn-for-game s)
(... (game-time s)
(fn-for-loinvader (game-invaders s))
(fn-for-lom (game-missiles s))
(fn-for-tank (game-tank s))))
;; Template rules used:
;; - compound :4 field
;; - atomic non-distinct: time(Natural)
;; - reference: invaders field is listof Invader
;; - reference: missiles field is listof Missile
;; - reference: tank field is Tank
(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 T4 (make-tank (/ WIDTH 2) 0)) ;idle
#;
(define (fn-for-tank t)
(... (tank-x t) (tank-dir t)))
;; Template rules used:
;; - compound :2 field
;; - atomic non-distinct: x(Natural)
;; - atomic non-distinct: dir(Natural)
(define-struct invader (x y dir))
;; Invader is (make-invader Number Number Number[-1,1]))
;; interp. the invader is at (x, y) in screen coordinates
;; the invader along x by (dir * INVADER-X-SPEED) pixels per clock tick
(define I1 (make-invader 150 100 1)) ;not landed, moving right
(define I2 (make-invader 150 HEIGHT -1)) ;exactly landed, moving left
(define I3 (make-invader 150 (+ HEIGHT 10) 1)) ;> landed, moving right
(define I4 (make-invader (/ WIDTH 2) 0 1))
#;
(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 I1
(define M2 (make-missile (invader-x I1) (+ (invader-y I1) 10))) ;exactly hit I1
(define M3 (make-missile (invader-x I1) (+ (invader-y I1) 5))) ;> hit I1
#;
(define (fn-for-missile m)
(... (missile-x m) (missile-y m)))
(define G0 (make-game empty empty T0 0))
(define G1 (make-game empty empty T1 0))
(define G2 (make-game (list I1) (list M1) T1 0))
(define G3 (make-game (list I1 I2) (list M1 M2) T1 0))
(define G4 (make-game empty empty T4 0))
; ==================================
; World:
;; game -> game
;; start the world with (main G4)
;;
(define (main game)
(big-bang game ; game
(on-tick tock-game) ; game -> game
(to-draw render-game) ; game -> Image
(on-key key-game))) ; game KeyEvent -> game
;; tock-game
;; game -> game
;; produce the next game status.
;; Add single Invader every 5 secs = 140 tick.
;; Add two Invaders every 15 secs = 420 tick.
; (define (tock-game game) (make-game empty empty (make-tank (/ WIDTH 2) 0) 0)) ; stub
(check-expect (tock-game (make-game empty empty (make-tank (/ WIDTH 2) 0) 0)) (make-game empty empty (make-tank (/ WIDTH 2) 0) 1))
(check-expect (tock-game (make-game empty empty (make-tank (/ WIDTH 2) 0) 140)) (make-game (cons (make-invader (/ WIDTH 2) 0 1) empty) empty (make-tank (/ WIDTH 2) 0) 141))
(check-expect (tock-game (make-game (cons (make-invader (/ WIDTH 2) 0 1) empty) empty (make-tank (/ WIDTH 2) 0) 141)) (make-game (cons (make-invader (+ (/ WIDTH 2) INVADER-X-SPEED) INVADER-Y-SPEED 1) empty) empty (make-tank (/ WIDTH 2) 0) 142))
;; <Template from game>
(define (tock-game s)
(make-game
(next-invader s)
(next-missile s)
(next-tank (game-tank s))
(next-game-time (game-time s))))
;; next-game-time
;; number -> number
;; get next game time
(define (next-game-time s) (+ s 1))
(check-expect (next-game-time 0) 1)
;; next-tank
;; Tank -> Tank
;; get next Tank status
; (define (next-tank t) (make-tank (/ WIDTH 2) 0)) ;stub
;; Tests:
(check-expect (next-tank (make-tank (/ WIDTH 2) 0)) (make-tank (/ WIDTH 2) 0)) ; no move
(check-expect (next-tank (make-tank (/ WIDTH 2) 1)) (make-tank (+ (/ WIDTH 2) (* TANK-SPEED 1)) 1)) ; right
(check-expect (next-tank (make-tank (/ WIDTH 2) -1)) (make-tank (+ (/ WIDTH 2) (* TANK-SPEED -1)) -1)) ; left
(check-expect (next-tank (make-tank 0 -1)) (make-tank 0 0)) ; left when left edge
(check-expect (next-tank (make-tank WIDTH 1)) (make-tank WIDTH 0)) ; right when right edge
(check-expect (next-tank (make-tank 0 1)) (make-tank (+ 0 (* TANK-SPEED 1)) 1)) ; right when left edge
(check-expect (next-tank (make-tank WIDTH -1)) (make-tank (+ WIDTH (* TANK-SPEED -1)) -1)) ; left when right edge
;; <Template from Tank>
(define (next-tank t)
(cond [(over-x-edge? (next-tank-pos (tank-x t) (tank-dir t))) (make-tank (tank-x t) 0)]
[else (make-tank (next-tank-pos (tank-x t) (tank-dir t)) (tank-dir t))]))
;; next-tank-pos
;; number number -> number
;; get next tank position
; (define (next-tank-pos x t) x) ;stub
;; Tests:
(check-expect (next-tank-pos (/ WIDTH 2) 0) (/ WIDTH 2))
(check-expect (next-tank-pos (/ WIDTH 2) 1) (+ TANK-SPEED (/ WIDTH 2)))
(check-expect (next-tank-pos (/ WIDTH 2) -1) (+ (* TANK-SPEED -1) (/ WIDTH 2)))
(define (next-tank-pos x t)
(+ (* TANK-SPEED t) x))
;; Template rules used:
;; - compound: 2 fields
;; - atomic nondistinct : x
;; - atomic nondistinct : d
;; over-x-edge?
;; number -> boolean
;; check if x position is over the edge.
; (define (over-x-edge? x) false) ; stub
;;Tests:
(check-expect (over-x-edge? (+ WIDTH 1)) true)
(check-expect (over-x-edge? (- WIDTH 1)) false)
(check-expect (over-x-edge? -1) true)
(check-expect (over-x-edge? 1) false)
(check-expect (over-x-edge? 0) false)
(check-expect (over-x-edge? WIDTH) false)
(define (over-x-edge? x) (or (> x WIDTH) (< x 0)))
;; Template rules used:
;; - atomic nondistinct : x
;; next-invader
;; game -> ListOfInvader
;; get next invader status from game status.
;(define (next-invader s) empty); stub
;;Tests:
(check-expect (next-invader (make-game empty empty (make-tank (/ WIDTH 2) 0) 0)) empty) ;; default
(check-expect (next-invader (make-game empty empty (make-tank (/ WIDTH 2) 0) 140)) (cons I4 empty)) ;; create first invader
(check-expect (next-invader (make-game empty empty (make-tank (/ WIDTH 2) 0) 420)) (cons (make-invader (- (/ WIDTH 2) 20) 0 1) (cons (make-invader (+ (/ WIDTH 2) 20) 0 1) empty))) ;; create two invader
(check-expect (next-invader
(make-game (cons I4 empty) empty (make-tank (/ WIDTH 2) 0) 160))
(cons (make-invader (+ (/ WIDTH 2) (* INVADER-X-SPEED 1)) (+ 0 INVADER-Y-SPEED) 1) empty)) ;; move invader
(check-expect (next-invader
(make-game (cons (make-invader (+ (/ WIDTH 2) 30) 20 -1) (cons I4 empty)) empty (make-tank (/ WIDTH 2) 0) 160))
(cons (make-invader (+ (+ (/ WIDTH 2) 30) (* INVADER-X-SPEED -1) ) (+ 20 INVADER-Y-SPEED) -1) (cons (make-invader (+ (/ WIDTH 2) (* INVADER-X-SPEED 1) ) (+ 0 INVADER-Y-SPEED) 1) empty))) ;; move invaders
(check-expect (next-invader
(make-game (cons (make-invader 0 0 -1) empty) empty (make-tank (/ WIDTH 2) 0) 160))
(cons (make-invader (+ 0 (* INVADER-X-SPEED 1)) (+ 0 INVADER-Y-SPEED) 1) empty)) ;; bounce invader to right
(check-expect (next-invader
(make-game (cons (make-invader WIDTH 0 1) empty) empty (make-tank (/ WIDTH 2) 0) 160))
(cons (make-invader (+ WIDTH (* INVADER-X-SPEED -1)) (+ 0 INVADER-Y-SPEED) -1) empty)) ;; bounce invader to left
(check-expect (next-invader
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (/ WIDTH 2) 50) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader exactly
(check-expect (next-invader
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (/ WIDTH 2) (- 50 HIT-RANGE)) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader slightly-y
(check-expect (next-invader
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (+ (/ WIDTH 2) HIT-RANGE) 50) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader slightly-x
;; <Template from game>
(define (next-invader s)
(generate-invader
(move-invader
(destroy-invader
(game-invaders s) (game-missiles s)))
(game-time s)))
;; destroy-invader
;; ListOfInvader ListOfMissile -> ListOfInvader
;; destroy hitted invaders
; (define (destroy-invader loi lom) empty) ;stub
;; Tests
(check-expect (destroy-invader empty empty) empty) ; default
(check-expect (destroy-invader (cons I4 empty) (cons (make-missile (/ WIDTH 2) 250) empty)) (cons I4 empty)) ; nothing
(check-expect (destroy-invader (cons I4 empty) (cons (make-missile (/ WIDTH 2) 0) empty)) empty) ; hitted
(check-expect (destroy-invader (cons I4 empty) (cons (make-missile (/ WIDTH 2) HIT-RANGE) empty)) empty) ; hitted2
(check-expect (destroy-invader (cons I4 empty) (cons (make-missile (- (/ WIDTH 2) HIT-RANGE) 0) empty)) empty) ; hitted x direction
(define (destroy-invader loi lom)
(cond [(or (empty? loi) (empty? lom)) loi]
[else (if (check-missile-list-collision lom (first loi))
(destroy-invader (rest loi) lom)
(cons (first loi) (destroy-invader (rest loi) lom)))]))
;; Template rules used:
;; - one of: 2 cases
;; - empty
;; - (cons invader loi)
;; - reference: check-missile-list-collision
;; check-missile-list-collision
;; ListOfMissile invader -> boolean
;; check list of missile hit given invader.
; (define (check-missile-list-collision lom i) false) ; stub
;; Tests:
(check-expect (check-missile-list-collision empty I4) false) ; no missiles
(check-expect (check-missile-list-collision (cons (make-missile 200 200) empty) I4) false) ; no hit
(check-expect (check-missile-list-collision (cons (make-missile (/ WIDTH 2) 0) empty) I4) true) ; hit
(define (check-missile-list-collision lom i)
(cond [(empty? lom) false]
[else (if (check-collision
(missile-x (first lom)) (missile-y (first lom)) (invader-x i) (invader-y i) HIT-RANGE)
true
(check-missile-list-collision (rest lom) i))]))
;; Template rules used:
;; - compound: 2 fields
;; - one of: 2 cases
;; - atomic-distinct: empty
;; - (cons missile listOfMissile)
;; - invader: 2fields
;; - self-reference: lom has type ListOfMissile
;; check-collision
;; number number number number number -> boolean
;; Check given position x1, y1, x2, y2 are collided within hit range.
;; (define (check-collision x1 y1 x2 y2 r) false) ; stub
(check-expect (check-collision 0 0 10 10 10) false); out of hit range
(check-expect (check-collision 0 0 5 5 10) true); collided
(check-expect (check-collision 0 0 10 0 10) true); edge case
(check-expect (check-collision 0 0 3 4 5) true); edge case 2
(check-expect (check-collision 0 0 0 0 1) true)
(define (check-collision x1 y1 x2 y2 r)
(if (>= (* r r) (+(*(- x2 x1)(- x2 x1)) (*(- y2 y1)(- y2 y1))))
true
false))
;; generate-invader
;; ListOfInvader number -> ListOfInvader
;; generate invader by time.
; (define (generate-invader loi t) empty) ;stub
;;Tests
(check-expect (generate-invader empty 140) (cons I4 empty)) ;; create first ufo
(check-expect (generate-invader empty 420) (cons (make-invader (- (/ WIDTH 2) 20) 0 1) (cons (make-invader (+ (/ WIDTH 2) 20) 0 1) empty))) ;; create two ufos
(check-expect (generate-invader (cons (make-invader (/ WIDTH 2) 100 1) empty) 280) (cons I4 (cons (make-invader (/ WIDTH 2) 100 1) empty))) ;; create duplicate ufo
(define (generate-invader loi t)
(cond [(and (> t 0) (= (modulo t 420) 0)) (cons (make-invader (- (/ WIDTH 2) 20) 0 1) (cons (make-invader (+ (/ WIDTH 2) 20) 0 1) loi))]
[(and (> t 0) (= (modulo t 140) 0)) (cons I4 loi)]
[else loi]))
;; Template rules used:
;; - interval t
;; - compound: 2cases
;; - atomic distinct: empty
;; - (cons invader loi)
;; - self-reference: loi
;; move-invader
;; ListOfInvader -> ListOfInvader
;; move all the invader in the invader list.
;; (define (move-invader loi) empty);stub
;;Tests:
(check-expect (move-invader empty) empty)
(check-expect (move-invader (cons (make-invader (/ WIDTH 2) 50 1) empty)) (cons (make-invader (+ (/ WIDTH 2) (* INVADER-X-SPEED 1)) (+ 50 INVADER-Y-SPEED) 1) empty)) ;; move invader
(check-expect (move-invader (cons (make-invader (+ 20 (/ WIDTH 2)) 100 -1) (cons (make-invader (/ WIDTH 2) 50 1) empty))) (cons (make-invader (+ (+ 20 (/ WIDTH 2)) (* INVADER-X-SPEED -1)) (+ 100 INVADER-Y-SPEED)-1) (cons (make-invader (+ (/ WIDTH 2) (* INVADER-X-SPEED 1)) (+ 50 INVADER-Y-SPEED) 1) empty))) ;; move invaders
(check-expect (move-invader (cons (make-invader 0 50 -1) empty)) (cons (make-invader (+ 0 (* INVADER-X-SPEED 1)) (+ 50 INVADER-Y-SPEED) 1) empty)) ; bounce to right
(check-expect (move-invader (cons (make-invader WIDTH 50 1) empty)) (cons (make-invader (+ WIDTH (* INVADER-X-SPEED -1)) (+ 50 INVADER-Y-SPEED) -1) empty)) ; bounce to left
;; <Template from move-missile>
(define (move-invader i)
(cond [(empty? i) empty]
[(over-x-edge? (next-invader-x-pos (invader-x (first i)) (invader-dir (first i))))
(cons (bounce (first i)) (move-invader (rest i)))]
[else (cons (make-invader (next-invader-x-pos (invader-x (first i)) (invader-dir (first i))) (+ (invader-y (first i)) INVADER-Y-SPEED) (invader-dir (first i)))
(move-invader (rest i)))]))
;; bounce
;; invader -> invader
;; make invader bounce
; (define (bounce i) i) ;stub
;; Tests:
(check-expect (bounce (make-invader 0 0 -1)) (make-invader (+ 0 (* INVADER-X-SPEED 1)) (+ 0 INVADER-Y-SPEED) 1))
(check-expect (bounce (make-invader WIDTH 0 1)) (make-invader (+ WIDTH (* INVADER-X-SPEED -1)) (+ 0 INVADER-Y-SPEED) -1))
(check-expect (bounce (make-invader (/ INVADER-X-SPEED 2) 0 -1)) (make-invader (- (* INVADER-X-SPEED 1) (/ INVADER-X-SPEED 2)) (+ 0 INVADER-Y-SPEED) 1))
(check-expect (bounce (make-invader (- WIDTH (/ INVADER-X-SPEED 2)) 0 1)) (make-invader (- WIDTH (- INVADER-X-SPEED (/ INVADER-X-SPEED 2))) (+ 0 INVADER-Y-SPEED) -1))
(define (bounce i) (make-invader (get-bounced-x (invader-x i)) (+ (invader-y i) INVADER-Y-SPEED) (* (invader-dir i) -1)))
;; get-bounced-x
;; number -> number
;; get x position after bounced.
; (define (get-bounced-x n) n);stub
;; ASSUME: position number x or WIDTH - x should be less than INVADER-X-SPEED
;; Tests:
(check-expect (get-bounced-x 0) INVADER-X-SPEED)
(check-expect (get-bounced-x WIDTH) (- WIDTH INVADER-X-SPEED))
(check-expect (get-bounced-x (/ INVADER-X-SPEED 2)) (/ INVADER-X-SPEED 2))
(check-expect (get-bounced-x (- WIDTH (/ INVADER-X-SPEED 2))) (- WIDTH (/ INVADER-X-SPEED 2)))
(define (get-bounced-x n) (if (> INVADER-X-SPEED n)
(- INVADER-X-SPEED n)
(- WIDTH (- INVADER-X-SPEED (- WIDTH n)))))
;; next-invader-x-pos
;; number number -> number
;; get next invader x postion to given direction.
; (define (next-invader-x-pos x d) x) ;stub
(define (next-invader-x-pos x d) (+ x (* INVADER-X-SPEED d)))
;; skip verbose jobs with short func.
;; next-missile
;; game -> ListOfInvader
;; get next missile status from game status.
; (define (next-missile s) empty);stub
;;Tests:
(check-expect (next-missile (make-game empty empty (make-tank (/ WIDTH 2) 0) 0)) empty) ;; default
(check-expect (next-missile
(make-game empty (cons (make-missile (/ WIDTH 2) 50) empty) (make-tank (/ WIDTH 2) 0) 160))
(cons (make-missile (/ WIDTH 2) (- 50 MISSILE-SPEED)) empty));; move missile
(check-expect (next-missile
(make-game empty (cons (make-missile (+ (/ WIDTH 2) 20) 100) (cons (make-missile (/ WIDTH 2) 50) empty)) (make-tank (/ WIDTH 2) 0) 160))
(cons (make-missile (+ (/ WIDTH 2) 20) (- 100 MISSILE-SPEED)) (cons (make-missile (/ WIDTH 2) (- 50 MISSILE-SPEED)) empty))) ;; move missles
(check-expect (next-missile
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (/ WIDTH 2) 50) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader exactly
(check-expect (next-missile
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (/ WIDTH 2) (- 50 HIT-RANGE)) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader slightly-y
(check-expect (next-missile
(make-game (cons (make-invader (/ WIDTH 2) 50 1) empty) (cons (make-missile (+ (/ WIDTH 2) HIT-RANGE) 50) empty) (make-tank (/ WIDTH 2) 0) 160))
empty);; hit invader slightly-x
;; <Template from next-invader>
(define (next-missile s)
(move-missile
(destroy-missile
(game-missiles s) (game-invaders s))))
;; destroy-missile
;; ListOfMissile ListOfInvader -> ListOfMissile
;; destroy hitted missile
; (define (destroy-missile loi lom) empty) ;stub
;; <Tests From destroy-invader>
(check-expect (destroy-missile empty empty) empty) ; default
(check-expect (destroy-missile (cons (make-missile (/ WIDTH 2) 250) empty) (cons I4 empty)) (cons (make-missile (/ WIDTH 2) 250) empty)) ; nothing
(check-expect (destroy-missile (cons (make-missile (/ WIDTH 2) 0) empty) (cons I4 empty)) empty) ; hitted
(check-expect (destroy-missile (cons (make-missile (/ WIDTH 2) HIT-RANGE) empty) (cons I4 empty)) empty) ; hitted2
(check-expect (destroy-missile (cons (make-missile (- (/ WIDTH 2) HIT-RANGE) 0) empty) (cons I4 empty)) empty) ; hitted x direction
;; <Template from destroy-missile>
(define (destroy-missile lom loi)
(cond [(or (empty? lom) (empty? loi)) lom]
[else (if (check-invader-list-collision loi (first lom))
(destroy-missile (rest lom) loi)
(cons (first lom) (destroy-missile (rest lom) loi)))]))
;; check-invader-list-collision
;; ListOfInvader Missile -> boolean
;; check list of Invader hit given Missile.
; (define (check-invader-list-collision lom i) false) ; stub
;; <Tests From check-missile-list-collision>
(check-expect (check-invader-list-collision empty (make-missile 200 200)) false) ; no invader
(check-expect (check-invader-list-collision (cons I4 empty) (make-missile 200 200)) false) ; no hit
(check-expect (check-invader-list-collision (cons (make-invader 200 190 1) empty) (make-missile 200 200)) true) ; hit
;; <Template from check-missile-list-collision>
(define (check-invader-list-collision loi m)
(cond [(empty? loi) false]
[else (if (check-collision (invader-x (first loi)) (invader-y (first loi)) (missile-x m) (missile-y m) HIT-RANGE)
true
(check-invader-list-collision (rest loi) m))]))
;; move-missile
;; ListOfMissile -> ListOfMissle
;; move all the missiles in the Missile list.
; (define (next-missile m) m) ; stub
;;Tests:
(check-expect (move-missile empty) empty)
(check-expect (move-missile (cons (make-missile 0 HEIGHT) empty)) (cons (make-missile 0 (- HEIGHT MISSILE-SPEED)) empty))
(check-expect (move-missile (cons (make-missile 0 0) empty)) empty)
(define (move-missile m)
(cond [(empty? m) empty]
[(over-y-edge? (next-missile-y-pos (missile-y (first m)))) (move-missile (rest m))] ;; contain help function logic for convinience.
[else (cons (make-missile (missile-x (first m)) (next-missile-y-pos (missile-y (first m)))) (move-missile (rest m)))]))
;; over-y-edge?
;; number -> boolean
;; check if y position is over the edge.
; (define (over-y-edge? x) false) ; stub
;;Tests:
(check-expect (over-y-edge? (+ HEIGHT 1)) true)
(check-expect (over-y-edge? (- HEIGHT 1)) false)
(check-expect (over-y-edge? -1) true)
(check-expect (over-y-edge? 1) false)
(check-expect (over-y-edge? 0) false)
(check-expect (over-y-edge? HEIGHT) false)
(define (over-y-edge? y) (or (> y HEIGHT) (< y 0)))
;; Template rules used:
;; - atomic nondistinct : y
;; next-missile-y-pos
;; number -> number
;; check if y position is over the edge.
; (define (over-y-edge? x) false) ; stub
;;Tests:
(check-expect (next-missile-y-pos 0) (- 0 MISSILE-SPEED))
(check-expect (next-missile-y-pos HEIGHT) (- HEIGHT MISSILE-SPEED))
(check-expect (next-missile-y-pos (/ HEIGHT 2)) (- (/ HEIGHT 2) MISSILE-SPEED))
(define (next-missile-y-pos y) (- y MISSILE-SPEED))
;; Template rules used:
;; - atomic nondistinct : y
(define TANK-Y (- HEIGHT 15))
;; game -> Image
;; render screen by game status
; (define (render-game game) BACKGROUND) ; stub
;; TESTS:
(check-expect (render-game G0) (place-image TANK (/ WIDTH 2) TANK-Y BACKGROUND)) ;; default
(check-expect (render-game G1) (place-image TANK 50 TANK-Y BACKGROUND)) ;; moved TANK
(check-expect (render-game G2) (place-image MISSILE (missile-x M1) (missile-y M1) (place-image INVADER (invader-x I1) (invader-y I1)(place-image TANK 50 TANK-Y BACKGROUND)))) ;; bullet and single invader with tank.
(check-expect (render-game G3) (place-image MISSILE (missile-x M2) (missile-y M2) (place-image INVADER (invader-x I2) (invader-y I2) (place-image MISSILE (missile-x M1) (missile-y M1)(place-image INVADER (invader-x I1) (invader-y I1)(place-image TANK 50 TANK-Y BACKGROUND)))))) ;; two bullets and two invaders with tank.
;; <Template from game>
(define (render-game s)
(place-missile
(game-missiles s) (place-invader
(game-invaders s) (place-tank
(game-tank s) BACKGROUND))))
;; place-invader
;; ListOfInvader IMG -> IMG
;; draw invaders in the screen.
; (define (place-invader loi img) BACKGROUND) ;stub
;; Tests:
(check-expect (place-invader empty BACKGROUND) BACKGROUND) ;; nothing
(check-expect (place-invader (cons I1 empty) BACKGROUND) (place-image INVADER (invader-x I1) (invader-y I1) BACKGROUND)) ;; a invader
(check-expect (place-invader (cons I2 (cons I1 empty)) BACKGROUND) (place-image INVADER (invader-x I2) (invader-y I2) (place-image INVADER (invader-x I1) (invader-y I1) BACKGROUND))) ;; two invaders
;; Template:
(define (place-invader loi img)
(cond [(empty? loi) img]
[else (place-invader (rest loi) (place-image INVADER (invader-x (first loi)) (invader-y (first loi)) img))]))
;; Template rules used:
;; - compound:
;; - one of two cases:
;; - empty
;; - (cons invader loi)
;; - Image
;; place-missile
;; ListOfMissile IMG -> IMG
;; draw missiles in the screen.
; (define (place-missile loi img) BACKGROUND) ;stub
;; Tests:
(check-expect (place-missile empty BACKGROUND) BACKGROUND) ;; nothing
(check-expect (place-missile (cons M1 empty) BACKGROUND) (place-image MISSILE (missile-x M1) (missile-y M1) BACKGROUND)) ;; a missile
(check-expect (place-missile (cons M2 (cons M1 empty)) BACKGROUND) (place-image MISSILE (missile-x M2) (missile-y M2) (place-image MISSILE (missile-x M1) (missile-y M1) BACKGROUND))) ;; two missiles
;; <Template FROM place-invader>
(define (place-missile lom img)
(cond [(empty? lom) img]
[else (place-missile (rest lom) (place-image MISSILE (missile-x (first lom)) (missile-y (first lom)) img))]))
;; place-tank
;; Tank IMG -> IMG
;; draw tank in the screen.
;(define (place-tank tank img) BACKGROUND) ;stub
;; Tests:
(check-expect (place-tank T0 BACKGROUND) (place-image TANK (tank-x T0) TANK-Y BACKGROUND)) ;; a tank
(check-expect (place-tank T1 BACKGROUND) (place-image TANK (tank-x T1) TANK-Y BACKGROUND)) ;; tank moved
;; <Template FROM place-invader>
(define (place-tank tank img)
(place-image TANK (tank-x tank) TANK-Y img))
;; game e -> game
;; produce the new game status when key inputted.
; (define (key-game game e) G0) ; stub
;; Tests:
(check-expect (key-game G0 "e") (make-game empty empty (game-tank G0) 0)) ; nothing changed
(check-expect (key-game G0 " ") (make-game empty (cons (make-missile (tank-x (game-tank G0)) TANK-Y) empty) (game-tank G0) 0)) ; shoot once
(check-expect (key-game G0 "left") (make-game empty empty (make-tank (tank-x (game-tank G0)) -1) 0)) ; go left
(check-expect (key-game G0 "right") (make-game empty empty (make-tank (tank-x (game-tank G0)) 1) 0)) ; go right
;; <Template from handle-key>
(define (key-game g e)
(cond [(key=? e " ") (shot g)]
[(key=? e "left") (go-left g)]
[(key=? e "right") (go-right g)]
[else g]))
;; shot
;; game -> game
;; Create missile at the tank position.
;(define (shot g) g) ;stub
;; Tests :
(check-expect (shot G0) (make-game empty (cons (make-missile (tank-x (game-tank G0)) TANK-Y) empty) (game-tank G0) 0))
;;<Template from game>
(define (shot g)
(make-game (game-invaders g) (cons (make-missile (tank-x (game-tank g)) TANK-Y) (game-missiles g)) (game-tank g) (game-time g)))
;; go-left
;; game -> game
;; make tank go left, change direction to left.
;(define (go-left g) g) ;stub
;; Tests :
(check-expect (go-left G0) (make-game empty empty (make-tank (tank-x (game-tank G0)) -1) 0)) ; go left
;;<Template from game>
(define (go-left g)
(make-game (game-invaders g) (game-missiles g) (make-tank (tank-x (game-tank g)) -1) (game-time g)))
;; go-right
;; game -> game
;; make tank go right, change direction to right.
;(define (go-right g) g) ;stub
;; Tests :
(check-expect (go-right G0) (make-game empty empty (make-tank (tank-x (game-tank G0)) 1) 0)) ; go right
;;<Template from game>
(define (go-right g)
(make-game (game-invaders g) (game-missiles g) (make-tank (tank-x (game-tank g)) 1) (game-time g)))
소감
- 강의에서 배운 설계법을 엄격히 지키면서 진행했다. 처음에는 어색했지만 이윽고 함수 설계와 테스트 설계에 아주 익숙해졌다.
- 설계와 TDD의 위력을 알 수 있었다. 버그가 극도로 적고, 문서화가 동시에 진행됬으며, 프로그램에 대한 설계가 눈에 쉽게 들어왔다.
- 모든 함수의 테스트를 완전히 지키며 코딩하였더니, 마지막으로 이들을 조립하여 완벽한 프로그램을 완성한 뒤의 디버깅은 단 하나의 버그, 30초만에 해결됬다.
- 원래 였다면 수많은 버그들의 시너지로 우왕좌왕했을 것이다.
- 현세대의 프로그래밍 언어와 IDE가 너무나도 그리웠다.
- 비직관적이고 생소한 BSL
- 덧셈 조차 전위 방식으로 짜야 했다. (ex) 1 + 2 (X), (+ 1 2) (O))
- 함수 내에 변수 할당 및 재활용 없음
- 랜덤한 요소가 있는 함수는 어떻게 테스팅해야하는가?
- 별 기능을 지원하지 않는 Dr.Racket
- AutoComplete 기능 없음
- 함수 참조 확인 기능 없음
- 패키지 분할은 적어도, 이번 프로젝트에서 사용하지 않게 막아놔서 한 파일에 전부 작성
- 버그 표시 및 오타 지정, 사용하지 않는 상수 등의 표시 없음
- 비직관적이고 생소한 BSL
- 기존의 게임 엔진들의 기초적인 기능들이 얼마나 좋은 기능인지 알 수 있었다.
- 랜더링부터 스프라이트 충돌 설정까지 제로부터 전부 직접 구현해야 했다.
- 물론, 덕분에 아주 뿌듯했다.
- 넉넉하게 10~12시간 정도 걸린것 같은데, 좀더 숙달되고, 좋은 IDE와 현세대 프로그래밍 언어로 구현하면 절반 이하로도 구현할 수 있을 것 같다.
- 하지만 만약, 내가 이 정도의 프로그램을 기존처럼 설계없이 코드했다면 두배의 시간이 들었을 것 같다.
- 앞으로 알고리즘 문제나 다른 경우에도 이 설계법을 도입하고 싶고, 다음 강의가 너무 기대된다.
_articles/computer_science/OSSU/PL/HowToCode/스페이스 인베이더-세계 설계 예시 프로젝트.md