Part III · Making It Solid

Chapter 11: Where Names Live

Every variable you've written so far has lived at the top level of your program, visible everywhere, all the time. That's worked because your game has been small. It won't stay small forever, and once you have several functions and a program with real structure, where a name is visible, and where it isn't, starts to matter.

Global and Local

QF Code keeps track of names in a small number of places: one global environment for your whole program, and one fresh local environment every time a function is called. Variables you declare at the top level of your file are global. Variables and parameters declared inside a function exist only for that one call, and disappear once the function returns.

QF Code example 1
var score = 0   // global

function addScore(amount)
    var doubled = amount * 2   // local, gone once addScore finishes
    score = score + doubled
end function

What Functions Can See

A function can read and change a global variable, as long as it doesn't have a local variable or parameter with that exact same name. addScore above does exactly that, score isn't a parameter or a local variable inside the function, so the line score = score + doubled reaches out and updates the global one directly.

A Surprise: if and Loops Don't Create Their Own Scope

Here's the one genuinely surprising rule in this chapter, especially if you've used a language where curly braces mark off their own private scope. In QF Code, if, while, for, for each, and match do not create a scope of their own. A variable declared inside one of them belongs to whatever scope surrounds it.

QF Code example 2
if true then
    var message = "Hello from inside the if."
end if

writeln(message)   // this works, message is still visible here

That would be an error in plenty of other languages. In QF Code, it's just how the surrounding scope works, an if block doesn't wall anything off. It's worth knowing on purpose, rather than being surprised by it the first time a variable you thought was temporary turns out to still be there afterward.

Constants, Properly

Back in chapter 2, you met const briefly. Here's the rest of the story. A constant needs its value up front, you can't declare one without initializing it, and once it's declared, that name can never be reassigned.

QF Code example 3
const maxAttempts = 3

maxAttempts = 5   // error, a constant binding cannot be reassigned

One nuance worth knowing: const protects the binding, the name itself, not necessarily everything the name points to. If a constant holds an array, the array's contents can still change, even though the constant itself can never be pointed at a different array.

QF Code example 4
const knownItems = array()
append(knownItems, "torch")   // fine, this changes the array's contents

knownItems = array()          // error, this reassigns the constant itself

Same-Name Redeclaration

One more behavior worth knowing about, so it doesn't confuse you later: declaring a var with a name that already exists in the same scope doesn't cause an error, it just quietly updates that variable's value.

QF Code example 5
var x = 1
var x = 2
writeln(x)   // 2

This is usually harmless, but it means QF Code won't warn you if you accidentally reuse a variable name you'd already used for something else nearby. Once a name is a const, though, that protection kicks back in, you can't redeclare a constant at all.

Avoiding Accidental Dependencies

None of this means you should avoid globals entirely, your adventure's inventory and score are globals on purpose, and that's fine for a program this size. The practical habit worth building is this: when a function needs a value, prefer passing it in as a parameter over reaching out and grabbing a global directly, the same instinct chapter 6 pointed at when talking about the absence of closures. It keeps a function's behavior predictable just by reading its declaration line, instead of needing to know what else happens to be sitting in scope when it runs.

Growing the Adventure

A running score, tracked with a global variable and updated through a function, exactly the pattern from the top of this chapter.

QF Code example 6
var score = 0

function addScore(amount)
    score = score + amount
    writeln("(+" + amount + " points)")
end function

Call it wherever the player accomplishes something:

QF Code example 7
if guess == secret then
    writeln("The chest clicks open!")
    found = true
    addScore(10)
end if

Make It Yours. Add another place in your game where addScore() makes sense, finding an item, answering the riddle, whatever fits, and print the final score somewhere near the end of the program.

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 10 checkpoint.

Completed code (Chapter 11):

QF Code example 8Open in QF Code
// The Hollow Cottage
// Chapter 11


const cottageName = "The Hollow Cottage"

var score = 0

function addScore(amount)
    score = score + amount
    writeln("(+" + amount + " points)")
end function

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
            addScore(10)
        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

    writeln("Final score: " + score)
end if

Next: your game has been entirely silent so far. Chapter 12 gives it a voice.


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.