Skip to content

Corg-Labs/ember

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

DOOM Fire Effect in C

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

Features

  • 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

How It Works

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.


Tutorial / Rendering Pipeline

1. The Fire Grid

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).


2. The Heat Source

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.


3. Propagation

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 drift lets the flame curl left or right
  • The decay lets the flame cool as it rises

Together they produce the flickering, lapping motion of fire.


4. Color Palette

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.


5. ANSI Background Coloring

Each cell renders as one space character with a colored background:

printf("\x1b[48;5;%dm ", palette[v]);
  • \x1b[48;5;N m sets 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");

6. Terminal Rendering

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.


Build

Compile using:

gcc ember.c -o ember

Or just:

make

No math library needed — the algorithm is pure integer arithmetic.


Run

./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).


Customizing

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"

Example Output

(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.


Concepts Practiced

  • 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

Dependencies

Standard C libraries only:

  • stdio.h
  • stdlib.h
  • string.h
  • unistd.h
  • time.h

No graphics engine required. No math library required.


Inspiration

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.

About

► A terminal-based recreation of the legendary PSX DOOM fire effect written in C.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors