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_levelto 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:
winandrenare needed to draw anything at all.bg_studio,bg_correct,bg_wrong— one texture per background colour;draw_backgroundpicks the right one fromstate.font— the loaded TrueType font handle.draw_stringpasses 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;levelindexes into it.safe_level— the last safe threshold passed;-1means none.lifelines— a three-bit field: bit 0 is 50:50, bit 1 is phone, bit 2 is audience. The same bitfield from g01a'shandle_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 byevaluate_answerwhen 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.