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.
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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
