Lesson 3 — Conditionals

By the end of this you can: make a program choose, compare values with == and <, chain decisions with elif, combine tests with and and or, and make a game react to what is actually happening.

The problem conditionals solve

Here is a program with no decision in it:

health = 100
health = health - 30
print("Health:", health)
Health: 70

It works. It also does exactly the same thing every single time you run it, which is the problem. A game that cannot notice anything is not a game — it is an animation. Something has to be able to ask has this happened yet?

That question is if:

health = 30

if health < 50:
    print("Warning: low health")

print("Still playing")
Warning: low health
Still playing

Change health to 70 and run it again. The warning disappears and Still playing is all you get. The program now behaves differently depending on what is true, and nothing about the code changed.

Two things to notice, because both catch people:

  • The line ends in a colon.
  • The line underneath is indented. That indent is what says "this belongs to the if". print("Still playing") is back out at the left, so it is not part of the decision and always runs.

Asking the question

The thing between if and the colon is a comparison, and it comes out either True or False:

print(10 > 5)
print(10 < 5)
print(10 == 10)
print(10 != 10)
print(10 >= 10)
print(3 <= 2)
True
False
True
False
True
False
  • > and < — bigger than, smaller than.
  • >= and <= — bigger or equal, smaller or equal.
  • == — equal. Two equals signs, always.
  • != — not equal.

= and == are different and it matters. One equals sign stores: health = 30 puts 30 into health. Two equals signs asks: health == 30 is a question whose answer is True or False. Writing this:

if health = 30:

is not a typo Python can forgive — it stops with SyntaxError: invalid syntax, because you asked it to store something in the middle of a question. It is the most common mistake in this lesson, and the error message does not explain itself, so learn what the message means rather than what it says.

else — the other path

if on its own does something or does nothing. else gives it somewhere to go:

score = 40

if score >= 50:
    print("You win")
else:
    print("Try again")
Try again

Exactly one of those two lines will print, always. That is the point of else: there is no way to fall through both.

elif — more than two answers

Real games have more than two outcomes. elif — short for "else if" — chains them:

score = 78

if score >= 90:
    print("Rank: S")
elif score >= 75:
    print("Rank: A")
elif score >= 50:
    print("Rank: B")
else:
    print("Rank: C")
Rank: A

Python checks them top to bottom and stops at the first one that is True. 78 is not 90 or more, so it moves on. 78 is 75 or more, so it prints Rank: A and skips everything below — including score >= 50, which is also true. First match wins.

That "stops at the first true one" is the whole reason elif exists. Watch what happens with three separate if statements instead:

score = 95

if score >= 90:
    print("Rank: S")
if score >= 75:
    print("Rank: A")
if score >= 50:
    print("Rank: B")
Rank: S
Rank: A
Rank: B

Three ranks for one score. Each if is its own separate question, so all three get asked and all three are true. Nothing errors — you just get nonsense, which is the worst kind of bug to find.

Order matters too. If you put score >= 50 first, every score of 50 or more prints Rank: B and the better ranks can never be reached. With elif, put the hardest test at the top.

and, or, not

Sometimes one question is not enough:

has_key = True
door_locked = True

if has_key and door_locked:
    print("You unlock the door")

if has_key or door_locked:
    print("At least one of those is true")

if not has_key:
    print("You need a key")
You unlock the door
At least one of those is true
  • and — both have to be true.
  • or — at least one has to be true.
  • not — flips it.

The third one printed nothing, because has_key is True, so not has_key is False.

True and False are real Python values, spelled with a capital letter. You can store one in a variable like anything else, and a variable holding one is already a question you can ask:

game_over = False

if not game_over:
    print("Keep going")
Keep going

You never need to write if game_over == True. if game_over says the same thing and reads like English.

Now put it on the screen

A health bar that tells you how worried to be:

from kaypy import *

kaypy(width=800, height=600, background=[20, 24, 36])

health = 30

if health > 60:
    bar = GREEN
elif health > 25:
    bar = YELLOW
else:
    bar = RED

add([rect(health * 4, 30), pos(40, 40), color(bar)])
add([text("Health: " + str(health), size=24), pos(40, 90)])

Press Run. The bar is short and yellow, because 30 is more than 25 but not more than 60.

Change one number. Set health to 80 and run it again — long and green. Then 10 — short and red. One variable, three completely different pictures, and you did not touch the drawing code. That separation is what conditionals buy you.

GREEN, YELLOW and RED are colors the engine already knows. There are eight of them, and they are only numbers — print(RED) gives (255, 0, 0).

Deciding every frame

The examples so far decide once, when the program starts. A game has to keep deciding, so the test goes inside something that runs every frame:

from kaypy import *

kaypy(width=800, height=600, background=[20, 24, 36])
loadSprite("bean", "images/bean.png")

player = add([sprite("bean"), pos(100, 300), area()])

@onUpdate
def wrap_around():
    player.pos.x = player.pos.x + 3
    if player.pos.x > width():
        player.pos.x = 0

The bean walks right, and the moment it goes past the right-hand edge the if notices and puts it back at 0. Run it and watch it loop forever.

width() is the width of the window, so this works whatever size you made it. That is worth more than typing 800 — a number you typed by hand is wrong the day you change the window.

Watch for

== asks, = stores. The error is SyntaxError: invalid syntax and it points at the if line.

The colon and the indent are both required. Forgetting the colon is a syntax error. Forgetting the indent gives you IndentationError: expected an indented block.

Use elif, not a stack of separate ifs — unless you really do want every true one to happen.

Put the hardest test first in an elif chain, or it can never be reached.

Do not compare to True. Write if ready: rather than if ready == True:.

Classwork 1 — The Damage Check

Build a program that reacts to a single number.

Start from this and press Run to see where you are:

from kaypy import *

kaypy(width=800, height=600, background=[20, 24, 36])

health = 45

add([text("Health: " + str(health), size=28), pos(40, 40)])

Now add, one at a time, running after each:

  1. A bar under the text: rect(health * 4, 24) at pos(40, 90).
  2. Color it with an if / elif / else chain — green above 60, yellow above 25, red otherwise.
  3. A message under the bar that says Critical! only when health is below
  4. Use a plain if, with no else.
  5. A second variable, shield = True, and a line that prints Shield holding to the console when the shield is up and health is above 0.

Change health and rerun until you have seen all three bar colors, and made Critical! appear and disappear.

Stretch: make Critical! appear only when health is below 20 and the shield is down. What do you set the two variables to so that it shows?

Classwork 2 — The Locked Door

Two sprites and one decision.

from kaypy import *

kaypy(width=800, height=600, background=[20, 24, 36])
loadSprite("bean", "images/bean.png")
loadSprite("key", "images/key.png")
loadSprite("door", "images/door.png")

has_key = False

add([sprite("door"), pos(600, 260)])
add([sprite("bean"), pos(120, 280), area()])
  1. Add the key to the screen at pos(340, 300) — but only when has_key is False. Once it has been picked up there is nothing left to draw.
  2. Add a label at pos(40, 40) that says You have the key or The door is locked, depending on has_key.
  3. Set has_key = True and run it again. The key should vanish and the label should change, and you only changed one word.

Stretch: add a moves_left number. The label should say Out of moves when it reaches 0, whatever has_key is — so that test has to come first in the chain. Prove it by setting has_key = True and moves_left = 0 together.

Check yourself

What does this print?

lives = 3

if lives > 5:
    print("Plenty")
elif lives > 0:
    print("Careful")
elif lives > 2:
    print("Fine")
else:
    print("Game over")
Careful
Why not "Fine"?

lives > 2 is true — 3 is more than 2 — but Python never gets there. It takes the first branch that is true, which is lives > 0, prints Careful, and skips the rest of the chain.

That third branch can never run at all. Any number that passes lives > 2 has already passed lives > 0 one line earlier. A branch that is impossible to reach is not an error and Python will not warn you, which is exactly why the order of an elif chain is worth reading twice.



← Lesson 2 — Input · All nine lessons · Lesson 4 — Loops →