Skip to content

Instantly share code, notes, and snippets.

@timcowlishaw
Created October 10, 2025 09:45
Show Gist options
  • Select an option

  • Save timcowlishaw/a7a193fba4b28df7da9db3891915c415 to your computer and use it in GitHub Desktop.

Select an option

Save timcowlishaw/a7a193fba4b28df7da9db3891915c415 to your computer and use it in GitHub Desktop.
# We're going to make an adventure game!
# In the game, you navigate through a maze of rooms, by typing in "north", "south", "east", or "west".
# In each room you enter, the computer will describe your surroundings, and then ask you what direction
# you want to go in next.
# If you haven't already done so, run this program in the PYTHON INTERPRETER to see how the gameplay works.
# So, how to put all this together?
# First, we need some descriptions for our rooms.
# Let's define them, using VARIABLES to give them each a name, and making each room a DICTIONARY, containing the description.
# Using a dictionary here might seem a bit superfluous, given it's only got one entry (the description) - we could
# just not bother with the dictionary, and make each variable just contain the string describing the room.
# However, wrapping this in a dictionary is a useful move for when we want to extend our game - If we wanted
# to keep some other information about each room here (Such as a list of characters or objects that live there),
# we can just extend the dictionary to contain it.
chapel = {
"description": "a dusty chapel, with broken windows through which the wind howls and the rain spits.",
}
passageway = {
"description": "a dingy, dimly lit passageway. you can hear the sound of dripping water faintly in the distance",
}
fork = {
"description": "a fork in the passageway, un-signposted.",
}
knight = {
"description": "a small candlelit chamber, containing a suit of armour",
}
dragon = {
"description": "A large, cavernous chamber from which heat radiates - its walls illuminated with the yellow glow of a fire, against which, in silhouette, you see a sleeping dragon"
}
# Once we've done that, we need to describe the layout of the maze. This is like a two dimensional
#grid (from north to south, and from east to west).
# We can represent that with a list of lists (the outer list represents the steps from north to south,
#and the inner lists represent the steps from west to east)
# Here, you can use the variables we defined earlier, in order to place each room in the right place in the grid.
# Note, you can repeat rooms if you want - like our "passageway" example which appears multiple times
# connecting different rooms. "None", which you see here is a special value in python - it represents an absence,
# nothing. When we include None here, it represents a part of the map that the player can't access.
map = [
[None, None, dragon, passageway, fork, knight ],
[None, None, None, None, passageway, None ],
[None, None, None, passageway, passageway, None ],
[None, None, None, passageway, None, None ],
[None, None, None, chapel, None, None ]
]
# We also need to define in which room the player starts their adventure. This is a coordinate - two
# numbers, one representing which ROW of the map they're in (their position from north to south),
# and the other representing which COLUMN (their position from west to east).
# A coordinate like this we can represent as a TUPLE.
# Tuples are very much like lists, except we normally use them for collections that have a fixed, known
# length, ahead of time. For isnstance, a coordinate in our game will ALWAYS be two numbers, not one or three, or seventeen,
# or whatever, so it makes sense to use a tuple rather than a list.
# In python, when we count positions in a list or tuple, we count from 0, instead of from 1, so
# the player will start in the 4th column of the 5th row - the chapel.
# Once again, we're going to wrap this position in a dictionary, representing the state of our player,
# for the same reason as before. If later on we wanted to allow the player to pick up and keep objects they find,
# for instance, or keep an accumulated score, or number of lives, we'd need a place to store it, and this allows
# us to do that.
player = {
"position": (4, 3)
}
# We've now set up everything we need to describe our maze! Each room has a description, the map
# describes what room is at what position, and we know which room the player starts in.
# We could change everything above this point and get a completely different maze, and a completely
# different story! Everything below here can remain the same.
# So, your first challenge, is to make your own game, based on this structure. Edit the variables above,
# the map and the starting position, to tell a different story, and describe a different world.
# When you're done, we can read on, but we're going to do it slightly out of order, for reasons that
# will become obvious shortly. Once you've got to this point, scroll down the file until you find a
# comment which starts with the double arrow "repeat" emoji: ๐Ÿ”,
# ********************************** STOP READING HERE AND FOLLOW THE EMOJI! ************************
# ๐Ÿ› ๏ธ THIS IS WHERE WE DEFINE OUR FUNCTIONS!
# Ok, welcome back, sorry about that diversion. Maybe now you're getting an idea about why it was
# necessary - we wanted to read our program starting "from the outside, in", working out what our MAIN
# loop does, and then getting into the details later.
# However, our main loop takes advantage of a bunch of FUNCTIONS, much like len, input, and print, that we've
# already met, but which seem to be kinda specific to the game we're writing! It seems unlikely that python comes
# with a built in room_at function, for instance, for folks wanting to build a text adventure game. So, where
# do these functions come from? The answer is, we define them ourselves, here in the file!
# We're gonna talk more about this next week, but for now, I just want you to note that we can use the special
# 'def' keyword to define functions to use later on in our program.
# This is the reaason I had to send you to the bottom of the file first, in order to read the main loop of the program:
# Our main loop has to come *after* our function definitions, as python interprets the file from top to
# bottom, and our functions have to be defined BEFORE they gets used!
#
# This is an example of where our intution of a program being "a bunch of statements that are executed in order,
# from top to bottom" falls down a bit, so we need to be alert.
# Anyway, on with the game logic.
# There were three places in the main loop where we called a function to help us with some specific part of the
# game logic.
#
# - We called 'room_at', to be able to tell what room a player is in, based on his position, so we can print out
# the right description.
#
# - we called 'move', to update the player's position when they move - giving them new coordinates in response to they
# direction that they specify.'
#
# - we called 'neighbours', to check the player's position, tell them which directions they are allowed to move in.
# - we're also going to define a function which tells us whether or not the player has reached the edge
# of the map, for reasons that will become clear extremely soon.
#
# Each of these are simple, self-contained things, so we're going to define them as FUNCTIONS
# that we can use later on.
# First, a function which tells us which room is at a given point on the map.
# It takes a coordinate as an ARGUMENT, and RETURNS the room, or None, if there's
# nothing on the map at that point.
def room_at(coords):
return map[coords[0]][coords[1]]
# Now, a function which checks whether we're about to go off the edge of the map or not.
# It takes the current coordinates of the player, and the direction that they are travelling in,
# and returns a BOOLEAN - True if it's safe for them to move in that direction, and False if not.
def check_edge(coords, direction):
if direction == "north":
return coords[0] > 0
if direction == "south":
return coords[0] < len(map) - 1
if direction == "west":
return coords[1] > 0
if direction == "east":
return coords[1] < len(map[0]) - 1
# Next, Another function which updates the player's position as they move. Like the previous one,
# it takes the current position of the player, and the direction they're travelling in as an argument,
# then returns their NEW position after they've moved.
def move(position, direction):
if direction == "north":
return (position[0] - 1, position[1])
if direction == "south":
return (position[0] + 1, position[1])
if direction == "west":
return (position[0], position[1] - 1)
if direction == "east":
return (position[0], position[1] + 1)
# Finally we need a function which tells us in which direction a player is allowed to move, based on their current
# position. This takes their current position and checks the square of the map in every direction, to see if there
# is something there or not, then returns a list of the allowed directions. Note that this USES the three functions
# we defined previously: check_edge, room_at, and move. This is an extremely common strategy in programming - you
# break down a tricky problem into smaller simpler, parts, then combine them together to make it easier to solve.
def neighbours(coord):
directions = []
for direction in ["north", "south", "east", "west"]:
if check_edge(coord, direction):
room = room_at(move(coord, direction))
if room is not None:
directions.append(direction)
return directions
# ๐Ÿ” THIS IS THE MAIN PROGRAM LOOP!
# This is the main loop of the program, it's what gets run when we execute this file with python.
# Why do we call it a "main loop"? Because it loops around forever, until the player quits.
# This is because we've used the "while" keyword below, and passed it "True" as the condition.
while True:
# First, we store the position of the player in a variable, as we'll use it a lot below,
# this makes everything that follows much more readable
position = player["position"]
# Then we get the room that they are in currently
room = room_at(position)
# We print out the description of the room they are in
print("You are in " + room["description"])
# Then we get the list of possible directions they can travel in.
directions = neighbours(position)
# We print out the list of directions:
print("You can go: " + ", ".join(directions))
# Then we ask them which way they want to go.
direction = input("Which way?> ")
# We check that the direction they gave us is a possible direction they can move in:
if direction in directions:
# If it is, we update the player with the new position
player["position"] = move(position, direction)
else:
# If not, we print a warning, and we don't update their position
print("You can't go " + direction)
# Finally we print a divider and some space to help make it easier for them to read
# through the story
print("\n---\n")
# At the end of the loop, we go back to the start and do it agin.
# If you're reading through this for the first time you might be thinking - "hang on, you're cheating
# a bit here - you haven't explained anything about how these room_at, neighbours, and move functions
# actually work, and where they come from!", and you'd be entirely right. this isn't some magic functionality
# for making text games that comes included with python - we need to define all these things themselves!
# However, it didn't really make sense to start getting into the detail of how each of these things work,
# until we'd seen, in broad strokes, how the game was going to behave (which we've just done, reading through
# the main loop). Now we've done that, we're ready to dig into the detail - so return above to the comment that
# says "๐Ÿ› ๏ธ THIS IS WHERE WE DEFINE OUR FUNCTIONS", and continue from there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment