Four complete programs: random walk, projectile motion, Game of Life, and the SIR epidemic model.
What is simulation?
A simulation reproduces the rules of a real-world system in a program and advances time step by step, letting you watch the state evolve. It lets you study behavior that is hard to solve analytically.
Stochastic
Random walk
A particle picks a direction at random each step. Models diffusion.
Physics
Projectile motion
Apply gravity and air drag at small time intervals (Euler integration).
Discrete
Game of Life
A 2D cellular automaton with simple birth/death rules.
Population
SIR epidemic
Track how susceptible, infected, and recovered people change over time.
Three common steps
Initialize: set up the state (positions, velocities, grid, populations, ...).
Update rule: a single function that advances the state by one step.
Loop: call the update a fixed number of times (or until some condition) and print intermediate values.
Try K=0: you get the ideal parabola with range v₀²·sin(2θ)/g = 91.8 m. Vary K to see how drag shortens the throw.
Plot the result
Save the output as CSV and plot it with Excel / gnuplot / Python.
$ ./projectile > traj.csv
$ gnuplot -e "set datafile separator ','; plot 'traj.csv' using 2:3 with lines"
Conway's Game of Life
Cells on a 2D grid are either alive or dead. The next generation is computed from the count of live neighbors. Complex patterns (like the glider) emerge from very simple rules.
Rules
Live cell: survives with 2 or 3 live neighbors, dies otherwise.
Dead cell: becomes alive with exactly 3 live neighbors.
#include<stdio.h>#include<string.h>#include<unistd.h>// usleep (POSIX)#define W 30#define H 15#define GENS 40int grid[H][W];
int next_grid[H][W];
intcount_neighbors(int y, int x) {
int c = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dy == 0 && dx == 0) continue;
int ny = y + dy, nx = x + dx;
if (ny < 0 || ny >= H || nx < 0 || nx >= W) continue;
c += grid[ny][nx];
}
}
return c;
}
voidstep(void) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
int n = count_neighbors(y, x);
if (grid[y][x]) next_grid[y][x] = (n == 2 || n == 3);
else next_grid[y][x] = (n == 3);
}
}
memcpy(grid, next_grid, sizeof(grid));
}
voidprint_grid(int gen) {
printf("\033[H\033[2J"); // clear screenprintf("gen: %d\n", gen);
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
putchar(grid[y][x] ? '#' : '.');
}
putchar('\n');
}
}
intmain(void) {
// place a glider
grid[1][2] = grid[2][3] = 1;
grid[3][1] = grid[3][2] = grid[3][3] = 1;
for (int g = 0; g < GENS; g++) {
print_grid(g);
usleep(150000); // 0.15 s pausestep();
}
return0;
}
Why two grids? The next state of each cell must be computed from the current neighborhood. If you write into the same grid while reading it, already-updated neighbors corrupt the rule. Write to next_grid, then memcpy in one shot.
SIR epidemic model
The simplest mathematical model of disease spread splits the population into three groups:
S
Susceptible
Not yet infected.
I
Infected
Currently infectious.
R
Recovered
Recovered (and immune).
Discrete-time update
ΔS = -β · S · I / N
ΔI = +β · S · I / N - γ · I
ΔR = +γ · I
β: infection rate, γ: recovery rate (1/duration), N: total population.
#include<stdio.h>#define DAYS 100intmain(void) {
double N = 1000.0; // total populationdouble S = 999.0; // susceptibledouble I = 1.0; // initial infecteddouble R = 0.0; // recovereddouble beta = 0.3; // infection ratedouble gamma = 0.1; // recovery rate (10-day illness)printf("day,S,I,R\n");
for (int day = 0; day <= DAYS; day++) {
printf("%d,%.1f,%.1f,%.1f\n", day, S, I, R);
double dS = -beta * S * I / N;
double dI = beta * S * I / N - gamma * I;
double dR = gamma * I;
S += dS;
I += dI;
R += dR;
}
return0;
}
Basic reproduction number R₀ = β/γ. Here 0.3/0.1 = 3 — one infected person spreads to three others on average. Lowering β (less contact) or raising γ (faster recovery) flattens the curve.
Find the peak
// track the maximum of I inside the loopdouble peak = 0;
int peak_day = 0;
for (int day = 0; day <= DAYS; day++) {
if (I > peak) { peak = I; peak_day = day; }
// ... regular update
}
printf("peak on day %d: %.0f people\n", peak_day, peak);
Challenges
Challenge 1: 2D random walk
Pick one of 4 directions (N/S/E/W) each step. Run 10,000 trials and verify the mean distance still scales like √N.
Challenge 2: Optimal launch angle
Run the projectile simulation for angles 5°–85° in 5° steps and find the angle that maximizes range. With drag the optimum is below 45°.
Challenge 3: Random initial grid
Initialize the Life grid randomly with 50% live cells. Report how many cells are alive after 50 generations. See the random-number lesson.
Challenge 4: SIR with vaccination
Add a term that moves v people per day directly from S to R (vaccination). Compare v=10 to v=0 — how does the peak change?
Challenge 5: Lotka-Volterra (predator-prey)
Rabbits (R) and foxes (F):
dR/dt = α R - β R F, dF/dt = δ R F - γ F
Start with R=40, F=9, α=0.1, β=0.02, γ=0.4, δ=0.02 and watch the oscillation.