CSE 102 – Tutorial 10 – Pac-Man
Sections tagged with a green marginal line are mandatory.
Sections tagged with an orange marginal line are either more advanced or supplementary, and are therefore considered optional. They will help you deepen your understanding of the material in the course. You may skip these sections on a first pass through the tutorial, and come back to them after finishing the mandatory exercises.
After you are finished with the tutorial, please upload your submission to Moodle by the indicated deadline.
Discussion of the tutorials with friends and classmates is allowed, with acknowledgment, but please read the Moodle page on “Collaboration, plagiarism, and AI”.
There are no automated tests for this tutorial. Simply check that your game is playable and has the expected behavior.
Updated installation instructions
To get the most out of this tutorial you will need to have the pygame library installed, as well as an IDE with support for type hints. Assuming you are using Spyder under Anaconda, the safest way of updating your environment is probably to create a new virtual environment with everything installed, by executing the following commands:
conda create -n CSE102_env
conda activate CSE102_env
conda install spyder
conda install -c conda-forge pylsp-mypy pyls-spyder
pip install pygame
If you are on a Windows machine, you may need to replace the first
line by conda create -n CSE102_env python=3.11
.
Alternatively, if you are using another IDE with integrated support
for type hints, such as vscode or pycharm, then you only need to install
the pygame
library.
Pygame
To test that you’ve successfully installed pygame, run the following from the interpreter:
1]: import pygame In [
If that runs without errors, you have the library correctly installed.
You can read the pygame online documentation to find out more about the library.
Type hints
Once you’ve installed the necessary packages, then you can simply restart Spyder and it will indicate type errors in line. For example, if you copy the type-incorrect definition below into a file
def foo(oranges : int, apples : str) -> int:
return oranges + apples
and save it, then Spyder should display something like the following warning:
If you run into trouble, please ask for help directly from a teacher/student, or post to the #techsupport channel on Slack.
Pac-Man
In this tutorial you are given an implementation of some aspects of the classic Pac-Man game. You are also given a Pac-Man entity that currently does not do anything but sit in its starting location.
Your task will be to make the Pac-Man entity react to your keyboard inputs and thereby play the game of Pac-Man.
You are given the following files.
level.py
: an implementation of the level in which Pac-Man and the ghosts operateghost.py
: an implementation of ghosts. Each ghost randomly picks a direction to go whenever it encounters a fork in its path.pacman.py
: an incomplete implementation of the Pac-Man entity. Most of your work will be focused here.game.py
: the implementation of the Pac-Man game and the location of the main game loop.
Download all of them as a .zip file
Make a new project with these files, and then run the
game.py
file. You should see a Pac-Man window pop up. Hit
the return key to start the game.
The template contains type annotations, a.k.a. type hints, which are intended both to help you understand the existing code and to debug the additional code that you write. Again, to get the most out of these type annotations you should use an IDE that can understand them and indicate type errors interactively.
We expect you to modify the files given above and to upload them using the form below. You can resubmit your solution as many times as you like.
Making Pac-Man move
The first task is to get the Pac-Man entity to move at all. We’re not going to worry about user input right yet. What we are going to do is to have the Pac-Man entity move around randomly.
The level is represented in the level.Level
class. Each
cell position pos : tuple[int,int]
is a tuple of a row and
a column coordinate, numbering from (0, 0) for the top left cell. The
level object has the __getitem__()
and
__setitem__()
special functions defined so that to get the
contents of the cell at position pos
from the level object
l
, you would say l[pos]
, and similarly for
setting the contents of the cell.
The contents of each cell is a value of an enumeration type
Cell
, which includes the following values:
Cell.EMPTY
(empty cell), Cell.WALL
(wall),
Cell.PILL
(a cell that has a pill – more on that below),
Cell.POWERPILL
(a cell with a powerpill – explained in the
optional section below), or Cell.PIT
(the pit where ghosts
spawn).
The Pac-Man entity (in the class pacman.Pacman
) has a
pos
data attribute that is its current
(row, col)
coordinate in the level. From any such
coordinate, you can get the accessible neighbors by means of the
.neighbors()
method of level.Level
. The
current level is stored in the data attribute .level
in the
pacman.Pacman
class.
On every update triggered by the game loop—i.e., in the
.update()
method inpacman.Pacman
—move the Pac-Man entity to a random neighbor of its current position. To find the neighbors of the entity’s current position, call.neighbors()
to get the adjoining cells that the Pac-Man can move to from its current cell. Pick one such neighbor at random and then update the.pos
data attribute of the entity.When the Pac-Man entity enters a cell that has a pill (i.e., the cell contains the value
level.Cell.PILL
), change the cell to be empty instead. Also increase the score of the Pac-Man by 1.
If you did this right, you’ll see the Pac-Man zoom around in the level at ludicrous speed, probably running into a ghost eventually.
Slowing the Pac-Man down
The ghosts currently move at the rate of 5 cells per second, which is
set in the class ghost.Ghost
. The Pac-Man should also move
at this very same speed.
Modify your Pac-Man class so that it only moves from one cell to the next once enough time has elapsed. This can be done using the
millis
argument to.update()
, which contains the number of milliseconds that has elapsed since the previous call to.update()
. Change the class so that the Pac-Man decides where to go and then gradually goes there at its correct speed.For now just make the Pac-Man move at the same speed as the Ghosts. Don’t worry about making the Pac-Man’s movements appear smooth – i.e., it’s OK if it jumps from one cell to the next about 5 times every second.
- Update the
.render()
method ofpacman.Pacman
so that it draws the Pac-Man smoothly between two cells while it is in the process of moving between those cells. You may find it useful to see how theghost.Ghost
class is implemented.
Responding to user-input
Now for the main challenge in this tutorial: getting the Pac-Man to move in response to user inputs.
The
.process_event()
method inpacman.Pacman
currently does nothing. You will now make it detect and respond to keyboard events, specifically the keypress event (event.type == pg.KEYDOWN
) or a key release event (event.type == pg.KEYUP
). In either case, the key that was pressed will be in theevent.key
attribute. You need to watch for the keyspg.K_LEFT
,pg.K_RIGHT
,pg.K_UP
, andpg.K_DOWN
. Alternatively, if you like to play using WASD, you may respond to the keyspg.K_w
,pg.K_a
,pg.K_s
, andpg.K_d
. Based on which event you received, set a.direction
attribute for the Pac-Man entity. If the user presses any other key, just ignore it.Modify the
.update()
method so that the Pac-Man moves in the direction indicated by its current.direction
attribute. Make sure that the entity does not enter forbidden areas such as the walls or the pit. You can check if a given cell is enterable using the.can_enter()
method oflevel.Level
. If moving the Python along its user-indicated direction would cause it to enter a forbidden cell, you should prevent the Pac-Man from moving.Figure out what should happen if a user presses multiple keys at once, such as the left and right keys at the same time.
If you have gotten this far, you will have a playable version of Pac-Man.
Powerpills
The Pac-Man can turn the table on its ghost pursuers whenever it swallows a powerpill. The level by default doesn’t have any powerpills.
Modify the
level.Level
class to take a list of powerpill locations in its initializer. Change thelevel.level_1
variable to create some initial powerpills, say one near each of the four corners of the map.Whenever the Pac-Man swallows a powerpill, the following happens:
- It gets a flat 10 points added to its score
- Its speed doubles
- Whenever it runs into a ghost, the ghost gets reset
(
.reset()
), which sends it back to the pit. - Each ghost the Pac-Man resets in this way adds 50 to the score
- The powerpill effect lasts for a total of 15 seconds.
- When the powerpill effect is active, you may want to modify the ghost’s behavior to make it run away from the Pac-Man. Modify its behavior on forks by having it select the direction that will take it farthest away from the Pac-Man entity.
Polishing the look
While the game so far is playable, it is not very fun to look at. Let’s try to fix some aspects of the presentation.
Give the Pac-Man some eyes, like the ghosts.
Add a death animation for the Pac-Man. You may find that you need to add a new game state that is like
Game.GAME_OVER
but is used just to play the death animationChange the rendering of ghosts while the Pac-Man is powerpilled. This can be achieved by calling the
.blanch()
method of theGhost
class.Make the Pac-Man’s mouth point in the direction that it is moving.
Have the Pac-Man’s mouth open and close in a loop. Both this and the previous task will require you to figure out how the Pac-Man is being drawn in its
.render()
method, and then to modify it.
Differentiating the ghosts
Currently all the ghosts do the same random thing. In the original Pac-Man game, the ghosts had subtly different behaviors.
Blinky, the red ghost, always chases the Pac-Man. In other words, whenever it comes to a fork, it takes the fork that gets it closest to the current location of the player.
Pinky, the pink ghost, tries to get in front of the Pac-Man. At all choice points, it extrapolates the current direction of the Pac-Man and tries to get to the fork that is directly ahead of the Pac-Man.
Clyde, the orange ghost, will alternately chase the Pac-Man (like Blinky) and try to go back to the entrance of the pit.
Inky, the blue ghost, randomly picks a behavior of the other three ghosts and executes it for a random amount of time before switching to a different one of the behaviors.
- Create classes that inherit from
ghost.Ghost
and override the.update()
method to implement the behaviors above.