풀스택 웹🌐 개발자 지망생 🧑🏽‍💻
➕ 인공지능 관심 🤖


Categories


Recent views

  • 1
  • 2
  • 3
  • 4
  • 5

스페이스 인베이더-세계 설계 예시 프로젝트

  1. 시연
    • 목표
      • 코드(김 주의)
        • 소감

          어떻게 코딩할 것인가-세계 설계 예시 프로젝트

          🗣️ 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 기능 없음
              • 함수 참조 확인 기능 없음
              • 패키지 분할은 적어도, 이번 프로젝트에서 사용하지 않게 막아놔서 한 파일에 전부 작성
              • 버그 표시 및 오타 지정, 사용하지 않는 상수 등의 표시 없음
          • 기존의 게임 엔진들의 기초적인 기능들이 얼마나 좋은 기능인지 알 수 있었다.
            • 랜더링부터 스프라이트 충돌 설정까지 제로부터 전부 직접 구현해야 했다.
            • 물론, 덕분에 아주 뿌듯했다.
          • 넉넉하게 10~12시간 정도 걸린것 같은데, 좀더 숙달되고, 좋은 IDE와 현세대 프로그래밍 언어로 구현하면 절반 이하로도 구현할 수 있을 것 같다.
            • 하지만 만약, 내가 이 정도의 프로그램을 기존처럼 설계없이 코드했다면 두배의 시간이 들었을 것 같다.
          • 앞으로 알고리즘 문제나 다른 경우에도 이 설계법을 도입하고 싶고, 다음 강의가 너무 기대된다.