thecodingidiot.com

Writing CThe Guessing Game

The Guessing Game

The guessing game uses everything from the previous pages: variables, a loop, conditionals, functions, and two standard library calls you have not seen yet — rand() and srand().

Random numbers

rand() from <stdlib.h> returns a pseudo-random integer between 0 and RAND_MAX (at least 32767). Pseudo-random means the sequence is deterministic — the same seed produces the same sequence every time.

srand(seed) sets the seed. Seeding with time(NULL) (the current Unix timestamp from <time.h>) produces a different sequence each run:

srand((unsigned int)time(NULL));
n = rand() % 100 + 1;   /* 1 to 100 */

rand() % 100 gives a remainder in the range 0–99. Adding 1 shifts it to 1–100. This is not a cryptographically uniform distribution, but it is sufficient for a guessing game.


guess.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main(void)
{
    int target;
    int guess;
    int attempts;
 
    srand((unsigned int)time(NULL));
    target = rand() % 100 + 1;
    attempts = 0;
    printf("I'm thinking of a number between 1 and 100.\n");
    while (1) {
        printf("Your guess: ");
        if (scanf("%d", &guess) != 1)
            break;
        attempts++;
        if (guess < target)
            printf("Too low.\n");
        else if (guess > target)
            printf("Too high.\n");
        else {
            printf("Correct — %d attempt", attempts);
            if (attempts != 1)
                printf("s");
            printf(".\n");
            break;
        }
    }
    return (0);
}

Compile and play:

gcc -Wall -Wextra guess.c -o guess
./guess

A few things worth noting:

  • while (1) loops forever. The only exits are break — on a correct guess or an input error. This is the idiomatic C pattern for "loop until a condition inside the loop is met."
  • scanf("%d", &guess) != 1 checks whether scanf successfully read one integer. If the user types a non-number or sends EOF (Ctrl+D), scanf returns a value other than 1 and the loop exits cleanly.
  • The attempt pluralisation (attempt vs attempts) is a small detail. Small details in text output are visible to users.

Reading Doom's random number generator

You now know enough C to read a real codebase. Clone the Doom[1] source code — released by id Software in December 1997:

git clone https://github.com/id-Software/DOOM.git

Open linuxdoom-1.10/m_random.c. It is 76 lines. The core is a table of 256 bytes and two index variables:

static unsigned char rndtable[256] = {
    0,   8, 109, 220, 222, 241, 149, 107,  75, 248, 254, 140,  16,  66 ,
    /* ... 242 more values ... */
};
 
static int rndindex = 0;
static int prndindex = 0;
 
int M_Random (void)
{
    rndindex = (rndindex+1)&0xff;
    return rndtable[rndindex];
}

This is not rand(). It is a lookup table — the same 256-value sequence, every time, on every platform, in every game session. Doom records demos by saving only the player inputs, then replaying them against the same random sequence. If rand() varied between runs, the demo would desync. The determinism is intentional.

P_Random() (gameplay random — for enemy AI, projectile spread, and everything that affects game state) uses prndindex. M_Random() (misc random — for menu effects and things outside gameplay) uses rndindex separately so it does not disturb the gameplay sequence.

You wrote a guessing game that picks a number nobody can predict. id Software wrote a game that picks numbers everyone can reproduce. Both are the same idea — a sequence of integers that behaves like random — solved for different constraints.

Read the file. You understand it now.

Footnotes

  1. GitHub - id-Software/DOOM: DOOM Open Source Release · GitHub

up next

Building C

Building C