Part III · Making It Solid

Chapter 12: Sound and Feedback

Your game has been completely silent for eleven chapters. A locked chest clicking open, a wrong guess, a door swinging shut, all of it has been text only. Sometimes a sound sells a moment better than a sentence can. This chapter gives your game a voice, literally.

bell

The simplest sound QF Code can make is bell(), a short, fixed tone.

QF Code example 1
bell()

That's it, no arguments, no options. It plays a brief beep at a fixed pitch and duration, useful anywhere you want a quick, neutral "ding" without thinking about musical details.

sound

sound(pitch, duration_ms) gives you control over both the pitch and how long it plays. Pitch can be a plain frequency in hertz:

QF Code example 2
sound(440, 300)   // A4, for 300 milliseconds

Or, often easier to reason about, a note name:

QF Code example 3
sound("C4", 300)
sound("A#3", 200)
sound("Bb5", 500)

A note name is a letter A through G, an optional sharp or flat, and an octave number. You don't need music theory to use this, a short rising sequence of notes reads as "good news" to almost anyone, and a falling one reads as "bad news," without a single word of text.

QF Code example 4
sound("C4", 150)
sound("E4", 150)
sound("G4", 150)   // a bright, rising little chime

The One Thing to Know Before You Use This

Here's the part worth understanding before it surprises you: both sound() and bell() block your program while they play. Nothing else happens until the tone finishes, not the next line of your code, not even the Stop button. If you call bell() five times in a row inside a loop, your game, and your browser tab, will feel frozen for the entire string of tones, one after another, with no way to interrupt it partway through.

QF Code example 5
for i = 1 to 5
    bell()
end for

That's five short beeps in a row, and for that whole stretch, Stop simply can't do anything, there's no statement running for it to catch between tones. This isn't a bug, it's just how blocking playback works. The practical takeaway: keep sound sequences short and deliberate, a handful of notes for a specific moment, not something that could end up looping an unknown number of times.

Growing the Adventure

A few well-placed sounds at the moments that already matter in your game:

QF Code example 6
if guess == secret then
    writeln("The chest clicks open!")
    found = true
    addScore(10)
    sound("C4", 150)
    sound("E4", 150)
    sound("G4", 150)
end if

And something lower and duller for a wrong guess:

QF Code example 7
else
    sound("A3", 200)
    var distance = abs(guess - secret)
    ...

Make It Yours. Add a sound somewhere else in your game, a bell for opening the door, a short tune for typing an unrecognized command, whatever fits. Keep each one to a handful of notes at most, both because it reads better, and because you now know exactly why.

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

Completed code (Chapter 12):

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


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)
            sound("C4", 150)
            sound("E4", 150)
            sound("G4", 150)
        else
            sound("A3", 200)
            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.")
        bell()
    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: everything you've written so far runs top to bottom, or loops in a predictable circle. Chapter 13 shows you a function that can call itself, a small, optional detour into recursion.


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.