Wednesday, 29 August 2012

Special Exercise - Space Invaders Part 4

 Space Invaders Part 4


My goals for the 4th iteration were to:

  •   allow the tank to shoot at the invaders
  •   have invaders  'die' when hit


To implement these goals we need a number of new elements and behaviours:

  •  the world now has a collection of bullets.
  •  bullets  have a location and consist of a single block.
  •  bullets move upwards until they collide with a space invader (cause the  invader to disappear)


In addition play testing showed that we need to restrict the number of bullets on the screen (or introduce a delay between shots). Without this you can just fire a lethal stream of bullets taking out all the UFO's very quickly.

Bullets, tanks and ufos turned out to be structured very similarly. I should have implemented these using plain posn's as this would have allowed more function sharing.

Code


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

; constants
(define WIDTH 300)     ; the maximal number of blocks horizontally
(define HEIGHT 300)    ; the maximal number of blocks horizontally
(define BULLET_DIST 5) ; the distance travelled by a bullet
(define SIZE 10)       ; the size of the block making up the ufo
(define UFO-WIDTH 50)
(define MAX-BULLETS 2) ; max number of bullets on screen at a time

(define BLOCK ;rendered as red squares with black rims  - stolen from tetris

  (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 (printf  "first ~a" (+ (ufo-x (first ufos))))
              (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 a bullet on the background
; bullet, scene -> scene
(define (draw-bullet bullet background)
   (place-image/align BLOCK (bullet-x bullet) 
                            (bullet-y bullet) "left" "bottom" background))

; Draw a collection of bullets
(define (draw-bullets bullets background)
  (cond ((empty? bullets) background)
        (else (draw-bullet (first bullets) 
                           (draw-bullets (rest bullets) background)))))

; Move the bullets;

; If the bullet moves past the top of the screen, remove it.
(define (move-bullets bullets)
  (cond ((empty? bullets) bullets)
        (else  (cond ((> 0 (bullet-y (first bullets))) 
                         (move-bullets (rest bullets)))
                     (else
                         (cons (move-bullet (first bullets))
                               (move-bullets (rest bullets))))))))

; simple test case for moving a list of bullets

(check-expect (move-bullets (list (make-bullet 100 100) (make-bullet 90 100)))
              (list (make-bullet 100 (- 100 BULLET_DIST))
                    (make-bullet 90 (- 100 BULLET_DIST))))


; Move a bullet

(define (move-bullet bullet)
  (make-bullet (bullet-x bullet) (- (bullet-y bullet) BULLET_DIST)))

; basic bullet moving test

(check-expect (move-bullet (make-bullet 50 50)) (make-bullet 50 (- 50 BULLET_DIST)))

                               

; 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)
                        (draw-bullets (world-bullets 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))))



; simplest test for changing direction.
(check-expect (change-ufo-direction (make-ufo 10 10 1)) (make-ufo 10 40 -1))
                                                                 

; 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) ))




; Fire a bullet and returns new collection of bullets

; Allows only MAX-BULLETS on screen
; World -> Bullets
(define (fire-tank world)
  (cond ((eq? MAX-BULLETS (length (world-bullets world))) (world-bullets world))
        (else
         (cons (make-bullet (tank-x (world-tank world) )
                     (tank-y (world-tank world) )) (world-bullets world) ))))

; 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) ]
                [else (world-tank world)])
              (cond
                [(key=? " " ke)  (fire-tank world)]
                [else (world-bullets world)]
              )))

; bullets/ufo collision detector

; returns true in the event of a collision
; ufo, bullets -> boolean
(define (collide? ufo bullets)
  (cond ((empty? bullets) false)
        ((collide-helper?  (first bullets) ufo) true)
        ( else (collide? ufo (rest bullets)))))

; bullet/ufo collision helper. detects collision between a single bullet and
; a ufo
; ufo, bullet -> boolean
(define (collide-helper?  bullet ufo)
  (cond ((and (between? (bullet-x bullet) (- (ufo-x ufo) SIZE 5) 
                                          (+ (ufo-x ufo)  SIZE 5))
              (between? (bullet-y bullet) (- (ufo-y ufo) SIZE 5)
                                          (+ (ufo-y ufo) SIZE 5)))
               true)
        (else false)))

; helper function to determine if a number is within a range.

; i'm sure there must be a similar one defined in the libs, but I 
; couldn't find it.
; Integer, Integr, Integer -> Boolean
(define (between? x low high)
  (cond ((and (> x low) (< x high) true))
        (else false)))

(check-expect (between? 50 10 100) true)

(check-expect (between? 10 50 100) false)

; remove any ufos from the collection that have been hit by a bullet

; ufos, bullets -> ufos
(define (bullet-collision-ufos ufos bullets)
  (cond ((empty? ufos)       ufos )
        (else (cond ((collide? (first ufos) bullets) 
                               (bullet-collision-ufos (rest ufos) bullets))
                    (else (cons (first ufos) 
                                (bullet-collision-ufos (rest ufos) bullets)))))))
                                                   

; On each clock tick, move the world further in time.

; this calls the bullet-collision-ufos function twice - this calculates which
; bullets and ufos are left. I wish I didn't have to call this twice but not 
; sure how else to save the value...
(define (progress-world world)
  (make-world
     (bullet-collision-ufos (move-ufos (world-ufos world)) 
                                       (world-bullets  world))
                (world-tank world)
                (move-bullets (world-bullets world))))


; This is the big bang function that drives the game.

(define (space-invaders-main rate)
  (big-bang (make-world (make-ufos 3 empty ) TANK empty)      
            (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))


; simple example - a single block in an empty landscape
(check-expect (draw-ufo TEST-UFO (empty-scene WIDTH HEIGHT))
  (place-image/align BLOCK (ufo-x TEST-UFO) (ufo-y TEST-UFO) "left" "bottom"
      (place-image/align BLOCK (- (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (+ (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (ufo-x TEST-UFO) (+ (ufo-y TEST-UFO) SIZE) 
         "left" "bottom" 
                (empty-scene WIDTH HEIGHT))))))

; test drawing three ufo's via the draw-ufos function
(check-expect (draw-ufos TEST-UFOS (empty-scene WIDTH HEIGHT))
              (draw-ufo (first TEST-UFOS) 
                (draw-ufo (second TEST-UFOS) 
                   (draw-ufo (third TEST-UFOS) (empty-scene WIDTH HEIGHT)))))

; test the tank drawing works
(check-expect (draw-tank TANK (empty-scene WIDTH HEIGHT))
  (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" 
        (empty-scene WIDTH HEIGHT)))))))))



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)

Tuesday, 14 August 2012

Special Exercise - Space Invaders Part 2


Design a complete Space Invaders Game  - Part 2


For the second iteration I am going to add a big-bang function to the game and draw the game as a game world. The game world current consists of a collection of ufos and our tank.


As adding the game code here for drawing the world is pretty simple I have also added a function that will programatically generate a collection of ufos. This is to save specifying the initial position of each ufo by hand.  To start with I am drawing 3 rows of how ever many ufos will currently fit.

Code






(require racket/base)
(define-struct ufo (x y) #: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))   ; for testing
(define TEST-UFOS (list (make-ufo 30 30)
                        (make-ufo 80 30)
                        (make-ufo 130 30))); 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 supplied 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) 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) ufos )))))




; create a complete 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 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))))


; This is the big bang function that drives the game.
(define (space-invaders-main rate)
  (big-bang (make-world (make-ufos 3 empty) TANK)
            (to-draw    render-scene)))


(space-invaders-main 0.2)





; tests


; simple example - a single block in an empty landscape
(check-expect (draw-ufo TEST-UFO (empty-scene WIDTH HEIGHT))
  (place-image/align BLOCK (ufo-x TEST-UFO) (ufo-y TEST-UFO) "left" "bottom"
      (place-image/align BLOCK (- (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (+ (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (ufo-x TEST-UFO) (+ (ufo-y TEST-UFO) SIZE) 
         "left" "bottom" 
                (empty-scene WIDTH HEIGHT))))))


; test drawing three ufo's via the draw-ufos function
(check-expect (draw-ufos TEST-UFOS (empty-scene WIDTH HEIGHT))
              (draw-ufo (first TEST-UFOS) 
                (draw-ufo (second TEST-UFOS) 
                   (draw-ufo (third TEST-UFOS) (empty-scene WIDTH HEIGHT)))))


; test the tank drawing works
(check-expect (draw-tank TANK (empty-scene WIDTH HEIGHT))
  (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" 
        (empty-scene WIDTH HEIGHT)))))))))



Tuesday, 7 August 2012

Special Exercise - Space Invaders Part 1

Design a complete Space Invaders Game

The description of the space invader game we are given is very sparse. This is probably because the details don't matter so much as the act of actually designing the game.  For lack of further specification I'm going to try for something similar to the original space invader game. 


We will have a  few rows of aliens that will be moving slowly closer (and left and right). At the moment I am not planning on letting them shoot, but that may change. On the ground there will be a tank that can move left and right and shoot at and destroy the aliens. (With possibly some blocks to hide behind if the aliens end up being able to shoot.  If any of the aliens reach earth, the player loses. If he manages to destroy all the ufo's the player wins. 


For this  first iteration I'm going to create the functions to:

  1.  draw the ufo,
  2.  draw a collection of ufos
  3.  draw our tank

Code



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


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





; 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 BLOCK ; they are rendered as red squares with black rims  - stolen from tetris
  (overlay (rectangle (- SIZE 1) (- SIZE 1) "solid" "red")
           (rectangle SIZE SIZE "outline" "black")))


; 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))))))))


(define (draw-ufos ufos background)
           (cond ((empty? ufos)  background)
                 (else (draw-ufo (first ufos) 
                                 (draw-ufos (rest ufos) background)))))




; tests


; simple example - a single block in an empty landscape
(check-expect (draw-ufo TEST-UFO (empty-scene WIDTH HEIGHT))
  (place-image/align BLOCK (ufo-x TEST-UFO) (ufo-y TEST-UFO) "left" "bottom"
      (place-image/align BLOCK (- (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (+ (ufo-x TEST-UFO) SIZE) (ufo-y TEST-UFO) 
         "left" "bottom"
       (place-image/align BLOCK (ufo-x TEST-UFO) (+ (ufo-y TEST-UFO) SIZE) 
         "left" "bottom" 
                (empty-scene WIDTH HEIGHT))))))


; test drawing three ufo's via the draw-ufos function
(check-expect (draw-ufos TEST-UFOS (empty-scene WIDTH HEIGHT))
              (draw-ufo (first TEST-UFOS) 
                (draw-ufo (second TEST-UFOS) 
                   (draw-ufo (third TEST-UFOS) (empty-scene WIDTH HEIGHT)))))


; test the tank drawing works
(check-expect (draw-tank TANK (empty-scene WIDTH HEIGHT))
  (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" 
        (empty-scene WIDTH HEIGHT)))))))))