thecodingidiot.com

Who Wants to Be a Game Developer?The Game Loop

The Game Loop

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: audience

When 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 available

An 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 AD 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.txt

The game should be playable end to end. Random selection and the full question bank come on the next page.