We are implementing a maze-escape game featuring several different lists and a maze that is read from a file.
The player tries to reach an exit and avoid colliding with any NPCs. There can be more than one NPC, more than one exit, and even more than one player (see below).
The player cannot go through walls nor go past the edges of the screen (in the supplied maze, the edges all have walls or exits). The player moves in the cardinal directions using the arrow keys. The player wins if they reach an exit. The player loses if they collide with an NPC (regardless of which instigated contact).
The player may have one or more clones. The idea is that the
human player can control one of the clones at any time; clicking on a
clone switches control to that clone. The player controls the “active
clone” which should be a similar yet distinguished color from the clone
color. (In my example, clones are blue
; the active player
is cyan
.)
NPCs move randomly, including diagonally. They cannot move past the edges of the screen. The cannot move through exits or through passive (inactive) clones (players that are not currently being controlled). If they move into the active player, the player loses. More interestingly, NPCs can move through walls - ghost-like.
Walls are fixed and do not change during game play. The row-columns
dimensions of the graphics window, locations of walls and exits, and
initial locations of players and NPCs are determined by a text file (see
the supplied map.txt
) - and see below.
Work on your own. (But reach out to me early and often with questions!)
Submit your work by uploading a zip file of your folder
of work via MySLC.
If you are having difficulty creating a zip file, then you can upload
relevant files which are in this case: hw3.py
and one or
more map files that you create.
Use only the Python expressions and statements I have used thus far in class or in examples I have sent or included with this assignment. If you are not sure whether to use a feature, just ask.
Download the starter archive.
(Right-click on the link and choose a location to save the file on your
computer.) Expand the archive. It includes
blocking_graphics.py
.
Write all your code in the file hw3.py
. But
do not start writing until you have a sense of what is being asked.
Specifically, read this entire document before writing any
code.
You can choose to implement your game as turn based - where the games waits for the player to make an action (either moving the active player one square or selecting a clone to become the active player) or interactive (meaning the NPCs move, at a speed for you to determine, regardless of whether the player takes action).
Minimal requirements: creating your own map with at least one player, two exits, and three NPCs; correct player movement; mostly correct NPC movement; winning if reaching an exit; and losing if player collides with an NPC.
Recommended: having NPCs move through walls like ghosts; having more than one player (i.e., clones) and making that feature work by handling mouse clicks accordingly; verifying quit - meaning that the player cannot just accidentally end the game by tapping ‘q’.
You can and should choose colors. The size of the screen (rows
and columns) should be determined from the map file you create. Your map
file should be notably different than mine. Maps use #
to
represent walls, P
for players, N
for NPCs,
and X
for exits. The number of rows of the graphics window
should correspond to the number of lines of your map file; the number of
columns should correspond to the length of the longest line of
the text file.
You should try to design your map - and game play - so that it is neither too easy nor too hard.
You should write a concise, but thoughtful description of the game’s “plot”.
Replace ... your name ...
with your name in
the comment near the top of the file. (Remove the ...
,
too.)
Lines that contain # ...
indicate places where you
should expect to modify or add one or more lines of code.
Test your work as you go!
Be sparing with your own comments. Proofread them before submitting.
Take care to format your code to be readable; follow the model of my examples.
Be consistent with formatting, spacing, indentation; be sensible with variable naming.
Before submitting: replace ... description ...
comment near the top of the file with a concise, but proofread and
thoughtful comment describing the “plot” of your game.
Before submitting: replace ... game title ...
comment near the top of the file with a better title than “maze
escape”.
Before submitting: replace ... status ...
comment
near the top of the file with a brief comment as to how much of the
assignment you completed. (For example, “I completed the entire game and
I am confident it works correctly.” or “I was only able to get the
player to move and to allow winning by reaching an exit.” )
You can watch a demo to get a better sense of how the game should look and play.
Hints available upon request!
After each step below, try your work! Test your changes frequently!
The first phase is to get the player to move (the other directions)
and be able to exit. At this point, assume we have only one player - and
its location is stored in the 0th index of the g_players
variable (g_players
is a list of points).
Examine handle_player_events
and
move_player
- modify both so that the other arrow keys can
be used to move the player. The idea is that move_player
is
called with two numbers - one representing how the player’s x-coordinate
should change; the other for the y-coordinate.
Winning: modify game_loop
so that if the
player’s location is in
the list of exits then the player
wins. Call the supplied game_over
function and use
break
to leave the loop.
The next phase of implementation is to get the map to (mostly) display properly: so, in addition to exits, walls and NPCs should be read. Until you get past this phase; use the supplied map file.
Modify parse_map
so that every time an npc symbol
(N
) is encountered, an x-y coordinate pair is appended to
the list of NPCs (g_npc
) - follow the example use for
exits. Modify draw_map
to show all the NPCs - again, follow
the example used to display the exits.
Losing: Modify game_loop
so that if the
player’s location is in
the list of NPCs then the player
loses. (Follow model for winning.)
Modify parse_map
so that every time a wall symbol
(#
) is encountered, the g_walls
list of lists
is modified so that value at row y, column x is set to be
true
- representing the presence of a wall at that
location. Modify draw_map
to use nested loops to show all
walls.
Modify move_player
so that the player cannot move
through walls. (With the supplied map and the artifical starting point,
the player should be unable to move until the next step.)
Modify initialize
so that g_players
starts as an empty list (rather than at [(0, 0)]
).
Modify parse_map
so that every time a player symbol
(P
) is encountered, an x-y coordinate pair is appended to
the list of players (g_players
) - follow the example use
for exits and npcs. Modify draw_map
to show all the players
(clones).
At this point the game should be somewhat playable - the full map gets displayed; you can win and lose (by running into the currently immobile NPCs); you cannot move through walls.
Copy the file map.txt
to
original-map.txt
. Edit the contents of map.txt
to be significantly different than the original. (Later, you may wish to
adjust your newly created map to make the game more or less
challenging.)
Modify move_npcs
so that each time the function is
called each NPC moves. A skeleton of a loop for achieving that is
supplied; you need to modify the loop’s body so that each NPC actually
moves. You can use a model similar to previous assignments and exercises
- randomly pick values (-1, 0 or 1) to change the x and y coordinates
for each NPC. You can use code similar to what is in move_player to
prevent NPCs from moving past edges or from moving through exits or
moving through clones (players). For now, like the player, have NPCs be
blocked by walls.
Modify move_npcs
and game_loop
so that
if an NPC moves into the active player (at this point that is still just
g_players[0]
) then the player loses. move_npcs needs to
allow the move to happen; game_loop
should cause the
game_over
to be called following previous parts of the
assignment.
At this point, assuming you have thoroughly tested your code, you will met the minimal requirements. But do not stop here!
Modify move_npcs
so that NPCs can move like ghosts -
through walls. (They should still be prevented from moving through
passive clones and exits.) The basic change is easy… but it can have the
unintended side effect of making it look like the wall has been eaten
(disappeared) by the NPC. To prevent that, check if the space that the
npc moves away from has a wall, if so, redraw the wall there rahter than
unpainting the npc.
Modify verify_quit
so that if the player presses
q
, the status bar is used to ask the player if they truly
want to quit; it should wait for a key, if that is a y
then
quitting can proceed (using the new raise SystemExit
feature); but if not the status bar should be cleared and the game
should resume (which will happen automatically by not having the program
exit).
Finally, let’s make the clones work.
Add a new global variable to initialize
called
g_active_player
. Intialize it to be None
.
Write a new function called select_player
which should get
a mouse click and if that point is in
the
g_players
list then it should change
g_active_player
to be the index (position) in that
list which corresponds to the point clicked on. Modify
initialize
so that before that function ends, it repeatedly
calls select_player
until g_active_player
is
no longer None
. You should use a status-bar message to tell
the player they need to click on a player square to begin and then clear
the status bar once they have. You need to modify all the places in your
code (move_player
, game_loop
,
move_npcs
) that previously used g_players[0]
so that the game works to move - and to allow winning and losing - based
on the activated player. Create separate colors to distinguish the
active player from the passive clones, adjust select_player
and move_player
accordingly.
Modify handle_player_events
so that if a key has not
been pressed but the mouse has been clicked, then
select_player
is called; at this point it does not matter
if the player clicks on something other than a palyer square, it can
just be ingored - it shoudl not loop until they do. Important
you no longer want to wait for a key in handle_player_events (think
about why); so you need to modify the function so that it processes keys
if one has been pressed and processes mouse clicks if
the mouse has been clicked. This may make the game no longer be turn
based - meaning suddently the NPCs may move blazingly fast. You
have two choice heres - either add code to
handle_player_events
so that it always waits for either a
key press or mouse click (thus restoring the game to its turn-based
form); or add code in other parts of the program that are similar to how
you handled interacivity for the previous assignment - so that NPCs
move, but neither too fast nor too slow. (Recommended: keep it turn
based.)
At this point you have built the full game! (Remove or adjust code that you have left in for testing purposes. Remove my comments that tell you where you need to work.)