Project Released: Wednesday, March 12, 2025
Design Checks: TBD (Monday, March 17, 2025 and Tuesday, March 18, 2025). Check your email to sign up for a design check with your design check TA. Remember to include your project partner in the Google Calendar invite when scheduling your design check!
Final Handin Deadline: Friday, April 4, 2025, at 11:59 PM
This is a two-week project, and you should have ample time to work on both the design check and project implementation. Do start early!
View a video demo of the game here!
In this project, you will be creating a maze game! The user will navigate a cow around the maze using key presses and/or mouse clicks. The cow will be able to move through the grass tiles, but is unable to move through water. In other words, the water and grass tiles serve as maze walls and paths, respectively. Along the grass, the cow can run into special items (widgets), such as milk that completely restore the cow's stamina, haystacks that give them a small stamina boost, dogs that deplete their stamina, or bells (representing portals) that allow the cow to teleport. If implementing widgets, the cow's stamina should be shown as the yellow bar on the right. The goal: Help the cow become the Moo-ster of Mazes by reaching the crown at the end of the maze!
This project has two broad components. You will write the code to:
To configure the maze, we will use tables in a new way: you'll use information from a spreadsheet to set up where the water and grass should be. Your program will read in these sheets and use them to create the actual background maze image and initial widget/portal placements. The gameplay itself will be implemented with a reactor (as we did in Lab 2 and have been doing in lecture).
The project offers different levels of completion, roughly corresponding to whether your goal is to earn an A, a B, or a passing grade on the project. The grading section further down goes into the details.
After all of the design checks are done, we will provide our solution to the design check phase, which you can use as you wish in producing your implementation. You are not required to use our solution if your own is functional!
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.
This project builds on reactors (from lab 2) and the recursion skills that you practiced in homework 5 and lab 6. We will refresh the concept of reactors and talk about designing our own Datatypes for them in lecture.
The entire maze should be surrounded by one layer of water (except at the end location), so you don't have to deal with the cow 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 water and grass tiles are 30x30 pixels (you will need this information to convert coordinates in the grid to coordinates in Pyret for place-image
). All other game objects, including the cow, widgets, and portals, are 24x24 pixels.
The position of the player in the maze is represented with an image of the cow. The human player moves the cow using the W
(up), A
(left), S
(down), and D
(right) keys. The cow cannot move through water, but can move through grass.
There are two kinds of items that can be placed in the maze: widgets and portals. Widgets are items a cow can collect that affect its stamina. Portals are tools a cow can use to teleport around the maze.
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). Widgets are immediately consumed, and portals are "held on to" until use. Multiple portals can be collected; only one is used each time the player teleports.
You will implement either widgets or portals 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.
The cow 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.
In the widgets version, the player loses the game if the cow runs out of stamina. If you implement portals, 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 and will not give any extra credit.
For this project, you will write a bit more code than you did for the project 1 design check. Our goal with the design check is for you to (1) design your data structures, (2) make sure you have a basic reactor infrastructure working, and (3) have viable plans for building out the reactor to the full version you are implementing.
For design check, you will hand in an initial game-s25.arr
file. Submit this to Gradescope at least 24 hours before your design check starts.
We recommend doing the questions in order as much as possible, as each question will help you think about the next.
Task 0: Make a copy of the project starter file.
Task 1: Decide whether you want to implement widgets or portals.
Task 2: Watch our demo video and figure out what information changes over time as the game is played. Just write this down in prose (no code) in a block comment in your Pyret file.
Task 3: In game-s25.arr
, 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 widgets or portals you've chosen.
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, and don't worry about whether this GameState
errors – 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 widgets or portals. Your BACKGROUND
image should be built using a function and maze-grid
– you should not be manually constructing the maze.
Note: The maze design will be imported as a list of lists, such as the following ("x" is a wall/water, "o" is open space/grass):
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 this task you are only required to build a maze background image from a small example. Use small-maze
for this.
How can you do this? Notice that the list of lists resembles a grid; the maze background will also be a grid, just made from image icons (with "water" icon in place of "x" and a "grass" icon in place of "o").
In lab, you practiced aggregating a list into a single image. For homework 1, we aggregated multiple images into one image. This question has you apply and combine these ideas; if you come to hours or post on Ed, talk through your proposals for doing this, rather than simply asking us how to get started.
The Pyret Image documentation will be helpful for this! You'll have to decide which functions should be used for aggregating images.
Task 6: Set up a simple reactor that moves the cow across the background image when the human player presses a key. At this point, it is fine if your cow walks on cells with water (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. Make a copy of a configuration spreadsheet. In Google Sheets, you can do this by clicking File > Make a Copy...
. Then:
ssid
in your game-s25.arr
filemaze-grid
and item-table
and make sure your file runs.Then look at maze-grid
and item-table
to get an idea of what they look like.
From here, we want you to plan key parts of the rest of the solution, so you can review them with your design-check TA.
Task 8: Write a plan for how you will add the widgets or portal images to the maze. You may include this in a block comment in your code file.
Task 9: In another comment, write out a plan or general sketch of how the key-pressed
function should work. Pick one of the four keys (W
, A
, S
, D
) and write out the tasks (3-5 one sentence bullet points) needed to compute the new GameState
based on the current GameState
. What are some edge cases that you'll need to consider? Write a single where
test case on a small GameState
for the key that you planned out.
Task 10: Plan how to implement widgets or portals.
GameState
when the cow lands on a square with a specific widget (pick whichever one you want to handle). Add a where
example that corresponds to your plan.mouse-clicked
function, tied to on-mouse
, takes in the current GameState
value, the x
and y
value of the position of the mouse event, and a String
that represents the name of the mouse event (in our case, button-down
, which essentially means a mouse click). Write out the tasks needed to compute the new GameState
based on the current GameState
if the mouse has been clicked. Write a corresponding simple where
example for mouse-clicked
.The game will be populated based on your copy of one of the following spreadsheets (depending on whether you want to implement widgets, portals, or both):
Each spreadsheet has two sheets: maze-layout
and items
.
maze-layout
sheet determines which parts of the maze are water and which are grass. This determines where the cow can go – the cow can go on grass but cannot go on water. "x" corresponds to a water tile, and "o" corresponds to a grass tile.items
sheet contains a list of items that will be placed on the maze. The Note 1: Positions are zero-indexed; the row "Milk", 1, 7, "milk.png"
corresponds to
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.
Do not adjust or rescale the sizes of the images that we provide. Parts of the support code will break if you do.
You have seen the reactor properties init
, to-draw
, and on-key
before. If you are implementing portals, 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 and lab 2 with reactor examples.
The reactor on-mouse
function takes in get-maze-index
is a function (provided in the source code) that takes in the coordinate that is input to on-mouse
and converts it to a game grid coordinate. For example, if the x
-coordinate clicked is 334, get-maze-index(334)
returns 11 (for the 12th column).
If you implement widgets, the cow needs to have stamina. Stamina is a measure of energy often used in video games. In this game, the cow's stamina depletes by moving. If the cow runs out of stamina, it's game over.
There are three things you can encounter in the maze: haystacks, milk, and dogs.
For example, if the cow's stamina is 20 and they move onto a dog, they may move down to 7 stamina. Then moving onto a haystack may heal them to 7 + 8 = 15 stamina, and finding milk 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 cow's stamina. In our demo, this is done via the yellow bar on the right.
The portals in this maze will be represented by the bell
Each bell can be used only once. Your game screen should have some visual indication of how many portals the cow has (simply a number using the Image library's text
function is fine).
To compute how far the human player is trying to move the cow, you can use the standard formula for computing distance between two points (see the spoiler below if you need a refresher on computing distance). In your game, if a player with a bell clicks on a cell, and the distance between the current and clicked cells is within the threshold you choose, the cow moves to the new cell and uses up a bell. If the distance is larger than the threshold, nothing changes (the cow stays in the same place and keeps the bell for another try).
The distance formula is based on the differences in x-coordinates and differences in y-coordinates between two points. Call the change in the
For example, if the player is at
Congratulations on finishing your maze game! We hope that you had fun implementing a game that helped the cow become the Moo-ster of Mazes by reaching the crown! Another important aspect of creating a game is ensuring that it is accessible. Read this short article that explains why game accessibility is important and skim this website for examples of game accessibility designs/guidelines.
Task 1: Choose two guidelines from the website (or you can come up with your own). For each guideline, have one partner describe a situation in which the guideline would be difficult to implement and/or would not apply, and another partner describe a situation in which the guideline would be implementable and applicable (This is an application question that applies to games other than the game you created for this project. Think about games you or your partner have played in the past or currently!). Please write 4-6 sentences for each guideline in a block comment in your Pyret file.
Task 2: Now let’s try to implement a guideline of game design in the game you just created! Please choose one of the following options to implement:
For options 2 and 3, you don't have to add any user interfaces (buttons etc). The use of arrow keys/difficulty can be configured before running the game by changing the values of use-arrows
or difficulty
. The code should use these names to determine which key controls/allowed radius it should be using.
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?
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).
Since there are many functions that will be written for this project, we will only be requiring minimal testing for all functions that are not critical functions. In an ideal world, you would thoroughly test all functions; however, we would rather see you test one or two functions really well (to show you understand the concept) and others partially, rather than only doing partial testing or no testing at all for all functions, especially critical functions.
Because of this, we will grade (1) minimal testing (>= 2 test cases) for non-critical functions that output things other than an Image, and (2) your thorough testing (try to hit as many tests of edge cases as possible!) for the function that moves the character (the one you use as on-key
, or on-mouse
). Test this function thoroughly, covering as many cases as you can. Regarding your other functions, write as many tests as you think you need to be comfortable with it working - but include at least 2. You do not need to write tests for functions outputting an Image.
Your design, testing, and clarity grades will not be based on your functionality grade. Functionality will be weighted more heavily in your final grade for this project in particular. Here is what we're looking at when grading functionality:
This project was designed to give you practice with organizing and manipulating structured data. Answer the following questions about these topics in a block comment at the bottom of game-s25.arr
.
Block comments are written with #|
and |#
:
#|
this is a block comment.
with multiple lines!
yay!!
|#
Hand in game-s25.arr
on Gradescope.