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.
var score = 0 // global
function addScore(amount)
var doubled = amount * 2 // local, gone once addScore finishes
score = score + doubled
end functionWhat 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.
if true then
var message = "Hello from inside the if."
end if
writeln(message) // this works, message is still visible hereThat 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.
const maxAttempts = 3
maxAttempts = 5 // error, a constant binding cannot be reassignedOne 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.
const knownItems = array()
append(knownItems, "torch") // fine, this changes the array's contents
knownItems = array() // error, this reassigns the constant itselfSame-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.
var x = 1
var x = 2
writeln(x) // 2This 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.
var score = 0
function addScore(amount)
score = score + amount
writeln("(+" + amount + " points)")
end functionCall it wherever the player accomplishes something:
if guess == secret then
writeln("The chest clicks open!")
found = true
addScore(10)
end ifMake 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.
Start this chapter
Chapter 11 starting checkpoint.
Continue from here
Chapter 11 completed checkpoint.
Starting code: the Chapter 10 checkpoint.
Completed code (Chapter 11):
// 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 ifCopyright © 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.