How a 1 KB JavaScript Demo Creates a 3D Fire‑Extinguishing Game

This article dissects a 1 KB js1k competition entry that builds a 3‑D forest‑fire game using only built‑in canvas variables, explaining the competition rules, the demo’s gameplay, the compact object‑creation logic, the clever key‑state handling, and the full event‑loop rendering pipeline.

Aotu Lab
Aotu Lab
Aotu Lab
How a 1 KB JavaScript Demo Creates a 3D Fire‑Extinguishing Game

Background – the js1k competition

The annual js1k contest challenges developers to create a JavaScript demo of no more than 1024 bytes that runs in modern browsers, forbids external resources, and encourages minification and clever hacks.

For the 2016 “Let’s get eleMental!” theme, one of the top entries was Firewatch , a 3‑D game where the player must extinguish a spreading forest fire using a water gun.

Built‑in environment variables

The competition runtime provides a few pre‑initialized globals to save bytes: window.a – a <canvas> element window.b

document.body
window.c

– the 2‑D/3‑D rendering context of

a
window.d

– the document object

Demo gameplay

Using the arrow keys and the space bar, the player moves around a forest. After the game starts a firework explodes, igniting a tree. Pressing space fires a water stream that can put out the fire; the fire can spread to nearby trees if not stopped.

Source‑code analysis

Object creation

The first part of the code builds the scene by populating an entities array with trees, leaves, fruits and other objects. The author heavily compresses variable names to single letters.

for (entities = [playerA = 256]; s = playerX = playerZ = playerA--; ) { // add trees
  // create trunk
  entities.push({c:[12,60,30], x: X = Math.sqrt(playerA)*12*Math.cos(playerA)+Math.random()*12, ...});
  // create leaf
  entities.push({c:[150,60,i*2], x: X+f*Math.cos(e=Math.random()*7), ...});
  // create fallen fruit
  entities.push({c:[50,60,i*2], x: X+f*Math.cos(e=Math.random()*7), ...});
}

User interaction

Key handling is done by assigning two functions directly to window.onkeydown and window.onkeyup. Each function stores a single character (the sixth character of the event type string) in a burn array indexed by keyCode‑32. This compact representation lets the main loop check key states with simple truthy/falsy tests.

onkeydown = onkeyup = function(e,f){
  burn[e.keyCode-32] = e.type[5];
};

Event loop

The second half of the script runs inside a setInterval (≈33 ms per frame). Each iteration performs:

Move the player based on the burn array (arrow keys).

If the space key is pressed, push a water‑stream object into entities.

Update all entities by calling their p (process) function, which decrements a lifetime h and handles fire spreading, water extinguishing, and smoke generation.

Render the sky, forest floor, and all visible entities using fillStyle with HSLA colors and fillRect. Objects farther than a distance threshold are culled; smoke and fireworks are always drawn.

Remove dead entities (where h <= 0).

// move player
playerA += (!!burn[7] - !!burn[5]) / 20;
playerX += (e = !!burn[6] - !!burn[8]) * Math.sin(playerA);
playerZ += e * Math.cos(playerA);

// discharge water when space is held
burn[0] && entities.push({c:[200,60,-5], x:playerX+12*Math.cos(playerA), ...});

// update entities
entities.some(function(f){ f.p && f.p(f); });

// draw sky
for(i=30;i--;){ c.fillStyle='hsla('+[160,60+'%',50+i+'%',1]+')'; c.fillRect(0,i*4,320,4); }

// sort by distance and draw each entity
entities.some(function(f){
  f.Z = (f.x-playerX)*Math.sin(playerA)+(f.z-playerZ)*Math.cos(playerA);
});
entities.sort(function(e,f){ return f.Z-e.Z; });
entities.some(function(f){
  if(!f.v && (f.Z>160||f.Z<8)) return;
  if(Math.abs((f.x-playerX)*Math.cos(playerA)*160/f.Z - (f.z-playerZ)*Math.sin(playerA)*160/f.Z) < 160){
    c.fillStyle='hsla('+[f.c[0],f.c[1]+'%',f.Z/6-f.c[2]+46+'%',f.S?1:0.8]+')';
    c.fillRect(160+e-f.s*160/f.Z/2,120-f.y*160/f.Z-(f.S||f.s)*160/f.Z/2,f.s*160/f.Z,(f.S||f.s)*160/f.Z/2);
  }
});

// filter dead entities
entities = entities.filter(function(e){ return e.h>0; });

Rendering tricks

The author uses HSLA strings inside a loop to draw a gradient sky and forest floor, and leverages the some method instead of forEach to save a few characters. Object lifetimes are encoded in the h property, which decrements each frame; a value of 30 corresponds to roughly one second.

Conclusion

Despite its tiny size, the demo contains a full game loop, physics, collision detection, particle effects, and a compact input system. It demonstrates how aggressive minification, clever reuse of variables (e.g., using burn both as a function and a key‑state array), and the HTML5 canvas API can produce surprisingly rich interactive experiences within a 1 KB budget.

References

js1k official site: http://js1k.com

js1k 2016 Elemental rules: http://js1k.com/2016-elemental/rules

Firewatch demo page: http://js1k.com/2016-elemental/demo/2512

Firewatch source details: http://js1k.com/2016-elemental/details/2512

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptCanvascode golf3Djs1k
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.