NOTE: Reminder that this assignment is not covered by the Emergency Grade Erasure policy.

Submitting

Regardless of how you choose to complete the assignment, you MUST submit the file you worked on to Canvas.

If you’re submitting remotely, your submitted file will be graded for completeness and correctness via an autograder. Make sure your functions are named correctly and that all of the built-in check-expects pass.

If you’re submitting in-class, make sure to check-in (there will be one question called CHECK-IN Q only available for the first 7 minutes of class) and check-out (there will be a three question survey called CHECKOUT SURVEY only available for the last 10 minutes of class) using PollEverywhere with your location verified.


Structure of a Game

This assignment will start here in the tutorial and then be finished in Exercise 9.

In this tutorial we’ll do a simple re-implementation of the classic arcade game Asteroids. Since this is your first imperative programming activity, we’ve already implemented most of the core functionality for animation. Today we’ll be working on the rendering aspect of the game and then you’ll be focusing on the mechanics and extensions of the game in the Exercise.

Most computer games consist of a set of objects that appear on screen. For each object on screen, there is a data object in memory that represents it, along with functions for redrawing it on the screen and for updating its position and status. The overarching structure of the game really just a loop that runs indefinitely: first calling the update function for every object and then calling the render function for every object. Once the rendering part is complete, the whole process repeats. We’ve already implemented the main game loop and drawing functionally for you so today you’ll be focusing on the code for the update functions.

This will be similar in approach to our work on Snake. Except, now we have imperatives so we have the ability to update structs rather than having to make whole new ones and methods that will simplify our game design.


Intro: Asteroids

Asteroids was an incredibly popular multi-directional arcade game written by Lyle Rains and Ed Long and released by Atari in 1979. It’s now considered one of the most influential video games of all time, both for its effect on players as well as game developers. The objective of the game is to shoot asteroids moving about in the game space while avoiding collisions with your ship and sometimes fending off attacks from enemy spaceships. If you’ve never played the game, there’s a fully functional version available here on the web.

The game was developed for an 8-bit microprocessor (the MOS Technology 6502) which ran at a clock speed of between 1 and 2 MHz. What this technically means is outside the scope of this class…but for a very rough comparison of computing power, odds are your current laptop is running at a clock speed of around 3GHz (or 3000MHz) (and probably has upwards of 4 cores running at this speed). That means your computer is roughly twelve-thousand times more powerful than the original computer than ran Asteroids. Really this is mostly just a fun fact and doesn’t affect this assignment, but I like looking back at computer history to see how far we’ve come.

Today, you’ll be finishing up an implementation of a simplified version of asteroids that isn’t feature complete (i.e. it doesn’t have all of the features of the original game) but does emulate its core functionality.

Screenshot of Real Asteroids
Open Screenshot in a New Window
|real asteroids
Screenshot of Our Simplified Asteroids
Open Screenshot in a New Window
simple asteroids

Our simplified game (in the tutorial) will consist of three kinds of on-screen objects:

And in the Exercise you’ll add:

In our game, the-player will pilot the ship using the arrow keys on the keyboard. The left and right keys turn the player’s ship while the up arrow accelerates it in the direction it’s pointing. The space bar fires a missile. The s key fires a homing-missile (you’ll add this in the Exercise). The players’s goal is just to survive as long as possible and not get hit by an asteroid.


Getting Started

Tutorial 10 Starter Files

Start by opening the tutorial_10.rkt file. You can start the game by calling the asteroids function (no inputs) in the REPL. You can stop it by closing the game’s window. At the moment, much of the player’s control doesn’t work and the visuals leave something to be desired.

Reminder! If you decide to become any sort of programmer or software engineer later in life, this sort of task will become very familiar. You're given a large existing code base and asked to add new functionality. Before you can add the new functionality, you need to spend some time familiarizing yourself with the existing code. This is why it's so important to write readable and well-commented code because at some point...the roles will be reversed! Make sure to watch the pre-recorded lecture videos as they'll give you insight into how the game engine for Asteroids actually works.

Part 0A. Understanding Asteroids Lib

If you’re unsure of what to do at any given point, just come back to this section. This section contains a description of most of the code in the tutorial_10.rkt and asteroids_lib.rkt file that you will need to complete the assignment.

The code of Asteroids is structured around the primary game state, the collection of all game-objects, and an event loop that continuously accepts keyboard events and updates the game state (this mostly happens in the asteroids_lib.rkt library).

game engine chart

When the game is running, the event loop calls an update! method on each object in the game state to update their positions and velocities; then uses the render method to repaint the screen at a rate of 30FPS (frames per second). The event loop also dispatches the keyboard events to the on-key-press function. The on-key-press function then adjusts the player’s orientation and/or creates new missile objects accordingly

You’ll be working on defining what the game objects are (properties/fields) and what they do (methods) in the tutorial_10.rkt file. After you add a feature, make sure to both write some check-expects but also actually run the game by calling the asteroids function in the REPL.


The game-object Struct

Each of the on-screen objects is represented by a data object with a set of built-in properties used by the animation system. We will use game-object as an abstract type, in other words, it’s just a template for other structs and we won’t be creating any game-objects directly.

The starter code provides three kinds of structs: the player, the asteroid and the missile. All three kinds of structs inherit the game-object struct:

;; A game-object is a (make-game-object posn posn number number)
(define-struct game-object
  (position velocity orientation rotational-velocity)
  #:methods
  ...)

Every instance of game-objects comes with (at least) four pieces of data that is available through the usual accessor functions:

Reminder! Since we are working with imperative language features, all four fields come with mutators, e.g. set-game-object-position!, set-game-object-velocity!, etc., that lets you mutate the content of the fields in any game-object instance.

Additionally, a game-object has four methods defining its behaviors:


The Game State

The game state consists of three variables:

Note that your player won’t respawn when it dies. It’ll just “explode” like in the below screenshot. To play again, just close the window and rerun (asteroids).

Screenshot of Endgame
Open Screenshot in a New Window
dead player

Part 0B. Programming movement in Asteroids

The game code uses the posn (short for position) struct to represent positions in space along with velocities of travel. The posn struct is built in but behaves as if you had defined it as we normally do with custom structs:

; a posn is a (make-posn number number)
(define-struct posn (x y))

In other words, you DON’T need the above code and instead the posn object already contains two fields, x and y, accessed with the posn-x and posn-y functions, respectively, which store the x and y coordinates of the point, respectively, and you can create a new posn object by saying (make-posn x y).

Adding a velocity to a point

If you want to shift a position by a specified velocity, you can use the posn-+ function.

; posn-+: posn posn -> posn
(posn-+ a b)

This returns a new posn whose x coordinate is the sum of the x coordinates of the inputs and whose y coordinate is the sum of the y coordinates of the inputs. So if you have a location on the screen, represented by a posn, and an amount you want to shift it by, also represented by a posn, you can find the shifted position by adding them with posn-+. For example, if we shift the position (make-posn 1 2) right 3 units and up 4 units, we get the position (make-posn 4 6):

(check-expect (posn-+ (make-posn 1 2)
                      (make-posn 3 4))
              (make-posn 4 6))

Scaling a velocity by multiplying it

You can scale a velocity, represented by a posn, to be “faster” or “slower” by multiplying it by a number. If you multiply it by 2, it moves twice as fast in the same direction. If you multiply it by a half, it moves half as fast, again, in the same direction.

; posn-*: number posn -> posn
(posn-* num posn)

Multiplies num by posn. The x and y coordinates of the original posn are each multiplied by num. For example:

(check-expect (posn-* 2
                      (make-posn 3 4))
              (make-posn 6 8))

Sensing the Player’s Input

You won’t need to worry about this as asteroids_lib.rkt takes care of all of this but it works the exact same way as Snake. When the user hits a key, the game “hears” the key and calls the appropriate function.


Part 1. The player Struct

We’ll start by completing this part in the tutorial today. The further you get in this assignment today, the less work you have to do in Exercise 9. If you do not finish today, you’re still responsible for the stuff in this assignment as Exercise 9 will assume you’ve already finished this part.

Let’s start by implementing the player struct. Right now, the player does not respond to the user’s command because it inherits the default update! method from game-object which does nothing. Complete the update!, render and radius methods for the player:

Hint on render
You may pick any design you like as the appearance of the player object in the render method and any size appropriate in the radius method. The size usually matches the image of the player object. For more information about how the Asteroids library uses the update! and render method, see the game-object struct (Part 0A). There are a TON of built-in image functions that you can choose from or you can feel free to make your own function to create an image: 2htdp/image library

If you are using an image with a "clear forward direction" (like a triangle) draw it so that the "forward point" is pointing directly to the right as the game engine will initialize your player as if it's facing directly to the right.
Hint on radius
If you're using a basic image for your player, then this function will likely be super simple. Say you have a triangle with a base width of 10. That means a bounding circle around that triangle would need to have radius 5. So your function would literally be (define (radius p) 5) since the player is always the same size. If you want to be more like a real game developer, see if there's any helpful functions in the 2htdp/image library that you can use to actually calculate the size of a given image!
More details on update!
We'll need a little basic physics knowledge to get this working. Acceleration is just a fancy word for "increasing velocity." That means, in order to accelerate the player, we need to increase (mutate) its velocity.

Step 1. We need to review Part 0A to find some property of the game state that indicates whether or not the engine is currently firing.

Step 2. We need to figure out how to mutate the velocity of the player (Part 0A).

Step 3. We need to write an expression to tell the player to accelerate (Part 0B).

In other words, let v denote the original velocity of the player and a denote the acceleration. Then the new velocity of the player should be v+a. If p is the player object, then the code will look like:
(set-game-object-velocity! p (posn-+ (game-object-velocity p)
                                     acceleration-expr))
  • The velocity v is computed by the function call (game-object-velocity p).
  • The vector addition, v+a, is calculated using the posn-+ function.
  • acceleration-expr is an expression computing the acceleration, a, pointing in the forward direction.
To simplify your job, the asteroids_lib.rkt library provides the forward-direction function that takes a game-object and returns a unit vector (a posn struct with a magnitude of 1) pointing in the forward direction of the given object by inspecting its orientation field.

; forward-direction: game-object -> posn
; Returns a unit vector in the forward direction of the game object.
A magnitude of 1 is either too fast or too slow compared to real asteroids, so you'll need to write an expression to scale that vector. You can decide on the magnitude of the acceleration (hint: remember, you won't be able to use just the * symbol as that multiplies two numbers).

Finally, remember you only need to change the acceleration when the up key is being held down. You do not need to decelerate the player. You can "slow down" in the game by turning around and accelerating in the opposite direction.

Part 2. The asteroid Struct

In the next step, let’s enrich the game with asteroids of different sizes and different colors. To achieve this goal, we define the asteroid struct with two additional fields storing the radius and the color information:

(define-struct (asteroid game-object) (radius color)
  #:methods
  ...)

Your task is to implement the radius method and the render method for asteroids. The Asteroids library takes on the job of creating asteroids with varying radii and colors and will also take care of updating the asteroids state (i.e. you don’t need an update! method for asteroids).

Note: The asteroid struct purposefully has both a radius attribute and a radius method. There isn’t a name conflict here, because in order to access the property radius of an asteroid we use the accessor asteroid-radius which is distinct from the radius method which we’d call by saying: (radius an-asteroid).


Part 3. how-many-of Function

In the next part (and in the exercise), you’ll be asked to limit the number of missiles (and later homing-missiles) on the screen at any given time. In order to do that, we need some way of counting how many of some object there are already in the game. To do that, write a function with the following signature and purpose:

; how-many-of : (game-object -> boolean) -> number
; calculates how many of some game-object are currently in the game

Notice this is a higher-order function. The inputs to this function will always be on the of the automatically defined predicates from define-struct. Remember, you can access the list of all game-objects through the all-game-objects list.


Part 4. The missile Struct

Finally, finish the base game by implementing the missile object and limiting a missile’s lifetime on the screen. A missile self-destructs when it reaches the end of its lifetime.

(define-struct (missile game-object) (lifetime)
  #:methods
  ...)

Note that the game engine will take care of actually moving the missile across the screen (which it will NOT do for the homing-missiles in Exercise 10).


Testing your Work Before Submitting

There are some built-in tests that we’ve provided, but also consider other ways you might test your completed work.

Make sure to actually try playing the game! Does it work as expected? If everything was defined correctly than you should have a working simple asteroids complete with moving asteroids, a possibly-moving/rotating/missile-firing/possibly-losing player, and destroyable asteroids.