Project 2: TBD Game
Overview
You will be creating a maze game. The user will navigate a pirate around the maze using key presses and mouse clicks. The maze will contain some obstacles, as well as special items (gadgets), such as meat to completely restore the pirate’s strength, lemons to give them a small strength boost, wet floor signs to deplete their strength, or explosives to allow the pirate to remove an obstacle. The strength is shown as the yellow bar on the right. The goal: help the pirate safely make it back to the end of the maze.
Specifically, you will make a) the code to “render” (draw) the maze, its items, and the pirate, and b) the logic required to allow the (human) player to use the mouse and keyboard to navigate the pirate around the maze.
To configure the maze, we will use tables in a new way: you’ll use a Google Sheet to set up where the walls should be, as well as additional Google Sheets to give the coordinates where gadgets should initially be. Your program will read in these sheets, using them to create the actual background image and initial game state. The game play itself will be implemented with a reactor (as we did in lab 2 and have been doing in lecture).
Learning Goals
This project applies what we’ve been learning about lists, recursion, and datatypes. It exercises your ability to detect what information does and doesn’t change over time in a program. It has the side benefit of showing you how animations can be built, and of showing how tables can be useful for more than just storing data. Here are the specific skills you will practice.
- Creating and using your own datatypes.
- Using recursion to process various types of lists.
- Breaking down complex goals into discrete, solvable tasks.
- Using tables for code configuration rather than as datasets.
What class material does this build on?
This project builds on the reactors from Lab 2 and recursion skills.
Game Details
Maze
The entire maze should be surrounded by one layer of walls (except at the end location), so you don’t have to deal with the pirate moving outside of the maze. This also makes it clear to the human player where they need to navigate to in order to complete the game.
The wall and floor tiles are 30x30 pixels (you will need this information to convert coordinates in the grid to coordinates for place-image in Pyret). The pirate, gadgets, and obstacles are 24x24 pixels.
The Player and Character
The current position of the player in the maze is represented with an image of the pirate. The human player moves the pirate using the W
(up), A
(left), S
(down), and D
(right) keys. The pirate cannot walk through obstacles or walls of the maze, but can move through empty space.
Gadgets and Obstacles
There are two kinds of items placed in the demo maze: gadgets and obstacles. Gadgets are things a player can pick up. Obstacles block the pirate’s movement until they are removed using explosives.
There can be an arbitrary number of these items placed on the maze. Items are picked up when the player moves into their cells (meaning they disappear subsequently); stamina-increasing gadgets are immediately consumed, and explosives are “held on to” until use. Multiple explosives can be collected; only one is used each time the player removes an obstacle.
You will implement either stamina gadgets or explosives/obstacles into the game. You only need to implement one of these to get full credit. You may do both if you want, but you will not get extra credit. Either option is challenging in its own way; we do not think either is significantly easier than the other.
Starting, Winning, and Losing
The pirate will start at a location of your choosing. The information of where they start should be hardcoded through named constants in your code.
The player wins the game upon reaching the exit location that you set. This information should also be hardcoded through constants.
The player loses the game if the pirate runs out of stamina. If you don’t implement stamina gadgets, the player doesn’t have to worry about losing.
Upon winning or losing, the game can simply stop. You may also choose to somehow inform the player that they have won or lost, but this is not required.
Phase 1: Basic Reactor
Task 0: Make a copy of the project starter file.
Task 1: Decide whether you want to implement gadgets (stamina) or obstacles.
Task 2: Watch the example video and figure out what information changes over time as the game is played. Just write this down in prose (no code).
Task 3: Organize this information into a Pyret data
block that captures the state of the game (call it GameState
). You will end up using a combination of lists and other data
blocks in your design. Include whichever of gadgets or explosives you’ve chosen (you can skip the other).
Hint: Figuring out the datatype for this is one of the more challenging parts of this assignment. Look at the things you identified for Task 2: you’ll end up making datatypes for objects in the animation (with their attributes), collections of similar objects, and datatypes that gather objects and collections into single “concepts” within the game. Expect to need some time here. If you aren’t sure what makes the most sense, come up with a couple of different ideas and review them with a TA.
Note: your data block should contain ONLY information that changes about your animation over time. An element that doesn’t change will go into the background in Task 5.
Task 4: In code, write a concrete example of GameState
data for an initial configuration of your game. Don’t worry about information that will eventually come from one of the Google Sheets – just make SOME example for now.
Task 5: Build the ‘BACKGROUND’ image for a maze (the part of the image that does NOT change over time). Don’t worry about including the gadgets or wormholes.
A maze design will be given as a list of lists of “x” and “o”, such as the following (“x” is a wall/blocked, “o” is open space):
small-maze =
[list: [list: "x", "x", "o", "x"],
[list: "x", "o", "o", "x"],
[list: "x", "o", "x", "x"],
[list: "x", "o", "x", "x"],
]
While later in the project you will read in such a list of lists from a Google Sheet, for a first pass just build a maze background image from a small example like small-maze
.
How can you do this? Notice that the list of lists is a form of grid; the maze image will also be a grid, just made from images (with a “wall” icon in place of “x” and a “floor” icon in place of “o”).
Task 6: Set up a simple reactor that moves the pirate across the background image when the human player presses a key, without worrying about the maze grid cells or walls (just get a basic reactor running). Reference Task 3 to set up your initial GameState configuration.
Task 7: Make sure you can load the configuration data from Google Sheets.
Task 8: Plan how to implement gadgets or portals.
If you are implementing gadgets: Consider what should happen to the GameState
when the pirate lands on a square with a specific gadget (pick whichever one you want to handle). If you are implementing obstacles: The reactor mouse-clicked
function takes in the current GameState value
, the x
and y
value of the position of the mouse event (in the reactor), and a String
that represents the name of the mouse event. Consider tasks needed to compute the new GameState
based on the current GameState
if the mouse has been clicked.
Phase 2: Implementation Details
Configuring the Maze (In Google Sheets)
The game will be populated based on your copy of one of the following spreadsheets (depending on whether you want to implement gadgets, obstacles, or both):
- Gadgets spreadsheet: link
- Obstacles spreadsheet: link
- Both items spreadsheet: link
Each spreadsheet has two sheets: maze-layout
and items
.
- The
maze-layout
sheet determines which parts of the maze are walls and which are blank and therefore where the player can walk. “x” corresponds to a wall, and “o” corresponds to a floor (tile). - The
items
sheet contains a list of items that will be placed on the maze. The x column is the distance from the left side of the screen, and the y column is the distance from the top of the screen. (These distances are in terms of the grid labels in the maze, not pixels for built-in Pyret image operations ) You will need to edit this sheet to put items in the appropriate places.
Note: You do not need to do any error checking for items being placed in invalid locations (like on walls or off the maze).
The maze we provide in the Google Sheet is 35 squares per “row” and 19 squares per “column” (19x35), but you can add or remove rows, columns, and items as you please and make the maze your own! If you change the number of columns in your maze, replace the call to load-maze in the starter code with a call to load-maze-n. The latter is the same as load-maze except it has a second parameter which is the number of columns to load.
Reactor Documentation
You have seen the reactor properties init
, to-draw
, and on-key
before. If you are implementing obstacles, you will also need to use the on-mouse
function, which works similarly to the on-key
function; check out the on-mouse
documentation for more.
You can refresh yourself on reactor properties here. You may also want to look at your code or the code files from recent lectures with reactor examples.
Implementing Gadgets
If you implement gadgets, the pirate needs to have stamina. Stamina is a measure of energy often used in video games. In this game, the pirate’s stamina depletes by moving. If the pirate runs out of stamina, it’s game over.
There are three things you can encounter in the maze: meat, lemons, and wet floor signs.
Finding meat replenishes the pirate’s stamina to its original value. Finding a lemons heals the pirate for some fixed amount of stamina. Stepping on a wet floor sign reduces the pirate’s stamina to some low, fixed amount. For example, if the pirate’s stamina is 20 and they move onto a wet floor sign, they may move down to 7 stamina. Then moving onto a popcorn may heal them to 7 + 8 = 15 stamina, and finding tickets will heal them to their original stamina (say, 30). Normal movement reduces stamina by 1 per cell.
There should be a visual indication of the pirate’s stamina, via the yellow bar on the right.
Implementing Obstacles
The Obstacles in this maze will be represented by the TODO image, and the Bombs will be represented by the TODO image. The pirate can carry one or more bombs, but starts with no bombs. When the pirateo has a bomb and the user clicks some obstacle on the screen, the pirate will clear the obstacle as long as the cell is within some reasonable range of the pirate’s current location. You can choose this range!
Each bomb can be used only once. Your game screen should have some visual indication of how many bombs the superhero has (simply a number using the Image library’s text function is fine).
To compute how far away the human player is trying to use the bomb, you can use the euclidiean distance formula for computing distance between two points. If the distance is larger than the threshold, nothing changes.
Approaching This: Work on One Feature at a Time
The key to tackling a larger assignment like this is to build the features up gradually. For example, get the game working on just basic functionality before adding more advanced features. What might that mean here?
- Implement a version with just the walls (no gadgets or obstacles), such that the player can move around the maze while respecting walls.
- Read in the items (gadgets, bombs, etc) from the Google Sheet, store them in your data structure(s), and make them show up when you draw the GameState.
- Get the items to disappear after a player visits their cells.
- Make the game handle the items (modifying stamina or bombs).
These phases correspond to the grading levels for the project (see the Grading section below). You can pass the project even if you don’t get beyond phase 1.
We strongly recommend saving a copy of your file after you get each stage working, just in case you need to get back to your last stable version (practicing programmers do this all the time).