thecodingidiot.com

Who Wants to Be a Game Developer?Random Selection

Random Selection

The game plays 15 questions drawn from a larger bank. Each run should present a different set, in a different order — otherwise the player memorises the sequence rather than the answers.

The tool for this is a shuffle. Load all available questions, shuffle the array in place, then take the first 15 for the game.


srand and rand

rand() from <stdlib.h> returns a pseudo-random integer between 0 and RAND_MAX. It is deterministic: given the same seed, rand() produces the same sequence every time. To get a different sequence on each run, seed the generator with the current time at program start:

srand((unsigned int)time(NULL));

time(NULL) returns the current Unix timestamp as a time_t. It changes every second, so two runs at least one second apart will produce different sequences.

time_t may be wider than unsigned int on some platforms; the cast silences the compiler warning without affecting the seed value in practice. time is declared in <time.h>, which game.h already includes.


Fisher–Yates shuffle[1]

The standard algorithm for an unbiased in-place shuffle iterates from the last element down to the first. At each step, one element is chosen at random from the unplaced pool and locked into the current position:

Each position gets one randomly selected element from everything not yet placed. For n elements there are n! possible orderings — this algorithm produces each one with equal probability. That is what "unbiased" means; naive approaches such as picking two random indices and swapping repeatedly do not share this property.

static void shuffle(question_t **arr, int n)
{
    int          i;
    int          j;
    question_t  *tmp;
 
    for (i = n - 1; i > 0; i--) {
        j = rand() % (i + 1);      /* random index in 0..i inclusive */
        tmp    = arr[j];
        arr[j] = arr[i];           /* swap arr[j] and arr[i] */
        arr[i] = tmp;
    }
}

After shuffle(questions, count), the first 15 elements of questions are a uniformly random sample of the bank, in a uniformly random order. The elements beyond index 14 are not used and are freed with the rest when the game ends.

rand() % (i + 1) is not perfectly uniform when RAND_MAX is not a multiple of i + 1 — some indices come up slightly more often than others. For a question bank of 30–40 questions this bias is negligible.


Seeding and shuffling in main

Add srand and the shuffle call to main.c:

#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);
    }
    srand((unsigned int)time(NULL));
    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);
    }
    shuffle(questions, count);
    game_loop(questions, count);
    return (0);
}

shuffle is defined in main.c — it is only called from main, so there is no reason to put it in a header. Declare it static above main to keep it file-local:

static void shuffle(question_t **arr, int n)
{
    /* ... as above ... */
}

Verify the shuffle

Run the game twice in quick succession with the full question bank from the next page. The first question should differ between runs. If it is the same, check that srand is called before load_questions and that time(NULL) is returning different values — printf("%ld\n", (long)time(NULL)) before and after a one-second sleep confirms this.

The tester bypasses the shuffle by feeding a fixed question file with a known order. The test.sh script sets the question file and pipes scripted input, so the output is deterministic regardless of srand.

Footnotes

  1. Fisher–Yates shuffle - Wikipedia