CSE 102 – Tutorial 12 – Pac-Man
Based on an original idea by K. Chaudhuri
This tutorialIS OPTIONALLYgraded. Your final grade for tutorials will be equal to the maximum between the average of the two first tutorials and the average for all the tutorials.
There are no automated tests for this tutorial. Simply check that your game is playable and has the expected behavior.
Collaborating in graded assignments without prior permission will be considered to be plagiarism, as the purpose is not just to learn but to be assessed. You are still allowed to have high-level discussions about the tutorial with your comrades, but such discussions should not go to the level of code. In this vein, donotpost solutions to any of the graded assignments to a public repository. Likewise, make sure not to post any solutions to the course Slack server on the #general or #group-X channels, although you are still welcome to raise questions there for clarification.
Sections tagged with a green marginal line are applications of the notions seen during the lecture. This part sums up to 20/20.
Sections tagged with an orange marginal line are more advanced. They will help you deepen your understanding of the material in the course.
Getting Pygame
In order to get started you first need to install thePygamelibrary. To do this, open a terminal and type everything after$
in the two lines below.
$ python3 -m pip install --user --upgrade pip $ python3 -m pip install --user pygame
If you use Anaconda/Spyder, you can also try:
$ conda install -c cogsci pygame
Also, if using Anaconda under Windows, do run the commands from the “Anaconda Prompt” (you can search for it in the start menu).
To verify that you have correctly installed it, run the following in a Python interpreter such as the one inside Spyder
In [1]: import pygame
If that runs without errors, you are good to go.
Pygame online documentation
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 to 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 operate
ghost.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 Spyder project with these files, and then run thegame.py
file. You should see a Pac-Man window pop up. Hit the return key to start the game.
The center of the level is called thepit. It is where the ghosts spawn. Currently ghosts that are in the pit do not move. When a “pit timeout” expires for each ghost, it leaves the pit from the top and then starts to move about in the level.
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.
ChooseSubmitReuse files from previous submissionsExpected files: game.py, ghost.py, level.py, pacman.py
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 thelevel.Level
class. It contains an important data attribute.cell
that is a two-dimensional list ofcells. To access the cell at rowr
and columnc
, use.cell[r][c]
.
The contents of each cell is an integer that will be one of the following values:level.Level.EMPTY
(empty cell),level.Level.WALL
(wall),level.Level.PILL
(a cell that has a pill – more on that below),level.Level.POWERPILL
(a cell with a powerpill – explained in the optional section below), orlevel.Level.PIT
(the pit where ghosts spawn).
The Pac-Man entity (in the classpacman.Pacman
) has apos
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 oflevel.Level
. The current level is stored in the data attribute.level
in thepacman.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 valuelevel.Level.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 classghost.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 themillis
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 thelevel.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 likeGame.GAME_OVER
but is used just to play the death animation
Change the rendering of ghosts while the Pac-Man is powerpilled. They should lose their color and become gray with fear. The gray color is obtainable in Pygame aspg.Color('gray')
.
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 three behaviors above.