A terminal-based recreation of the legendary PSX DOOM fire effect written in C.
This program reproduces the exact algorithm used in the PlayStation port of DOOM (1995) to animate flames on the main menu — implemented in less than 100 lines of C and rendered entirely with ANSI 256-color escape codes.
The animation is fully real-time and continuously regenerates the fire pattern, frame after frame, with no two frames ever identical.
This project focuses on learning:
- the classic PSX DOOM fire algorithm
- cellular-automaton style propagation
- random number based animation
- ANSI 256-color terminal rendering
- frame buffering
- real-time animation in C
- Authentic PSX DOOM fire algorithm
- 16-step color palette (black -> red -> orange -> yellow -> white)
- Real-time animation
- ANSI 256-color rendering
- Pure terminal output - no graphics library
- Written entirely in C
- Standard libraries only
The fire is stored as a 2D grid of intensity values. Every cell at row y reads its new value from a randomly chosen neighbor on row y + 1 (directly below) and subtracts a small random decay.
The bottom row is hard-coded to maximum intensity — that is the heat source.
For every frame:
- Walk from the bottom row upward
- For each cell, pick a random neighbor below (left, center, or right)
- Copy that neighbor's value, subtract a small random decay
- Write the result into the cell
- Map the cell's intensity to a color from the fire palette
- Print the colored cell using ANSI 256-color escape codes
That's it. The whole effect is just "copy from below, lose a tiny bit of heat" repeated thousands of times per frame.
A simple 2D array of intensities, one byte per cell:
unsigned char fire[WIDTH * HEIGHT];
Values range from 0 (no fire) to PALETTE_SIZE - 1 (white hot).
The bottom row is permanently set to max:
for (int x = 0; x < WIDTH; x++)
fire[(HEIGHT - 1) * WIDTH + x] = PALETTE_SIZE - 1;
This row is never modified after this — it constantly feeds heat into the cells above.
The core trick. Walk from the bottom up. For each cell, look at a random neighbor on the row below and steal its heat, minus a tiny random decay:
int drift = (rand() % 3) - 1; // -1, 0, or +1
int sx = clamp(x + drift, 0, WIDTH - 1);
int src = fire[y * WIDTH + sx];
int decay = rand() % 3; // 0, 1, or 2
int val = max(0, src - decay);
fire[(y - 1) * WIDTH + x] = val;
- The
driftlets the flame curl left or right - The
decaylets the flame cool as it rises
Together they produce the flickering, lapping motion of fire.
A handcrafted ramp of ANSI 256-color codes goes from black through deep red, bright red, orange, yellow, and finally white:
static const int palette[] = {
16, 52, 88, 124, 160, 196, 202, 208,
214, 220, 226, 227, 228, 229, 230, 231
};
Each intensity value is just an index into this array.
Each cell renders as one space character with a colored background:
printf("\x1b[48;5;%dm ", palette[v]);
\x1b[48;5;N msets the background to ANSI 256-color N- the space character fills the cell
At the end of each row, the color is reset:
printf("\x1b[0m\n");
Each frame:
- Move the cursor home:
printf("\x1b[H"); - Print every cell row by row
- Sleep briefly to control framerate
This redraws frames in-place to create animation.
Compile using:
gcc ember.c -o ember
Or just:
make
No math library needed — the algorithm is pure integer arithmetic.
./ember
Press Ctrl+C to exit.
For best results, run in a terminal that supports 256 colors (iTerm2, modern macOS Terminal, GNOME Terminal, Windows Terminal, kitty, alacritty — basically any modern terminal).
Edit the constants at the top of ember.c:
#define WIDTH 80
#define HEIGHT 28
- Bigger grid = larger fire
- The grid must fit in your terminal window
- The palette can be extended or rebalanced — try inverting it for cool-color "ice fire"
(Actual output is animated and colored — black at top, red in the middle, yellow-white at the bottom.)
░ ░
░ ░ ▒▒▒░░ ░
░░░▒▒▓▓▓▓▓▒▒▒░░
░▒▒▓▓████████▓▓▓▒▒░░
░▒▓▓████████████████▓▓▒░
░▒▓████████████████████████▓
▒▓████████████████████████████████
The bottom rows are pure white-hot, fading through yellow, orange, red, and finally black as the heat dissipates.
- The PSX DOOM fire algorithm
- 2D grid propagation
- Cellular automata
- ANSI 256-color rendering
- Random number based animation
- Frame buffering
- Real-time animation
- Terminal graphics
Standard C libraries only:
stdio.hstdlib.hstring.hunistd.htime.h
No graphics engine required. No math library required.
This is the algorithm Fabien Sanglard reverse-engineered from the 1995 PlayStation port of DOOM, published in his famous 2019 blog post "How DOOM fire was done".
It is one of the most striking examples of how a handful of lines of code, plus the right palette, can produce something that genuinely looks alive.