About this game
Balloon Blaster is a fast arcade game: balloons drift down from the top of the screen, and you move a shooter along the bottom, firing upwards to pop them before they get past you. Small balloons are worth the most, combos multiply your score, and power-ups fall from above. Every balloon that gets past costs a life.
The whole game is one HTML file of about 1,900 lines, almost all of it JavaScript drawing on a canvas. The sections below explain the rules and the scoring exactly, and then open up the code to show how a browser game like this actually works.
| Action | Keyboard | Touch screen |
|---|---|---|
| Move | β β or A D | Hold the left or right half of the screen |
| Shoot | Space (or click) | Tap |
| Change shooter speed | β β | - |
| Pause | P or Esc | - |
| Play again after game over | Space | Tap the button |
Choose Easy, Medium or Hard on the start screen. You start with 5 lives on Easy, 3 on Medium and 2 on Hard. A balloon that reaches the bottom costs one life - unless a Shield catches it, or you are in the short safe period (about 1.5 seconds) after losing a life.
Each balloon has a base value: small 3, medium 2, large 1 - smaller is harder to hit, so it pays more. The base is multiplied by the difficulty (Γ1.0 Easy, Γ1.5 Medium, Γ2.5 Hard) and rounded. A combo is a run of pops with no balloon escaping: from 5 in a row the points are Γ1.5, and from 10 in a row Γ2.
| Difficulty | Small (combo 1-4 / 5-9 / 10+) | Medium | Large |
|---|---|---|---|
| Easy | 3 / 5 / 6 | 2 / 3 / 4 | 1 / 2 / 2 |
| Medium | 5 / 8 / 10 | 3 / 5 / 6 | 2 / 3 / 4 |
| Hard | 8 / 12 / 16 | 5 / 8 / 10 | 3 / 5 / 6 |
Double Score doubles the value before the combo bonus, so a small balloon on Medium during Double Score, in a 10-pop combo, is worth 18. Playing on several days in a row adds a small streak bonus of 1% per day, up to 7%.
Every 30 points takes you up a level, and each level makes balloons appear 40 ms sooner, down to one every 400 ms. Hard starts at 700 ms, so it reaches that floor at level 9 (240 points); Medium, starting at 1200 ms, reaches it at level 21 (600 points).
The game also watches how you are doing. It remembers whether each of your last 20 balloons was popped or got away. If you are popping more than 85% of them, it gradually brings balloons sooner, by up to 300 ms; if you are popping fewer than 40%, it gradually gives you more time, by up to 400 ms. However well you play, balloons never come faster than one every 300 ms.
Power-ups fall from the top of the screen. Move under one to catch it.
| Power-up | Effect | Chance when one drops |
|---|---|---|
| β‘ Rapid Fire | Shoot much faster, for a while | 13.6% |
| β¦ Tri-Shot | Three bullets at once, for a while | 13.6% |
| β Freeze | Balloons slow down, for a while | 13.6% |
| 2οΈβ£ Double Score | Points Γ2, for a while | 13.6% |
| π₯ Laser | Shots pass through every balloon in their path, for a while | 13.6% |
| π‘ Shield | The next escaping balloon costs no life | 13.6% |
| β€ Extra Life | +1 life | 9.1% |
| π£ Bomb | Pops every balloon on screen, and scores them all | 9.1% |
The timed power-ups last 20 seconds on Easy, 12 on Medium and 7 on Hard. Reaching score milestones also gives you an extra life.
Every real-time game runs a loop: update the world a little, draw it, repeat. The browser calls the
drawing function once per screen refresh through requestAnimationFrame, but screens
refresh at different rates - 60, 120, 144 times a second or more. If the game moved each balloon a
fixed amount per drawn frame, it would run more than twice as fast on a 144 Hz gaming monitor as on a
60 Hz laptop.
Balloon Blaster separates the two. Its physics always steps in fixed slices of 1000/60 β 16.67 ms, and each drawn frame runs however many slices of real time have passed:
// from the game's source, with its comments trimmed
const FIXED_PHYSICS_STEP = 1000 / 60; // 16.6667ms baseline
...
if (frameDelta > 100) frameDelta = 100;
physicsLag += frameDelta;
let subSteps = 8;
while (physicsLag >= FIXED_PHYSICS_STEP && subSteps > 0) {
simTime += FIXED_PHYSICS_STEP;
update(simTime);
physicsLag -= FIXED_PHYSICS_STEP;
subSteps--;
}
On a 30 Hz screen that is about 2 physics steps per drawn frame; on a 60 Hz screen, about 1; on a 144 Hz screen, about 0.42 - most frames just redraw. The cap of 100 ms and at most 8 steps stops the game trying to "catch up" several seconds at once after you switch back from another tab.
The same idea - a fixed tick for the logic, independent of everything else - is how a microcontroller runs control loops from a timer interrupt.
A bullet hits a balloon when the distance between their centres is less than the balloon's radius plus the bullet's 5 pixels. Distance needs a square root, which is slow to do thousands of times a second, so the game compares the squares instead:
const dx = bal.x - bul.x, dy = bal.y - bul.y;
if (dx * dx + dy * dy < (bal.r + 5) * (bal.r + 5)) {
Both sides are positive, so comparing squares gives the same answer. For a small balloon (radius 15)
the test is dxΒ² + dyΒ² < 400.
Each power-up has a weight - 3 for most, 2 for Extra Life and Bomb - adding up to 22. The game picks a random number below 22 and walks along the running totals to see which power-up it lands in. That is where the odds above come from: 3/22 = 13.6% and 2/22 = 9.1%.
The game is always in exactly one state - menu, playing, paused or game over - and each key or tap means something different in each. That is a state machine, the same structure used in digital hardware and firmware.
Your high score, best combo, best level and daily streak are stored with localStorage,
which keeps small values for this site in your browser. They stay between visits on the same device,
and clearing your browser's site data resets them.
More: Serpentine, a 3D snake game in the same arcade, and the Hardware Academy, where the same ideas - timing, state and events - are taught for chips and microcontrollers.