thecodingidiot.com

Who Wants to Be a Game Developer? GraphicalThe Spec

The Spec

Before writing a render function, enumerate the screen states. A graphical game is still a state machine. Every box in the diagram below is a render function. Every arrow is a key event.

The prize ladder is not a state — it is drawn as a permanent right panel in every state except STATE_TITLE. There is no key to show or hide it.

Read each box:

  • STATE_TITLE → needs nothing; displays the title and start prompt
  • STATE_QUESTION → needs the current question, option visibility (hidden[]), lifeline availability, active lifeline results, and the ladder panel on the right
  • STATE_CONFIRM → needs the current question and pending (the selected answer letter), plus the ladder panel
  • STATE_CORRECT / STATE_WRONG → needs the current question to reveal the correct answer, plus the ladder panel
  • STATE_WIN / STATE_GAMEOVER → needs safe_level to display the final prize, plus the ladder panel

That reading produces the struct. Nothing in game_t is invented — it is read out of the diagram.


game_state_t

The seven boxes map directly to an enum:

typedef enum e_state {
    STATE_TITLE,
    STATE_QUESTION,
    STATE_CONFIRM,
    STATE_CORRECT,
    STATE_WRONG,
    STATE_WIN,
    STATE_GAMEOVER
} game_state_t;

An enum is a set of named integer constants. STATE_TITLE is 0, STATE_QUESTION is 1, and so on. The names make the switch statement in render_frame readable; the integer values are what the CPU compares.


game_t

The struct carries everything the render functions need:

typedef struct s_game {
    SDL_Window      *win;         /* the OS window */
    SDL_Renderer    *ren;         /* the renderer that draws to it */
    SDL_Texture     *bg_studio;   /* dark navy — title, question, confirm */
    SDL_Texture     *bg_correct;  /* dark green — correct, win */
    SDL_Texture     *bg_wrong;    /* dark red — wrong, gameover */
    TTF_Font        *font;        /* Px437 IBM EGA 8×14 TrueType font */
    game_state_t     state;       /* current screen state */
    question_t     **questions;   /* shuffled question bank */
    int              count;       /* total questions loaded */
    int              level;       /* current level index 0–14 */
    int              safe_level;  /* last safe level reached, -1 if none */
    int              lifelines;   /* bits: 0=50:50, 1=phone, 2=audience */
    int              phone_active;/* 1 after Phone-a-Friend is used */
    int              hidden[4];   /* 1 = option removed by 50:50 */
    int              audience[4]; /* audience percentages, 0 until used */
    char             pending;     /* selected answer: 'A', 'B', 'C', 'D' */
} game_t;

Every field reads from the diagram:

  • win and ren are needed to draw anything at all.
  • bg_studio, bg_correct, bg_wrong — one texture per background colour; draw_background picks the right one from state.
  • font — the loaded TrueType font handle. draw_string passes it to SDL2_ttf to render each string into a texture.
  • state — the current box in the diagram.
  • questions, count — the loaded and shuffled bank; level indexes into it.
  • safe_level — the last safe threshold passed; -1 means none.
  • lifelines — a three-bit field: bit 0 is 50:50, bit 1 is phone, bit 2 is audience. The same bitfield from g01a's handle_lifeline.
  • phone_active — set to 1 when the Phone-a-Friend lifeline is used, cleared when the next question begins. Without it the hint from question N would persist to question N+1 because the lifeline bit stays cleared for the rest of the game.
  • hidden[] — which options the 50:50 removed.
  • audience[] — the percentage values generated when the audience lifeline is used. Stored in the struct so the numbers stay stable across frames.
  • pending — the answer the player has selected but not yet confirmed. Set when A/B/C/D is pressed; read by evaluate_answer when Enter confirms.

What carries over from g01a

The game's visual output is new. Its logic is not.

question_t is unchanged. PRIZES[] and SAFE[] are unchanged; they move from display.c into game.c. load_questions and free_questions are copied from g01a verbatim. The Fisher-Yates shuffle in main.c is unchanged.

The lifeline bit operations — & 1, &= ~1, & 2, &= ~2 — are the same as g01a. The safe-level logic — SAFE[level] sets safe_level — is the same as g01a.

The difference is the call site. In g01a, display_ladder(level, safe_level) wrote characters to a file descriptor. In g01b, draw_ladder(g) reads those same values from g->level and g->safe_level and draws them to the SDL2 renderer. The data has not changed; the destination has.


The spec is the contract

Every function on the following pages is a clause in this contract. render_frame switches on g->state and calls the right draw function. draw_question reads g->questions[g->level] and g->hidden. evaluate_answer reads g->pending and writes g->state. None of that is decided in the implementation — it is decided here.

Write the spec first. Then write the code. When a function disagrees with the spec, the spec wins.