All course materials and instructions are available in Canvas this year.
This project is going to be a little different than projects we have done in the past. You'll get some starter code for a terminal-based "roguelike" game, some ideas about features you could add to it, and student code from last year. Your job will be to:
This assignment doesn’t have quite as many rules as we’ve had in previous projects - the goal is for you have fun adding features to your game!
Do this project on repl.it by looking in our Unit 7 - Dictionaries repl.it classroom in the Projects section (right under the Assignments section). You should see a project called "Roguelike for real". The first time I uploaded it, there were errors (oops! fixed now).
In the old days, there was a game called Rogue. It looked like this:
The player’s character is the little yellow smiley face. Wikipedia says: In Rogue, players control a character as they explore several levels of a dungeon as they seek the Amulet of Yendor located in the dungeon’s lowest level. The player-character must fend off an array of monsters that roam the dungeons. Along the way, they can collect treasures that can help them offensively or defensively, such as weapons, armor, potions, scrolls, and other magical items. Rogue is turn-based taking place on a square grid represented in ASCII.
A lot of the ideas originally found in Rogue have since spread to tons of other video games, and we call those games roguelikes. Here’s a very complex one called Brogue:
In this project, you’ll be building a roguelike of your own.
These examples were made by students (with the exception of the last one, which I made). Play them and check out the code. Which features do you like the best?
The starter code comes with three files: level.txt, roguelib.py, and main.py.
A simple text file that is a map of the level!
If you want the game’s level to look different, you can edit level.txt
. To have more levels/maps, you might rename it level1.txt
and add more files named level2.txt
, start.txt
, boss_level.txt
, etc.
For the starter code, a '#'
is a wall, a ' '
is an empty space, and a '@'
is the player’s starting position. You’re the @
sign, you can move around, you can’t go through walls, and you can quit the game with 'q'
when you get bored.
It looks like this:
#######################
# #
# #
# #
# #
# ###############
# #
# #
# #
# #
# #
# #
############################# ######
# @ #
# #
# #
# #
# #
###############
Please enter a command and then press Enter. Available commands: [u]p - move up [r]ight - move right [d]own - move down [l]eft - move left [q]uit - quit the game
>
Make sure your map is a rectangle, even if you have to put spaces at the end of a line.
A Python file that contains some useful functions that can be combined together in order to make a simple roguelike game.
Go ahead and read through this file - you don’t have fully read and understand every single line of code, but it would be a good idea to look through the file and see what functions it defines and what their docstrings say the functions do.
You’ll have to make changes to some of these functions when you add new features to the game!
A small Python file that combines the functions from roguelib.py
together into a simple game loop.
Read the game loop and convince yourself that you understand how it works. Refer back to the code in roguelib.py
whenever main.py
uses a function that don’t fully understand.
Feel free to add more code to this file. You can also create more files to hold parts of your code--it’s a useful thing to do as your project gets bigger.
As a general rule, it’s hard to read and understand a big complicated file with 1000 lines of code in it and a vague name like program.py
. It’s much easier to read and understand several smaller files, each of which has 150-200 lines of code and a good clear name like ai.py
or physics.py
or quest.py
.
This is what programming in real life is like. It’s almost never the case that you’re starting a new program completely from scratch - you’ll almost always be working on a (probably really big!) program that someone else wrote years ago, or that you wrote a few weeks ago but it’s been long enough that now you don’t really remember what all the code does.
This is what you do: you read through the preexisting code, convince yourself that you understand what it does and how it fits together, and then, once you’re familiar with the codebase, you can start making changes and adding features.
The point of this project is for you to add several features to make your roguelike interesting and fun to play. I'd like you to:
main.py
file, something like this:# Thanks to Tamara O'Malley for the coin pickup code.
Here are some ideas for features.
If you come up with an idea you like better, do that instead!
Below is a walkthrough of adding 2 features. If you're not sure where or how to start, keep reading...
For starters, let’s add a simple feature to the game: a goal space. If the player reaches it then we’ll tell them good job and end the game.
It’ll look like this:
#######################
# #
# #
# #
# #
# ###############
# #
# #
# #
# #
# #
# # ############################# ###### # #
# @ #
# #
# ! #
# # ###############
Please enter a command and then press Enter.
Available commands:
[u]p - move up
[r]ight - move right
[d]own - move down
[l]eft - move left
[q]uit - quit the game
>
Here are the things I did in order to add that feature to the game:
Once I had done all of those things, I had a working goal space feature! Notice that this involved messing around with a lot of the code in roguelib.py
- you’ll have to do this too when you add your own features. It’s going to involve reading code and figuring out what it does and figuring out what you want to do and where you should make your changes. This is what programming is like! :)
Click here for the code I wrote to make that happen. Then read the rest of this section!
This file is a “diff”, which stands for “the difference between an old version of a program and a new version of that program”.
NOTE: red lines are old lines, green lines are new lines
NOTE: look for the function name between the blue/purple @@
symbols
NOTE: look for the file name at the end of a line of blue text, as seen on line 1: diff --git a/roguelike/game.py b/roguelike/game.py
NOTE: game.py
is the same as main.py
-- repl.it requires the "runner" file to be named main so we had to change it
As you scroll through, you might notice that I included a couple of assert
statements (see lines 61 and 69 in the diff). You don’t have to do that when you add a feature, I just did it for this particular feature in load_level()
because I wanted the program to loudly and specifically complain if someone either a) forgot to add a goal space or b) tried to add two goal spaces even though the way I wrote this code only supports tracking the (x, y)
position of a single goal space.
As you add the goal space to your roguelike, it might be helpful to refer to the following list of where the changes are made. LOOK CAREFULLY at the couple lines of code shown in the diff before and after each change to make sure you are adding the code in the right place. The line number of the diff is NOT the same as the line number of your code.
Add a goal space to the map.
In level.txt
, add the goal space. I used the character !
.
(lines 28-29)
Notice that I added the goal really close to the player so that when I test it I don't have to travel across the whole map. I can always move it later once I know it works.
Find the position of the goal space in the map.
In roguelib.py
, update load_level()
:
* look for the goal space character, !
, in level.txt
(lines 51, 60-62, 69)
* save its (x, y) coordinate position in the game dictionary.
(line 73)
Print the goal space.
In roguelib.py
, update draw_game()
so that it prints out a !
where the goal space is.
(lines 40-42)
Do something when the player reaches the goal space.
In main.py
, update run_game()
to print a message and end the game (break
) when the player reaches the goal space.
(lines 9-15)
You might be thinking: but what if I want to add a feature that can happen on more than one space at a time? Keep reading!
Here’s a feature that adds dumb monsters to the level -- let's call them goblins. They just sit there and don’t do anything, but the player can’t move into a space if it has a goblin in it. Here’s what that looks like:
#######################
# #
# #
# #
# #
# ###############
# #
# @ g #
# g #
# #
# #
# # ############################# ###### # #
# #
# #
# #
# # ###############
Please enter a command and then press Enter.
Available commands:
[u]p - move up
[r]ight - move right
[d]own - move down
[l]eft - move left
[q]uit - quit the game
>
Here are the things I did in order to add that feature to the game:
Notice that this feature is a bit more complicated than the goal space feature was. First, a goblin is a player so I created it as a dictionary; it holds information like its type (goblin), its x and y coordinates, and a couple other things. And because there can be multiple goblins in the level, I had to keep track of them in a list. This list could potentially hold other types of monsters as well.
Click here for the code I wrote to make that happen. Then keep reading...
As you add goblins to your roguelike, it might be helpful to refer to the following list of where the changes are made.
Add goblins to the map.
In level.txt
, add some monsters. I used g
for goblin.
(lines 11-12)
Notice that again, I added them really close to the player so that when I test it I don't have to travel across the whole map. I can always move them later once I know it works.
Find the positions of the goblins in the map.
In roguelib.py
, update load_level()
:
* look for each g
in level.txt
, create a goblin monster by storing its information (type, position, etc) in a dictionary, and add each goblin to a list of monsters
(line 75, 84-92)
* add the list of monsters to the game dictionary.
(line 101)
Print the goblins.
In roguelib.py
, update draw_game()
so that it prints out a g
where the goblin is.
(lines 51-55, 61-66)
Do something when the player reaches a goblin.
In roguelib.py
, update move()
and process_user_input()
so that the player can't move on top of a goblin.
(move()
: lines 25, 31, 39-42)
(process_user_input()
: line 110)
We followed these steps when implementing both of those features:
☃
, and add it to level.txt
one or more times.load_level()
to look for that character AND save its position in the game dictionary. If it needs to hold information, make it a dictionary that holds the position (along with whatever other info you need). If you're adding more than one of the character, store them in a list.draw_game()
to print that character out in the right place so that the player can see this new thing you added to the game. Or not...maybe there's an invisible monster? Yikes!Steps 1 through 3 were similar both times, but step 4 was different for each feature.
Now it's your turn! Add:
Also feel free to modify the goal space / dumb monster features if you like.
Before submitting your project, make sure you:
dragonquest by Tamara
main.py
file, something like this: # Thanks to Tamara O'Malley for the coin pickup code.
Your CODE should always follow the style guidelines. The part about descriptive names is important! For instance:
n
is a bad name, username
is a good one.ns
is a bad name, number_of_symbols
is a good one.Since we are doing this in the repl.it classroom as a Project, I have access to your code and your name should be in the project name. No need to turn it in on Google Classroom.
Most of the projects in this class were created by JR Heard, a TEALS volunteer at Madison, 2017-2019. His version of this project lives at https://blog.jrheard.com/python/roguelike.