DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Building a Bubble Shooter from scratch: hex grids and flood-fill pops

Bubble Shooter looks like a casual time-killer, but there is a surprising amount of real computer science hiding under those glossy circles. I built a playable one in vanilla JavaScript on a single canvas, and almost every mechanic turned out to be a small, teachable algorithm. Here is what actually makes it work.

The board is a honeycomb, not a grid

The first thing you notice if you try to build this on a plain square grid is that it feels wrong. Real bubble shooters use a staggered hex grid — every other row is nudged sideways by half a bubble so the bubbles nestle into the gaps of the row above. The payoff is that each bubble touches six neighbours instead of four, which matches how round things actually pack against one another.

The cost is bookkeeping. Because odd rows are shifted, the cells directly above and below a bubble have different column indices depending on whether you are on an even or an odd row. So the neighbour lookup has to branch on row parity:

function neighbors(r, c){
  const even = (r % 2) === 0;
  return (even
    ? [[r,c-1],[r,c+1],[r-1,c-1],[r-1,c],[r+1,c-1],[r+1,c]]
    : [[r,c-1],[r,c+1],[r-1,c],[r-1,c+1],[r+1,c],[r+1,c+1]]
  ).filter(inBounds);
}
Enter fullscreen mode Exit fullscreen mode

Get that parity wrong and clusters silently break in half. It is the single most bug-prone line in the whole project.

Aiming and the bank shot

The shooter sits at the bottom. Aiming is just the angle from the cannon to the mouse, clamped so you can never fire sideways or down into yourself. Firing turns that angle into a velocity vector: vx = cos(a) * speed, vy = sin(a) * speed.

Then comes my favourite bit of physics in any game, because it is so cheap. When the flying bubble reaches a side wall, you reflect it — and reflection off a vertical wall is nothing more than flipping the horizontal velocity:

if (x < R) { x = R; vx = -vx; }          // bounce off the left wall
else if (x > W - R) { x = W - R; vx = -vx; }
Enter fullscreen mode Exit fullscreen mode

That one sign flip is the entire "bank shot" that lets you curl a bubble into a corner. The vertical motion is untouched; angle in equals angle out, exactly like light off a mirror. I also advance the bubble in a few small sub-steps per frame so it can never tunnel through a wall at high speed.

Snapping to the lattice

When the bubble finally collides — with the ceiling or another bubble — it is almost never sitting on a perfect grid cell. So you snap: round its pixel position back to an approximate row and column, then scan that little neighbourhood for the closest cell that is still empty and drop it there. That keeps the board a clean hex lattice no matter how chaotic the collision was.

Popping: flood-fill number one

Now the reward loop. From the cell the bubble just landed in, run a flood-fill (breadth- or depth-first, it does not matter) that only steps onto same-colour neighbours, with a visited set to avoid loops. What comes back is the connected blob of that colour. The genre-defining rule is then just a size test: three or more, and the whole cluster clears; one or two, and the bubble simply sticks and waits.

Dropping floaters: flood-fill number two

This is the mechanic that separates a toy from a real bubble shooter, and it is the part people forget. When a cluster pops, some bubbles that were only hanging on through the popped ones are now attached to nothing — but they will not fall on their own. You have to find them.

So run a second flood-fill, seeded from the ceiling row, stepping through neighbours of any colour. Everything the fill reaches is still anchored to the top. Anything it never reaches is floating in mid-air, so it drops:

// seed BFS from every filled cell in row 0, walk any colour.
// afterwards, any filled cell NOT marked "anchored" is floating -> remove it.
Enter fullscreen mode Exit fullscreen mode

That is why a single well-placed shot can trigger an avalanche that clears half the board. Two flood-fills — one for colour, one for connectivity — do most of the heavy lifting.

What it teaches

Bubble Shooter is a neat little tour of ideas that show up everywhere: coordinate systems (hex vs square), reflection physics, snapping continuous positions onto a discrete grid, and connected-components search used twice for two different questions. The board looks playful; the algorithms underneath are the same ones you would reach for in far more serious code.

Play it, aim a bank shot off the wall, and watch a good pop cascade into a shower of falling bubbles:
https://dev48v.infy.uk/game/day33-bubble-shooter.html

Top comments (0)