Part II · Growing the Project

Chapter 8: Numbers Under the Hood

Every room, every choice, every riddle in your game so far has been fixed in advance. Play it twice, get the exact same experience both times. Real games usually have some element of chance, and real programs constantly need to round, measure, and compare numbers. This chapter covers QF Code's math library, and puts it to work building something new: a locked chest with a combination the player has to guess.

Chance and Randomness

random() returns a random number between 0 and 1, not including 1 itself.

QF Code example 1
writeln(random())   // something like 0.4821...

You'll rarely want a fraction like that directly. Almost always, you want a whole number in a specific range, and that's what randomint() is for.

QF Code example 2
var roll = randomint(1, 6)   // a number from 1 to 6, inclusive on both ends

randomint() always includes both endpoints you give it, and both must be whole numbers.

Rounding

Four functions handle turning a number into a whole number, each rounding a different direction:

QF Code example 3
round(4.5)     // 5, rounds to the nearest whole number
floor(4.9)     // 4, always rounds down
ceiling(4.1)   // 5, always rounds up

And abs() strips away a number's sign, turning any negative number positive while leaving positive numbers alone:

QF Code example 4
abs(-7)    // 7
abs(7)     // 7

That last one is more useful than it looks. If you want to know how far apart two numbers are, without caring which one is bigger, abs() of their difference gives you the answer.

QF Code example 5
writeln(abs(50 - 82))   // 32

Comparing and Combining

min() and max() return whichever of two numbers is smaller or larger:

QF Code example 6
min(4, 9)   // 4
max(4, 9)   // 9

pow() raises a number to a power:

QF Code example 7
pow(2, 10)   // 1024

sqrt() gives you a square root, and only accepts non-negative numbers, QF Code raises an error rather than guessing what you meant by the square root of a negative number.

QF Code example 8
sqrt(64)   // 8

Growing the Adventure

Time to build something that uses several of these at once: a locked chest with a hidden combination.

QF Code example 9
// The Hollow Cottage
// Chapter 8


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 = num(input("Turn the dial to a number (1-100): "))
        guessesLeft = guessesLeft - 1

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

            if distance <= 5 then
                writeln("Very warm. " + guessesLeft + " turns left.")
            else if distance <= 20 then
                writeln("Warm. " + guessesLeft + " turns left.")
            else
                writeln("Cold. " + guessesLeft + " turns left.")
            end if
        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]

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

The "warm and cold" hints come straight from abs() measuring the distance between the guess and the secret number, exactly the kind of thing that function is built for.

Make It Yours. Change the range, the number of guesses, or the distance thresholds for warm and cold. Try making the hints stricter or more forgiving, and see how it changes the feel of the puzzle.

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

Completed code (Chapter 8):

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


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 = num(input("Turn the dial to a number (1-100): "))
        guessesLeft = guessesLeft - 1

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

            if distance <= 5 then
                writeln("Very warm. " + guessesLeft + " turns left.")
            else if distance <= 20 then
                writeln("Warm. " + guessesLeft + " turns left.")
            else
                writeln("Cold. " + guessesLeft + " turns left.")
            end if
        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]

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

Next: your command parser only recognizes a couple of words right now, and your if/else if chains are starting to stack up. Chapter 9 gives you a cleaner way to handle a lot of possibilities at once.


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.