How Interactive Programs Think
A loop that waits, decides, and shows
In this lesson
Every program you have written so far runs top to bottom and stops. A game does not. A window does not. They keep going, waiting for you to do something.
That difference is one idea — the event loop — and once you see it, games, desktop apps, and the web frameworks from Unit 6 all turn out to have the same skeleton.
Explain it like I’m 5
An interactive program is like a host at a party: it keeps watching for something to happen, then responds, then goes back to watching.
State: what the program remembers
State is everything your program knows right now: the score, the player's position, what is in the bag, whether the game is still running.
In a script that runs once, state is barely a concept: variables get set and used. In an interactive program it is the center of everything, because the same code runs over and over and the only thing that differs between one pass and the next is the state.
Keeping it in one place (a dictionary, or later an object) is what stops interactive code turning into a tangle.
The loop: wait, decide, update, show
Every interactive program is a loop that does four things: take an input, decide what it means, update the state, show the result. Then round again.
Below is that loop with the waiting removed. The commands arrive from a list instead of a person, so it runs the same way every time. That substitution is worth noticing, because it is also how interactive programs get tested.
COMMANDS = ["look", "take lamp", "score", "quit", "look"]
state = {"running": True, "score": 0, "bag": []}
for command in COMMANDS:
if not state["running"]:
break
if command == "quit":
state["running"] = False
print("goodbye")
elif command == "look":
print("you are in a dim room")
elif command.startswith("take "):
item = command[5:]
state["bag"].append(item)
state["score"] += 10
print(f"you take the {item}")
elif command == "score":
print("score:", state["score"])
else:
print("i do not understand")
print("final state:", state)
you are in a dim room
you take the lamp
score: 10
goodbye
final state: {'running': False, 'score': 10, 'bag': ['lamp']}
Terminal input versus events
There is a real difference between the loop above and a game loop, and it is about who is waiting.
input() blocks: the program stops dead until the user presses Enter. Nothing else can happen: no animation, no timers, no clock.
A game loop never blocks. It runs continuously, perhaps sixty times a second, and each pass asks whether anything happened. Nothing pressed? Fine — update and draw anyway. That is why a game keeps animating while you sit still, and why it must redraw constantly rather than once.
The structure is the same. The difference is that a game's loop keeps turning whether or not you do anything.
Write the decision half of an event loop. Walk the commands, and for each one: "north" counts a step and prints that you walked, "quit" stops the loop, and anything else prints the help line. The loop must stop processing once running is False, so the final "north" never happens.
COMMANDS = ["north", "help", "north", "quit", "north"]
state = {"running": True, "steps": 0}
for command in COMMANDS:
if not state["running"]:
break
# TODO: "quit" stops the loop and prints "bye"
# TODO: "north" adds a step and prints "you walk north"
# TODO: anything else prints "commands: north, quit"
pass
print("steps taken:", state["steps"])
print("still running:", state["running"])
An if/elif/else chain inside the loop. Check "quit" first and set state["running"] = False; the break at the top of the next pass does the rest. Remember state["steps"] += 1 for north.
COMMANDS = ["north", "help", "north", "quit", "north"]
state = {"running": True, "steps": 0}
for command in COMMANDS:
if not state["running"]:
break
if command == "quit":
state["running"] = False
print("bye")
elif command == "north":
state["steps"] += 1
print("you walk north")
else:
print("commands: north, quit")
print("steps taken:", state["steps"])
print("still running:", state["running"])
you walk north
commands: north, quit
you walk north
bye
steps taken: 2
still running: False
Keep the decisions out of the loop
The example above has all its logic inside the loop, which is fine for five commands and a disaster for fifty. Interactive programs grow, and they grow in the loop.
The fix is the one this unit will apply repeatedly: separate the logic from the display. Put “what does this command do to the state?” in its own function, and leave the loop to do nothing but fetch input, call that function, and show the result.
That split is worth the effort for a reason beyond tidiness: the logic half becomes testable without a screen or a person, which is exactly why every Try-It in this unit can run here at all.
Common mistake: Expecting an interactive program to run top to bottom once
Every script so far has done exactly that.
Setup runs once; the loop runs forever. Anything that should happen repeatedly belongs inside it.
Common mistake: Putting all the logic inside one giant loop
Adding another elif is always the quickest next step.
Move each decision into a function. The loop should read as fetch, decide, show.
Common mistake: Changing the same state from several places
It is convenient to update the score wherever the score changes.
Keep state in one structure and change it in as few places as possible, or you will not be able to work out what changed it.
Common mistake: Forgetting a way out of the loop
The exit condition is easy to leave until later.
Write the quit branch first. A loop with no exit is the most common way to hang an interactive program.
What is state?
Score, position, whether the game is running. It is what differs between one pass of the loop and the next.
What does an event loop do?
Fetch, decide, update, show, round and round until something says stop.
Why does a game need to redraw repeatedly?
Unlike input(), a game loop never blocks, so it updates and draws each pass regardless.
Mini exercise (medium)
Apply the separation this lesson argues for. Write a handle(state, command) function that takes the current state and one command and returns the message to display, changing the state as needed. Support "add <item>", "list", and "quit", with anything else returning an unknown-command message. Then drive it with a loop that does nothing but call handle and print.
Practice here. Fill in the missing piece and click Run to try your answer in place.
def handle(state, command):
"""Change the state, return the message to show."""
# TODO: "add <item>" stores the item and reports how many there are
# TODO: "list" reports the items, or says the bag is empty
# TODO: "quit" stops the loop
# TODO: anything else reports an unknown command
return ""
state = {"running": True, "bag": []}
for command in ["list", "add rope", "add torch", "list", "dance", "quit"]:
if not state["running"]:
break
print(handle(state, command))
print("final bag:", state["bag"])
handle should return the message rather than printing it. That is what makes it testable. Use command.startswith("add ") and slice with command[4:] to get the item. Set state["running"] = False for quit.
def handle(state, command):
"""Change the state, return the message to show."""
if command == "quit":
state["running"] = False
return "goodbye"
if command.startswith("add "):
item = command[4:]
state["bag"].append(item)
return f"added {item} ({len(state['bag'])} in the bag)"
if command == "list":
if not state["bag"]:
return "the bag is empty"
return "bag: " + ", ".join(state["bag"])
return f"i do not know how to {command}"
state = {"running": True, "bag": []}
for command in ["list", "add rope", "add torch", "list", "dance", "quit"]:
if not state["running"]:
break
print(handle(state, command))
print("final bag:", state["bag"])
the bag is empty
added rope (1 in the bag)
added torch (2 in the bag)
bag: rope, torch
i do not know how to dance
goodbye
final bag: ['rope', 'torch']
s = {"running": True, "bag": []}
assert handle(s, "list") == "the bag is empty", "an empty bag should say so"
handle(s, "add lamp")
assert s["bag"] == ["lamp"], "add should put the item in the bag"
handle(s, "quit")
assert s["running"] is False, "quit must stop the loop"
assert "dance" in handle(s, "dance"), "an unknown command should mention what was typed"
print("✓ Looks good!")