This assignment is due on Wednesday, 9/27 at 11:59 PM. Submit it using the Handin server as assignment a6.
Important Note Whenever we say to "develop a function", we mean that you need to follow the design recipe.
For this assignment, you will program a very simple single-player video game. The player’s goal will be to escape hungry zombies for as long as they can. The world will be represented as a scene containing a player icon (for example, a small green circle) and many zombie icons (for example, small red circles).
In your program, the player is just a position in the scene, which you will implement as a Posn. The actual player will want to be able to move to another position (to avoid, for example, being eaten). While playing, they will see their icon move towards the position of a mouse click (or drag).
Likewise, zombies are just positions in the scene, which you will also implement as Posns. Zombies sense that there is something delicious at the player’s position, and so will move there.
The player has a fixed distance (in pixels) they can move each tick. There is also a fixed zombie speed.
Now the problem is, given a player position and a mouse click position, how do you compute the new player position after one tick, assuming that the player moves in a straight line towards the mouse click position? Likewise, how do you compute the new zombie position after one tick, assuming the zombie moves in a straight line towards the player?
You will define a function approach which will answer both questions. approach will take two positions p, p0 and a distance per tick s and return a position p’.
Intuitively, here’s a way to find p’. Measure the distance (in pixels) from p to p0. Also, determine in which direction p lies with respect to p0 (which we will represent as a point on the unit circle). The idea is that p’ should lie in exactly the same direction, but at a smaller distance: a distance s pixels smaller.
To recapitulate, you can typically find p’ by following these steps: (1) determine the distance and direction of p relative to p0; (2) subtract s from the distance; and (3) compute a new Posn from the new distance and the same direction.
We say "typically" because there is an important exception to consider. Suppose that p and p0 are already close: that is, within s pixels. Then p’ and p0 should just be equal. The player does not overshoot their destination; a zombie stops moving when it has found its meal.
Develop the following three functions:
; posn-sum : Posn Posn -> Posn ; posn-diff : Posn Posn -> Posn ; posn-scale : Number Posn -> Posn
posn-sum computes the sum of two Posns by creating a new Posn from the sum of the x-coordinates and the sum of the y-coordinates. posn-diff similarly computes the difference of two Posns by subtracting the second x-coordinate from the first and the second y-coordinate from the first. Finally, posn-scale scales the given Posn by multiplying both the x- and the y-coordinates by the given number.
Exercise 2 Here we provide the definitions of three helper functions that compute distance, direction and a typical approach. (Note that these may work only as well as your functions from Exercise 1).
Provide the missing unit tests for dist and for approach-helper and the missing purpose statement for approach-helper.
Finally, develop approach. The first argument is the position to be updated. The second argument is the unchanging position being approached. The third input is the speed (pixels/tick).
Notice that approach-helper does not handle the case just described where the input positions are very close. approach must handle this case as well.
; dist : Posn Posn -> Number ; computes the distance between two Posns (define (dist p0 p1) (sqrt (+ (expt (- (posn-x p0) (posn-x p1)) 2) (expt (- (posn-y p0) (posn-y p1)) 2)))) (check-expect ...) (check-expect ...) ; direction : Posn Posn -> Posn ; computes the direction of a Posn with respect to another Posn, ; representing direction as a Posn on the unit circle (define (direction p p0) (posn-scale (/ 1 (dist p p0)) (posn-diff p p0))) (check-within (direction (make-posn 400 400) (make-posn 200 200)) (make-posn (/ (sqrt 2) 2) (/ (sqrt 2) 2)) .01) (check-within (direction (make-posn 0 400) (make-posn 200 200)) (make-posn (- 0 (/ (sqrt 2) 2)) (/ (sqrt 2) 2)) .01) (check-within (direction (make-posn 400 0) (make-posn 200 200)) (make-posn (/ (sqrt 2) 2) (- 0 (/ (sqrt 2) 2))) .01) (check-within (direction (make-posn 0 0) (make-posn 200 200)) (make-posn (- 0 (/ (sqrt 2) 2)) (- 0 (/ (sqrt 2) 2))) .01) ; approach-helper : Posn Posn Number -> Posn ; ... (define (approach-helper p p0 s) (posn-sum p0 (posn-scale (- (dist p p0) s) (direction p p0)))) (check-expect ...) (check-expect ...) ; approach : Posn Posn Number -> Posn ; ... (define (approach p p0 s) ...) (check-expect ...) (check-expect ...)
The goal of this brief section is to define constants for your game.
Define constants pspeed, zspeed, background and close-enough corresponding to the player speed, the zombie speed, the game background and the distance from the player position (in pixels) under which a zombie gets to have its meal (and the game ends). For the game background, a 400 x 400 empty scene works, but feel free to be creative.
This section will focus on player movement.
Exercise 4 Develop a data definition and a struct called pworld which has two fields. One field stores a Posn which is the player position and the other field stores a Posn which is the goal position (towards which the player moves).
Exercise 5 Develop a function move-player which takes and returns a Pworld. move-player will be an on-tick handler for big-bang. In the game, move-player will move the player closer to the goal position stored in the input Pworld. Use approach.
Exercise 6 Develop a function update-pgoal which takes a Pworld, two Numbers and a MouseEvent and returns a Pworld. update-pgoal will be an on-mouse handler for big-bang. In the game, either a "button-down" or "drag" event from the mouse will change the goal position towards which the player is moving.
Exercise 7 Develop a function draw-pworld which takes a Pworld and returns an Image. In the game, you should have an image for the player positioned on the background according to the player position data stored in the input Pworld. (A small green circle works, but feel free to express yourself.)
Exercise 8 Develop a function run-pworld which takes an initial World for big-bang, namely a pworld, and runs big-bang with draw-pworld, update-pgoal and move-player. The purpose of run-pworld is only to make sure that you have player movement working well in your game before you add zombies.
Exercise 9 Develop a data definition for a list of zombies. Again a zombie is a Posn. Develop a data definition and a struct called zworld which has two fields. One field stores a Pworld and the other field stores a list of zombies.
Exercise 10 Develop a function called draw-zworld which takes a Zworld and returns an Image. This function will be passed to big-bang as a to-draw argument. The goal is to add icons for zombies on the image produced by draw-pworld. Zombies may be represented by small red circles or something else of your choosing. Be sure to develop a helper function to process the ListOfZombies and of course use draw-pworld.
Exercise 11 Develop a function called move-zombies which takes a ListOfZombies and a Posn and returns a ListOfZombies. This is a helper function for a handler for on-tick. In the game, move-zombies will move all of the zombies towards the input Posn. Use approach.
Exercise 12 Develop a function called move-everyone which takes and returns a Zworld. This function is a handler for on-tick. In the game, move-everyone moves the player icon towards the goal position stored in the Pworld which is stored in the given Zworld; it also moves all of the zombies towards the player position. Use move-zombies and move-player.
Exercise 13 Develop a function called update-pgoal-2 which takes a Zworld, two Numbers and a MouseEvent and returns a Zworld. This function is a handler for on-mouse. In the game, it updates the goal of the player’s movement to that of the input x and y coordinates. Use update-pgoal.
Exercise 14 Develop a constant called ten-zombies which is a randomly generated ListOfZombies of length 10. Use random.
Exercise 15 Develop a function called the-end which takes a Zworld and returns a Boolean. This function will be passed to big-bang as a stop-when argument. Its purpose is to signal when your big-bang program should end. Your game will run as long as and only as long as the-end returns #false. Use your constant close-enough and the function dist. This function will require a helper to process a ListOfZombies.
Exercise 16 Define a constant run which is defined to be big-bang applied to the functions developed above for your game. Define the ListOfZombies of the initial Zworld to be ten-zombies.
Extra fun Develop a function called generate-zombies which takes a natural number and returns a ListOfZombies. Each zombie created by generate-zombies is a random Posn. You will use random and check-random. (Both of these as well as the template for recursively defined functions on the natural numbers will be discussed in class.) Use generate-zombies in place of ten-zombies.