Part III · Making It Solid

Chapter 10: When Things Go Wrong

Try something with your chapter 9 code: at the chest, instead of typing a number, type abc. Your game crashes. Not "the dial refuses," not "cold," an actual runtime error, and the program stops dead. Your player didn't do anything malicious, they just typed the wrong kind of thing, and your game had no way to recover. This chapter fixes that.

Why It Crashes

The line at fault is one you already know well:

QF Code example 1
var guess = num(input("Turn the dial to a number (1-100): "))

num() can convert a lot of things, but it can't convert "abc" into a number, there's no reasonable number that text could mean. When that happens, QF Code raises a runtime error, and unless something in your program is watching for it, that error stops the whole program right there.

attempt and error

attempt lets you run code that might fail, and catch the failure instead of letting it end your program.

QF Code example 2
attempt
    var guess = num(input("Turn the dial to a number (1-100): "))
error
    writeln("That doesn't look like a number.")
end attempt

If the line inside attempt succeeds, error never runs, execution just continues normally after end attempt. If it fails, QF Code jumps straight to error instead of crashing.

Capturing the Message

You can also grab the actual error message QF Code generated, by naming a variable right after error:

QF Code example 3
attempt
    var guess = num(input("Turn the dial to a number (1-100): "))
error message
    writeln("Problem: " + message)
end attempt

That variable only exists inside the handler, and it holds a human-readable description of what went wrong, useful for debugging while you're building, less useful to show a player directly in a finished game.

signal

Sometimes you want to raise an error on purpose, because your own code detected a problem, not because a built-in function failed.

QF Code example 4
function checkAge(age)
    if age < 0 then
        signal("Age cannot be negative.")
    end if
    return age
end function

signal() raises a real QF Code runtime error with whatever message you give it, and an attempt elsewhere can catch it exactly the same way it would catch any other runtime error.

last_error

last_error is always available, and holds three pieces of information about the most recent caught error: last_error.message, last_error.line, and last_error.code. For most situations, naming a variable after error is simpler and does the same job. last_error is there for the cases where you need it, particularly if you want to check error details somewhere other than immediately inside the handler that caught it.

Fixing the Chest

Now the dial stops crashing on bad input, and just asks again instead.

QF Code example 5
var guess = empty
var validInput = false

while not validInput
    attempt
        guess = num(input("Turn the dial to a number (1-100): "))
        validInput = true
    error
        writeln("That doesn't look like a number. Try again.")
    end attempt
end while

guessesLeft = guessesLeft - 1

A guess only counts against guessesLeft once it's actually a valid number. Typing abc costs the player nothing but a moment, instead of costing them the whole game.

Make It Yours. Find one other spot in your game where bad input could break something, maybe the name prompt, maybe a command, and wrap it in an attempt of your own. It's fine if nothing there is actually broken yet, the practice of asking "what could go wrong here" is the real point.

Chapter Checkpoint

Open either known-good checkpoint directly in QF Code, or download the .qfc file to keep locally. The IDE opens in a new tab and does not run the program automatically.

Starting code: the Chapter 9 checkpoint.

Completed code (Chapter 10):

QF Code example 6Open in QF Code
// The Hollow Cottage
// Chapter 10


const cottageName = "The Hollow Cottage"

function showInventory(items)
    writeln("You are carrying:")
    for each item in items
        writeln("- " + item)
    end for
end function

writeln("You are standing outside " + cottageName + ".")

var playerName = input("What is your name, traveler? ")
writeln("Welcome, " + playerName + ".")

var tries = 0
var solved = false

while tries < 3 and not solved
    tries = tries + 1
    var answer = input("There is a riddle carved into the door. It asks: what has keys but no locks? ")

    if lower(trim(answer)) == "piano" then
        writeln("The door swings open. You step inside.")
        solved = true
    else
        writeln("Nothing happens.")
    end if
end while

if not solved then
    writeln("The door refuses to budge. You'll have to find another way in.")
else
    writeln("You step inside. In the corner sits a locked chest with a dial numbered 1 to 100.")

    var secret = randomint(1, 100)
    var guessesLeft = 6
    var found = false

    while guessesLeft > 0 and not found
        var guess = empty
        var validInput = false

        while not validInput
            attempt
                guess = num(input("Turn the dial to a number (1-100): "))
                validInput = true
            error
                writeln("That doesn't look like a number. Try again.")
            end attempt
        end while

        guessesLeft = guessesLeft - 1

        if guess == secret then
            writeln("The chest clicks open!")
            found = true
        else
            var distance = abs(guess - secret)

            match distance
                when 0 to 5
                    writeln("Very warm. " + guessesLeft + " turns left.")
                when 6 to 20
                    writeln("Warm. " + guessesLeft + " turns left.")
                when else
                    writeln("Cold. " + guessesLeft + " turns left.")
            end match
        end if
    end while

    var inventory = array()
    append(inventory, "rusty key")
    append(inventory, "half-burned candle")

    if not found then
        writeln("The dial jams. The chest stays locked, for now.")
    else
        append(inventory, "small silver coin")
        writeln("Inside, you find a small silver coin.")
        showInventory(inventory)
    end if

    var command = input("What do you do? ")
    var words = split(lower(trim(command)), " ")
    var verb = words[0]

    match verb
        when "take"
            showInventory(inventory)
        when "look"
            writeln("A small cottage, one room, one door out.")
        when "quit"
        when "exit"
            writeln("You step back out into the cold.")
        when else
            writeln("Nothing happens.")
    end match
end if

Next: your program has been treating every variable the same way so far. Chapter 11 explains where names actually live, and finishes the job chapter 2 started with constants.


Copyright © 2026 Edison Mooers. Published by Gibidda Press. Free for personal learning and qualifying noncommercial educational use. Commercial training, organizational use, redistribution, and adaptation require written permission. Full use terms.