Tuesday, 21 August 2012

Special Exercise - Space Invaders Part 3

Special Exercise - Space Invaders Part 3

For this third iteration my goals were to

  • Make the tank move left and right.
  • Make the ufos move left and right and (always) down.


While I found it is nice and simple to make the pieces move as needed, I always struggle with the bounds checking. It this case we needed to stop the tank and the ufo's from moving off the side of the screen.

It's not that bounds checking is hard to do, just it always ends up looking messy and detracting from the simplicity of the code. Instead of "move left", it's "move left . . . unless this, or that or something else".  The way I have written it below works, but there is bound to be a better way to do it.

In addition to the changes needed to support the goals above,  ufos' now hold a direction integer to say which direction (left or right) they are currently travelling in.

Code

(require racket/base)
(define-struct ufo (x y direction) #:transparent)
(define-struct tank (x y) #:transparent)
(define-struct world (ufos tank))


; physical constants
(define WIDTH 300) ; the maximal number of blocks horizontally
(define HEIGHT 300) ; the maximal number of blocks horizontally


; graphical constants
(define SIZE 10) ; the size of the block making up the ufo
(define UFO-WIDTH 50)


(define BLOCK ; they are rendered as red squares with black rims  
  (overlay (rectangle (- SIZE 1) (- SIZE 1) "solid" "red")
           (rectangle SIZE SIZE "outline" "black")))


(define TEST-UFO (make-ufo 30 30 1))   ; for testing
(define TEST-UFOS (list (make-ufo 30 30 1)
                        (make-ufo 80 30 1)
                        (make-ufo 130 30 1))); for testing


; initial tank position should be middle of the screen, one block up from the bottom
(define TANK (make-tank (/ WIDTH 2) (- HEIGHT (* SIZE 2))))


; creates a row of ufos on the y-cordinate
; Integer Ufos -> Ufos
(define (make-ufos-for-y y-cordinate ufos)
  (cond ((empty? ufos) (make-ufos-for-y y-cordinate 
                          (cons (make-ufo 20 y-cordinate 1) ufos)))
        ((> (+ (ufo-x (first ufos)) UFO-WIDTH) WIDTH) ufos)
        (else (make-ufos-for-y y-cordinate 
                               (cons (make-ufo (+ (ufo-x (first ufos)) UFO-WIDTH) 
                                                y-cordinate 1) ufos )))))


; create a collection of rows of ufos
; Integer Ufos -> Ufos
(define (make-ufos rows ufos)
  (cond ((= 0 rows) ufos)
        (else (make-ufos (- rows 1) 
                 (append ufos (make-ufos-for-y (- ( * rows 60) 30) empty))))))


; draw a triangular shaped alien
(define (draw-ufo ufo background)
  (place-image/align BLOCK (ufo-x ufo) (ufo-y ufo) "left" "bottom"
    (place-image/align BLOCK (- (ufo-x ufo) SIZE) (ufo-y ufo) "left" "bottom"
     (place-image/align BLOCK (+ (ufo-x ufo) SIZE) (ufo-y ufo) "left" "bottom"
      (place-image/align BLOCK (ufo-x ufo) (+ (ufo-y ufo) SIZE) "left" "bottom"                                         
        background)))))


; draw a tank shaped tank
(define (draw-tank tank background)
  (place-image/align BLOCK (tank-x tank) (tank-y tank) "left" "bottom"
   (place-image/align BLOCK (- (tank-x tank) SIZE) (tank-y tank) "left" "bottom"
    (place-image/align BLOCK (+ (tank-x tank) SIZE) (tank-y tank) "left" "bottom"
     (place-image/align BLOCK (tank-x tank) (+ (tank-y tank) SIZE) 
          "left" "bottom"
      (place-image/align BLOCK (- (tank-x tank) SIZE) (+ (tank-y tank) SIZE ) 
         "left" "bottom"                                                                                                     
        (place-image/align BLOCK (+ (tank-x tank) SIZE) (+ (tank-y tank) SIZE)  
          "left" "bottom"                                                
         (place-image/align BLOCK (tank-x tank) (- (tank-y tank) SIZE) 
           "left" "bottom"                                                                                                                
          background))))))))


; draw the collection of ufos on to the background.
; a simple recursive function
; Ufos Scene -> Scene
(define (draw-ufos ufos background)
  (cond ((empty? ufos)  background)
        (else (draw-ufo (first ufos) (draw-ufos (rest ufos) background)))))




; draws the world as a scene
; World -> Scene
(define (render-scene world)
  (draw-ufos (world-ufos world)
             (draw-tank (world-tank world) (empty-scene WIDTH HEIGHT))))


; move the ufo left or right according to it's current direction
; when it reaches the edge of the screen, flip the direction the ufo 
; is travelling
(define (move-ufo ufo)
 (future-ufo-x  ufo) (ufo-direction ufo) WIDTH)
  (cond ((> 10 (future-ufo-x ufo))  (change-ufo-direction ufo))
        ((< (- WIDTH 10) (future-ufo-x  ufo)) (change-ufo-direction ufo))
        (else    (make-ufo (future-ufo-x  ufo) 
                           (ufo-y ufo) (ufo-direction ufo)))))




; returns an identical ufo but with the direction changed
(define (change-ufo-direction ufo)
  (make-ufo (ufo-x ufo) (+ (ufo-y ufo) 30) (* -1 (ufo-direction ufo))))


; moves each ufo one at a time
(define (move-ufos ufos)
  (cond ((empty? ufos) ufos)
        (else (cons (move-ufo (first ufos)) (move-ufos (rest ufos))))))


; moves the tank left or right
; the direction is positive/negative mulitplier that will
; determine in which direction the tank moves
; we use the future-tank-x function to determine if the tank would be moved
; into a position outside of the game word.
; tank direction -> tank
(define (move-tank tank direction)
  (cond ((> 10 (future-tank-x direction tank))  tank)
        ((< (- WIDTH 20) (future-tank-x direction tank))  tank)
        (else  (make-tank (future-tank-x direction tank) (tank-y tank) ))))


; returns the position the tank will be in if it moves in *direction*
; used for collision detection with walls and actual moving of tank
; Integer Tank -> Integer
(define (future-tank-x direction tank)
  (+ (* SIZE direction) (tank-x tank) ))


; just like future-tank-x but for ufos'
; the diference is that the ufo holds the direction internally
(define (future-ufo-x  ufo)
   (+ (* 1 (ufo-direction ufo)) (ufo-x ufo) ))




; respond to keyboard input.
; Left - Move tank left
; Right - Move tank right
; Space - Fire (not yet)
(define (handle-key-events world ke)
  (make-world (world-ufos world)
              (cond
                [(string=? "left" ke)  (move-tank (world-tank world) -1)]
                [(string=? "right" ke) (move-tank (world-tank world) 1) ])))




; On each clock tick, move the world further in time.
(define (progress-world world)
  (make-world (move-ufos (world-ufos world)) (world-tank world)))




; This is the big bang function that drives the game.
(define (space-invaders-main rate)
  (big-bang (make-world (make-ufos 2 empty) TANK)
            ;(big-bang (make-world (list (make-ufo 10 10 1)) TANK)
           
            (on-key     handle-key-events)
            (on-tick     progress-world rate)
            (to-draw    render-scene)))


(space-invaders-main 0.01)


Tests


; check moving ufo left and right works as expected
(check-expect (move-ufo (make-ufo 10 10 1)) (make-ufo 20 10 1))
(check-expect (move-ufo (make-ufo 20 10 -1)) (make-ufo 10 10 1))
; check hitting the edge of the screen changes the ufos direction
(check-expect (move-ufo (make-ufo WIDTH 10 1)) (make-ufo WIDTH 10 -1))



; check the ufo change direction changes left and right
(check-expect (change-ufo-direction (make-ufo 10 10 1)) (make-ufo 10 10 -1))
(check-expect (change-ufo-direction (make-ufo 10 10 -1)) (make-ufo 10 10 1))




;(other tests are unchanged)

No comments:

Post a Comment