Part 3¶
So far we’ve made a game about falling ingredients and catching them on a plate. In this part we’ll add a scoring system so your players can earn some points for the burgers they make.
To start with your code should look something like this:
import random
TITLE = "Burgers"
WIDTH = 1000
HEIGHT = 714
SPAWN_ITEM_INTERVAL = 0.5
ITEM_X_MIN = 250
ITEM_X_MAX = 750
FALL_SPEED = 5
PLATE_Y = 690
PLATE_SPEED = 10
CATCH_RANGE_X = 40
CATCH_RANGE_Y = 20
IMAGE_SIZE = 128
NUM_ITEM_TYPES = 5
item_images = ["burgers/bun_bottom",
"burgers/bun_top",
"burgers/meat",
"burgers/cheese",
"burgers/tomato"
]
item_heights = [11, 28, 14, 4, 6]
class GameData:
pass
game = GameData()
def start_game():
game.items = []
game.plate_items = []
game.plate = Actor("burgers/plate", (WIDTH/2, PLATE_Y))
clock.schedule(spawn_item, SPAWN_ITEM_INTERVAL)
def draw():
screen.blit("burgers/background",(0,0))
for item in game.items:
item.draw()
game.plate.draw()
for item in game.plate_items:
item.draw()
def update():
if (keyboard[keys.A] or keyboard[keys.LEFT]):
game.plate.x -= PLATE_SPEED
if (keyboard[keys.D] or keyboard[keys.RIGHT]):
game.plate.x += PLATE_SPEED
if (game.plate.x < ITEM_X_MIN):
game.plate.x = ITEM_X_MIN
if (game.plate.x > ITEM_X_MAX):
game.plate.x = ITEM_X_MAX
for item in list(game.items):
item.y += FALL_SPEED
if (item.y > HEIGHT):
game.items.remove(item)
elif (abs(item.y - (game.plate.y - game.stack_height)) < CATCH_RANGE_Y and
abs(item.x - game.plate.x) < CATCH_RANGE_X):
game.items.remove(item)
game.plate_items.append(item)
game.stack_height = 0
for item in game.plate_items:
item.y = game.plate.y - game.stack_height
item.x = game.plate.x
game.stack_height += item_heights[item.item_type]
def spawn_item():
item_type = random.randint(0, NUM_ITEM_TYPES-1)
new_item = Actor(item_images[item_type], (random.randint(ITEM_X_MIN, ITEM_X_MAX),100))
new_item.item_type = item_type
game.items.append(new_item)
clock.schedule(spawn_item, SPAWN_ITEM_INTERVAL)
start_game()
Pointless¶
OK, so now we have target burgers. Next, we should add some text beneath each one to say how many points it is worth. First, add this line to near the top of the file to represent how many points each type of burger is worth:
item_heights = [11,28, 14, 4, 6]
target_lists = [[0,2,3,4,1], [0,3,2,1], [0,2,1]]
target_points = [10, 5, 2]
Now let’s change the draw_burger_sequence
function so that it also displays the number of points for each burger. Make the changes in the highlighted lines below:
def draw_burger_sequence(sequence, pos_x, pos_y, points):
screen.draw.text("{0}points".format(points),
centerx = pos_x + IMAGE_SIZE/2,
centery = pos_y + 70,
color="orange")
for item_type in sequence:
screen.blit(item_images[item_type], (pos_x, pos_y))
pos_y -= item_heights[item_type]
We added a new line and we also changed the function signature so that it takes new parameter, points
. We used the screen.draw.text
function in Flappy Bird. Do you remember what each parameter in does?
If you try playing the game now, you’ll see that we get this error message:
TypeError: draw_burger_sequence() missing 1 required positional argument: 'points'
That’s because we added a new parameter points
to our draw_burger_sequence
function, but we forgot to add a points
argument when we call it in the draw
function. Make the end of your draw function look like this:
draw_pos = HEIGHT-150
for sequence, points in zip(target_lists, target_points):
draw_burger_sequence(sequence, 15 ,draw_pos, points)
draw_pos -= 200
Here we’re using the zip function. It’s a function that’s built into Python and we can use it to loop through two different lists at the same time. It’s like the two lists get zipped together!
In this code we’re looping through target_lists
and target_points
at the same time. sequence
and points
are variables that represent each item in the lists as we go through them. sequence
is the first variable, so it takes values from the first list in the zip, and points
is the second so it takes values from the second list, target_points
.
Well the points are there, but it looks like we have a couple of problems. The points are slightly hidden behind the burgers, and there is no space between the number, and the word “points”. Can you fix these two issues? This is how we want it to look:
What’s the score?¶
Great, now we have some burger targets to aim for. It would be good if we could actually earn those points!
First let’s create a score variable to keep track of how many points the player has earned so far. Add this line to your start_game function:
def start_game():
game.score = 0
game.items = []
Now let’s add a line to the end of the draw function to print this score on the screen. Make sure you indent it correctly so that it’s in the function but not inside the loop you already have at the end of the function.
screen.draw.text("Score: {0}".format(game.score),
centerx = WIDTH/2,
bottom = HEIGHT,
fontsize=40)
Now we have the score on the screen. But it doesn’t actually change yet, we need to do that next. But first we should fix the way the plate and the score overlap. Can you figure out a way to fix them?
Try to fix this now. If you get stuck just leave it for now and we’ll come back to it later.
Finally getting to the point¶
Ok, so let’s actually give the player some points. We’re going to compare the list of items currently on the plate to each of the target sequences. We have the game.plate_items
list, but this is a list of Actor objects, and each target sequence is a list of numbers. There are ways we could convert the Actor list to a number list, but to keep things simple we’ll make another list to keep track of what’s on the plate, but this new list will store numbers.
This new list will be called game.plate_item_types
. Let’s add this to start_game:
def start_game():
game.score = 0
game.items = []
game.plate_items = []
game.plate_item_types = []
game.plate = Actor("burgers/plate", (WIDTH/2, PLATE_Y))
Then we need to add to this list when we catch an item. Add the following line to the item loop in your update function.
for item in list(game.items):
item.y += FALL_SPEED
if (item.y > HEIGHT):
game.items.remove(item)
elif (abs(item.y - (game.plate.y - game.stack_height)) < CATCH_RANGE_Y and
abs(item.x - game.plate.x) < CATCH_RANGE_X):
game.items.remove(item)
game.plate_items.append(item)
game.plate_item_types.append(item.item_type)
Notice how it’s very similar to the line before it, except instead of adding item
(which is the Actor), we add item.item_type
, which is the number representing what type of item it is.
Now that we have this list, we need to compare it to the target sequences to see if the player has made one of the target burgers. Let’s add a new function to check for matches. Add this new function wherever you like, as long as it’s not inside another function.
def check_for_target_burgers():
for sequence, points in zip(target_lists, target_points):
if (game.plate_item_types == sequence):
game.score += points
game.plate_items = []
game.plate_item_types = []
We’re using the zip function again to iterate (loop) through two lists at the same time. If we find a match then we increase the players score the right number of points and then empty the plate by setting both of our item lists back to empty lists.
Now we need to actually call this function somewhere. We could put the check anywhere in the update function, but this is a bit wasteful. Most of the time there’s no need to check because nothing has changed. If we want to be efficient we could just only do the check when a new item is caught.
Find the code in the update function where we catch items and add the highlighted line:
for item in list(game.items):
item.y += FALL_SPEED
if (item.y > HEIGHT):
game.items.remove(item)
elif (abs(item.y - (game.plate.y - game.stack_height)) < CATCH_RANGE_Y and
abs(item.x - game.plate.x) < CATCH_RANGE_X):
game.items.remove(item)
game.plate_items.append(item)
game.plate_item_types.append(item.item_type)
check_for_target_burgers()
You should now be able to score points. But you’ve probably noticed that if your burger goes wrong it’s impossible to get rid of it! We’ll look at ways to fix this in part 4, but for now let’s do a simple fix. We’ll make it so that when you press the escape key on your keyboard the plate is cleared. Add this function:
def on_key_down(key):
if (key == keys.ESCAPE):
game.plate_items=[]
game.plate_item_types = []
Now if your burger goes wrong you can press escape to reset your plate.
Stop the plate overlapping the score¶
Finally, as promised here are some ways to stop the plate overlapping the score. If you’ve already found your own fix you can ignore this section. I’m going to show two different ways to fix it:
Solution 1: Move the score to the top of the screen¶
To put the score at the top of the screen just change the y value used in the call to screen.draw.text
in the draw function. This function actually allows a few different parameters to specify the y value, currently we’re using bottom = HEIGHT
to put the bottom of the text on the bottom of the screen. To position it 10 pixels below the top of the screen we can make the following change to the draw function:
screen.draw.text("Score: {0}".format(game.score),
centerx = WIDTH/2,
top = 10,
fontsize=40)
Solution 2: Move the plate up a bit¶
We already have a constant which defines the y position of the plate. If we want to move the plate higher all we need to do is change that value:
PLATE_Y = 670
Coming up in Part 4¶
In part 4 we will add an intro screen and a final score screen. We’ll add a better solution for what happens when your burger goes wrong, and we’ll also fix a problem you might have noticed, that sometimes you need to wait a long time for the ingredient you need.