The game loop is the state machine from the diagram, written as code. It steps through levels, reads input, applies lifelines, tracks safe levels, and exits through one of three doors: win, loss, or walk away.
This page builds three functions: read_input, handle_lifeline, and
game_loop.
read_input
read(0, &c, 1) reads one byte from stdin. In canonical mode — the
terminal's default — the kernel buffers input until Enter is pressed.
The player types a letter and then Enter; the program reads the letter
on the first call and consumes the newline on the second.
The function normalises the input to uppercase and returns it:
static char read_input(void)
{
char c;
char flush;
if (read(0, &c, 1) <= 0)
return ('\0');
read(0, &flush, 1); /* consume the newline that follows the letter */
return (tci_toupper(c));
}tci_toupper from c01 normalises lowercase to uppercase and returns any
other character unchanged. Both a and A become A, so the player can
answer in either case.
handle_lifeline
The lifeline handler takes the current question, the lifeline flags, and the hidden array. It applies the chosen lifeline and updates the flags:
static void handle_lifeline(question_t *q, int *flags, int hidden[4],
char choice)
{
int i;
int removed;
if (choice == '1' && *flags & 1) { /* bit 0: 50:50 still available */
removed = 0;
for (i = 0; i < 4 && removed < 2; i++) {
if (i != q->answer) {
hidden[i] = 1;
removed++;
}
}
*flags &= ~1; /* clear bit 0: 50:50 spent */
tci_printf("\n50:50 — two wrong answers removed.\n\n");
} else if (choice == '2' && *flags & 2) { /* bit 1: phone still available */
if (q->hint)
tci_printf("\nFriend says: %s\n\n", q->hint);
else
tci_printf("\nFriend says: The answer is %c.\n\n",
"ABCD"[q->answer]);
*flags &= ~2; /* clear bit 1: phone spent */
} else if (choice == '3' && *flags & 4) { /* bit 2: audience still available */
display_audience(q);
*flags &= ~4; /* clear bit 2: audience spent */
} else {
tci_printf("Lifeline not available.\n");
}
}The spec showed three separate int variables — one per lifeline.
This implementation packs all three into a single int, one bit per
flag.
An int is at least 16 bits wide. We use three. Bit 0 is 50:50, bit
1 is phone, bit 2 is audience. When all three are available the value
is 7 — binary 111:
7 = 0b111
|||
||+-- bit 0: 50:50 (1 = available, 0 = spent)
|+--- bit 1: phone
+---- bit 2: audienceWhen 50:50 is spent, bit 0 clears and the value becomes 6 (0b110).
When every lifeline is gone, the value is 0.
Testing a bit — &
The bitwise AND operator compares two integers bit by bit. A result bit is 1 only when the corresponding bit is 1 in both operands:
0b111 (flags = 7, all available)
& 0b001 (mask: isolate bit 0)
-------
0b001 → non-zero: 50:50 is available
0b110 (flags = 6, 50:50 spent)
& 0b001
-------
0b000 → zero: 50:50 is not availableAn if condition treats any non-zero result as true. *flags & 1
asks "is bit 0 set?"; *flags & 2 asks "is bit 1 set?"; *flags & 4
asks "is bit 2 set?". The masks 1, 2, and 4 are powers of two — each
has exactly one bit set at a different position.
Clearing a bit — &= ~
~ is the bitwise NOT operator. It flips every bit in the value.
~1 turns 0b00...001 into 0b11...110 — all bits set except bit 0.
ANDing the flags against that mask clears bit 0 without changing the others:
0b111 (flags = 7)
& 0b110 (~1: all bits set except bit 0)
-------
0b110 (flags = 6: 50:50 marked as spent)*flags &= ~1 is shorthand for *flags = *flags & ~1. The same
pattern clears bit 1 with &= ~2 and bit 2 with &= ~4.
You have seen this before
The 6502 status register packs the carry, zero, negative, and overflow
flags into a single byte. The CPU tests individual flags with bitwise
AND and clears them with masks — CLC clears the carry flag the same
way *flags &= ~1 clears bit 0 here. The hardware and the language
use identical logic; only the syntax differs.
The kernel uses the same technique for file permissions: the nine bits
of rwxrwxrwx live in a single mode_t, tested and set with the
same operators.
Why not three separate variables?
Three separate int variables work and read clearly. A packed int
has two practical advantages here: it is one argument instead of three
when passed to handle_lifeline, and if (!lifelines) tests all
three at once. Whether to pack or separate is a judgement call — at
this scale either is fine, and the packed form is worth knowing because
you will see it everywhere.
The flags argument points to lifelines in game_loop. Passing a
pointer lets the handler update the caller's copy directly — the same
pattern as load_questions writing through *count.
game_loop
The main loop runs through the shuffled question array, one level per correct answer:
void game_loop(question_t **questions, int count)
{
int level;
int safe_level;
int lifelines;
int hidden[4];
int i;
char c;
question_t *q;
level = 0;
safe_level = -1;
lifelines = 7; /* 0b111: bits 0=50:50, 1=phone, 2=audience — all available */
while (level < LEVELS && level < count) {
q = questions[level];
for (i = 0; i < 4; i++)
hidden[i] = 0;
display_ladder(level, safe_level);
if (SAFE[level])
safe_level = level; /* bank the floor prize for this level */lifelines = 7 initialises all three bits to 1 — binary 111, one
bit per lifeline.
safe_level is updated at the start of each level, before the
question is shown. Reaching level 5 secures £1,000 immediately —
the correct behaviour per the WWTBAM rules.
The question loop reads input until the player gives a valid answer or uses all lifelines:
tci_printf("Level %d — %s\n\n", level + 1, PRIZES[level]);
display_question(q, hidden);
tci_printf("[1] 50:50 [2] Phone [3] Audience "
"[W] Walk away\n");
tci_printf("Your answer (A-D): ");
while (1) {
c = read_input();
if (c == 'A' || c == 'B' || c == 'C' || c == 'D')
break;
if ((c == '1' || c == '2' || c == '3') || c == 'W') {
if (c == 'W') {
display_walkaway(level);
free_questions(questions, count);
return;
}
handle_lifeline(q, &lifelines, hidden, c);
display_question(q, hidden);
tci_printf("Your answer (A-D): ");
}
}After a lifeline the question is re-displayed, showing which options
remain. The prompt repeats. Walk away exits immediately — the current
level is passed to display_walkaway, which computes the banked
prize as level > 0 ? PRIZES[level - 1] : "£0".
The answer check and level advance:
if (c - 'A' == q->answer) { /* 'A'-'A'=0, 'B'-'A'=1, 'C'-'A'=2, 'D'-'A'=3 */
tci_printf("\nCorrect!\n");
level++;
if (level == LEVELS) {
display_win();
break;
}
} else {
display_loss(safe_level);
break;
}
}
free_questions(questions, count);
}c - 'A' converts the letter A–D to an index 0–3, which is
compared directly to q->answer. If the player answers Q15 correctly
(level reaches LEVELS), display_win fires and the loop exits
through the break. Any wrong answer calls display_loss with the
current safe_level and breaks.
Wire game_loop into main
Update main.c to call game_loop after the question bank is loaded.
Random selection comes on the next page — for now, pass the full
loaded array:
#include "game.h"
int main(int argc, char **argv)
{
question_t **questions;
int count;
if (argc < 2) {
tci_printf("usage: %s <questions.txt>\n", argv[0]);
return (1);
}
questions = load_questions(argv[1], &count);
if (!questions)
return (1);
if (count < LEVELS) {
tci_printf("error: need at least %d questions\n", LEVELS);
free_questions(questions, count);
return (1);
}
game_loop(questions, count);
return (0);
}Add the 15-question file from page 06 and run:
make re
./game questions.txtThe game should be playable end to end. Random selection and the full question bank come on the next page.