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.