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.
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:
sound(440, 300) // A4, for 300 millisecondsOr, often easier to reason about, a note name:
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.
sound("C4", 150)
sound("E4", 150)
sound("G4", 150) // a bright, rising little chimeThe 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.
for i = 1 to 5
bell()
end forThat'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:
if guess == secret then
writeln("The chest clicks open!")
found = true
addScore(10)
sound("C4", 150)
sound("E4", 150)
sound("G4", 150)
end ifAnd something lower and duller for a wrong guess:
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.
Start this chapter
Chapter 12 starting checkpoint.
Continue from here
Chapter 12 completed checkpoint.
Starting code: the Chapter 11 checkpoint.
Completed code (Chapter 12):
// 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 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.