DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

A centipede is just an array ordered head-first — every segment copies the cell ahead, and a bolt splices the array in two

Centipede looks like the arcade cabinet that surely needs an enemy AI and a physics engine — a creature snaking down a mushroom field, a blaster returning fire, a spider diving through your zone. It needs neither. The whole creature is a plain JavaScript array of body-segment cells ordered head-first, the field is a grid of mushroom hit-points, and the serpentine descent falls out of one shift of that array. About 250 lines of vanilla JavaScript on one canvas. Here's how it holds together.

The whole creature is one array

There's no world model. Mushrooms are a 2-D array of hit-points (0 = empty, 4 = fresh), the player is a rectangle near the bottom, and the centipede is nothing but an array of {cx, cy} cells — segment 0 is the head, the last item is the tail.

let centipede = {
  segments: [ {cx:0,cy:0}, {cx:-1,cy:0}, {cx:-2,cy:0} ], // head first
  dir: 1, vdir: 1        // marching right, descending down
};
Enter fullscreen mode Exit fullscreen mode

The chain follows its head — one array shift

This is the whole trick to the snaking, and it's exactly how Snake works. Once a tick the head picks a new cell; then, walking tail-to-head, every segment copies the cell of the one in front of it. Each body piece is always one tick behind its predecessor, so the chain flows along its own head's path. The wiggle isn't animated — it falls out of one loop.

for (let i = segs.length - 1; i > 0; i--){
  segs[i].cx = segs[i-1].cx;   // copy the one ahead
  segs[i].cy = segs[i-1].cy;
}
head.cx = nx; head.cy = ny;    // head leads
Enter fullscreen mode Exit fullscreen mode

Descend on the edge — turn on a wall or a mushroom

The head tries to march one cell sideways. If that cell is off the board or holds a mushroom, it's blocked: drop one row, flip the horizontal direction, and next tick sweep back the other way. Bounce the vertical direction off the top and floor and the creature zig-zags down the whole field, around every mushroom, with no path-finding at all.

const blocked = nx < 0 || nx >= COLS || mush[h.cy][nx] > 0;
if (blocked){ ny = h.cy + c.vdir; c.dir = -c.dir; nx = h.cx; }  // drop + reverse
Enter fullscreen mode Exit fullscreen mode

The split is one array slice

Bolts rise straight up the grid; a hit is a cheap box check because everything lives on the same grid. When a bolt lands on segment si, we cut the array there — everything before stays the original centipede keeping its head, everything after becomes a brand-new centipede with a fresh head. One creature becomes two, and a mushroom sprouts where the segment died, which is why the field thickens and the swarm multiplies the more you fire.

const front = segs.slice(0, si);         // keeps this head
const back  = segs.slice(si + 1);        // grows a new head
centipedes.splice(ci, 1, front, back);   // one becomes two
Enter fullscreen mode Exit fullscreen mode

Mushrooms do two jobs with no extra code

Mushrooms are just numbers in the grid — four hit-points, chipped one per bolt, gone at zero. The same cell answers two questions: it counts as "blocked" when the centipede tries to enter (so the creature turns around it), and it stops a bolt. That's the entire reason the field is interesting, and it costs nothing beyond the grid you already have.

Waves, and the loop that ties it together

The centipede marches on a fixed tick — an accumulator sums real seconds and fires a step whenever it crosses the interval, so it moves the same on any screen while bolts glide smoothly between ticks. Clear every segment and the next wave loads a longer, faster centipede over the mushrooms you left behind. Everything — the player, the bolts, the tick, every collision, the draw — runs inside one requestAnimationFrame loop scaled by dt.

function update(dt){
  updatePlayer(dt); updateBullets(dt);
  for (const c of centipedes){                 // fixed-tick march
    c.acc += dt;
    while (c.acc >= stepInterval){ c.acc -= stepInterval; stepCentipede(c); }
  }
  if (centipedes.length === 0) nextWave();     // field cleared -> next wave
}
Enter fullscreen mode Exit fullscreen mode

Add a diving spider worth more the closer you shoot it, three lives decided by an overlap test, and that's the game. No engine — just an array that snakes down a grid of mushroom hit-points, redrawn sixty times a second.

Slide with ←/→, fire up with Space, and split the swarm apart before it reaches you:
https://dev48v.infy.uk/game/day49-centipede.html

Top comments (0)