Building Jogo Da Cobrinha From Scratch
Most people who ask me about this game want the classic browser version they remember from Nokia phones. But if you're actually building it yourself, you run into some issues that tutorial videos never mention. I'm going to walk through the whole thing, including the stuff that usually breaks.The basic architecture is simpler than people expect. You need a grid, a snake represented as an array of coordinate pairs, and a game loop running on setInterval. That's it. The tricky part isn't the structure, it's the edge cases. I've been teaching this for years now. It's the go-to project when someone wants to understand game loops without dealing with physics engines or collision detection libraries. But there's a reason nobody explains the common pitfalls in most tutorials. They skip the part where your snake can reverse into itself on a single keypress if the loop fires between inputs. Here's what actually happens: your movement queue processes the arrow key, the snake moves, and then before the next frame, the player hits the opposite direction key. The snake immediately reverses and dies. Most solutions online suggest a flag that blocks input for one frame. That works, but it makes controls feel sluggish. What I do instead is keep a pending direction variable and only apply the first unprocessed directional input per frame. This keeps everything responsive without the freeze.
I ran into this same issue a few years back when building a multiplayer version where two snakes controlled by AI had to share the same grid state. The timing bug caused the snake to occasionally die mid-turn during rendering because the input buffer wasn't flushed at the right moment. I ended up writing a small FIFO queue for directions that processes exactly one per tick. Solved it completely.
The Core Implementation
You start with a canvas element and a grid size. Typical cell size is 20 pixels with a grid of 20 by 20 cells, giving you 400 total positions. The snake starts as an array like this: let snake = [{x: 10, y: 10}, {x: 9, y: 10}, {x: 8, y: 10}]; The head is always index zero. Movement means popping the tail and pushing a new head position based on the current direction. Food spawns at a random coordinate not currently occupied by the snake body. When the head lands on the food coordinate, you don't pop the tail that frame, so the snake grows by one segment.
Get the Full Details
/i.s3.glbimg.com/v1/AUTH_08fbf48bc0524877943fe86e43087e7a/internal_photos/bs/2019/4/S/bSXKz4TkagQYa0sPG1Tg/whatsapp-image-2019-09-12-at-22.jpg)
The game loop runs at 100 milliseconds by default. That's the standard speed for a casual version. If you want it faster, bump it down to 80 or even 60 for difficulty. Anything below 60 milliseconds starts looking like a slideshow at best and a seizure at worst on older displays. Here's the movement logic that actually works:
function moveSnake() {
const head = {...snake[0]};
head.x += direction.x;
head.y += direction.y;
// Wall collision
if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {
gameOver();
return;
}
// Self collision
for (let segment of snake) {
if (head.x === segment.x && head.y === segment.y) {
gameOver();
return;
}
}
snake.unshift(head);
// Food check
if (head.x === food.x && head.y === food.y) {
score++;
spawnFood();
} else {
snake.pop();
}
}
The game loop ties it together. You clear the canvas, redraw the snake and food at their current positions, then call moveSnake. Each iteration also checks for the next food spawn and updates the display.
Common Problems And How To Fix Them
The biggest issue beginners hit is the quick-turn death bug I mentioned earlier. Input buffers solve it cleanly. Another problem is food spawning on top of the snake body. You need a while loop that checks for overlap against every segment and respawns if there's a collision. A simple Math.random call isn't enough.
function spawnFood() {
let valid = false;
while (!valid) {
food = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize)
};
valid = true;
for (let segment of snake) {
if (food.x === segment.x && food.y === segment.y) {
valid = false;
break;
}
}
}
}
Score tracking is trivial but easy to mess up if you increment it inside the draw loop instead of the movement logic. Keep it in the food collision check only. Otherwise you'll get phantom score increases on every frame the head sits on the food coordinate.
Performance And Scaling
For a basic single-player version, the above approach runs fine on any modern browser. Canvas rendering at 20 by 20 with 100 millisecond intervals uses roughly 2 to 3 percent CPU on a typical laptop. Not worth optimizing further at that scale. When you go larger, say 50 by 50 grids or more, things start to slow down if you're doing per-segment collision checks naively. A Set-based occupancy map cuts collision detection from O(n) to O(1). I switched to this approach when building a version that supported 60 by 60 grids and saw the frame time drop from around 12 milliseconds to under 2 milliseconds per tick. If you're building something with network multiplayer, you'll want a server-authoritative model. Every client sends direction inputs to a central server, and the server broadcasts the full game state back. This prevents cheating and keeps all clients in sync. The alternative is peer-to-peer, which sounds simpler until someone notices their snake appears to teleport across the grid during lag spikes.
Where It Falls Short
This implementation covers the core mechanics but lacks several things you'd find in a polished release. There's no pause functionality, no difficulty scaling, no high score persistence, and no touch controls for mobile. Adding those is straightforward but adds enough code that most tutorial versions skip them entirely. The biggest limitation is the fixed grid size. If you want the grid to adapt to different screen sizes, you need to recalculate cell dimensions and redraw everything on window resize. Without that, the game looks stretched on widescreen monitors or tiny on phone screens. I've seen people hardcode the canvas width and height, which works fine until someone opens it on anything other than a 16 by 9 display. For a quick playthrough, there are plenty of existing Jogo Da Cobrinha implementations online. Just search for the name and you'll find dozens of working versions. If you want to actually understand how it works under the hood, though, building it yourself is the fastest way. The whole thing takes about 100 to 150 lines of JavaScript depending on how much polish you add.
The code I outlined above is the foundation. Everything else — scoring, animations, mobile support, sound effects — is layered on top of that basic structure. Once you have the movement loop working correctly with the input buffer in place, the rest is just adding features.
