How to Build a Classic Snake Game with MVC and Efficient Data Structures
This article explains how to implement the classic Snake game using the MVC pattern, detailing the model's grid representation, chain‑list versus array for the snake body, random food placement algorithms, view rendering optimizations with PIXI, and control APIs for game interaction.
MVC Architecture
The game follows the Model‑View‑Control pattern: the Model stores the game state, the View renders the state, and the Control processes user input and updates the Model.
Model
The playing field is an m × n grid (called zone) stored as a one‑dimensional array. Each cell holds 0 (empty), F (food) or S (snake). The snake itself is represented by a linked list of cell indices; food is a single index pointing to an empty cell.
Snake movement
Moving the snake consists of inserting a new head node and removing the tail node. A naïve array implementation uses unshift and pop:
function move(next) {
snake.pop() && snake.unshift(next);
}Because unshift is O(n), a true linked list with O(1) insert/delete is preferred. The author provides a Chain class (see https://github.com/leeenx/es6-utils#chain) that implements unshift and pop in constant time.
let snake = new Chain();Random food placement
Three strategies are discussed:
Gambling algorithm : pick a random index; if occupied, retry. Expected O(1) but may loop indefinitely.
Safe linear scan : count empty cells, pick the rnd ‑th empty cell. Average complexity O(n/2).
Bet algorithm : pick a random index; if occupied return -1, then fall back to the safe scan. Average complexity O(n/4).
Safe linear‑scan implementation:
function feed() {
let len = zone.length - snake.length;
if (len === 0) return;
let index = 0, count = 0;
let rnd = (Math.random() * len >> 0) + 1;
while (count !== rnd) {
if (zone[index++] === 0) ++count;
}
return index - 1; // index of the new food
}Bet algorithm (fallback to safe scan when the random pick fails):
function bet() {
let rnd = Math.random() * zone.length >> 0;
return zone[rnd] === 0 ? rnd : -1;
}
function feed() {
let food = bet();
if (food === -1) food = feed(); // safe scan as fallback
return food;
}View
The View uses the PIXI rendering engine. To keep rendering cheap, only the changed head or tail nodes are updated (incremental rendering), reducing the update cost to O(1) – O(2).
// Incremental update example
let modelSnake = model.snake, viewSnake = view.snake;
while (viewSnake.length <= modelSnake.length) {
let headA = modelSnake.next();
if (headA.data === viewSnake.head.data) break;
if (modelSnake.HEAD === headA.index) {
viewSnake.unshift(headA.data);
} else {
viewSnake.insertAfter(0, headA.data);
}
}Control
Control exposes game APIs (init, start, pause, turn, etc.) and emits events (countdown, eat, before‑eat, gameover). Example of subscribing to an event:
snake.event.on("countdown", time => console.log("Remaining time:", time));During each tick Control determines the next cell index ( next) and dispatches based on its content:
// B represents a wall collision
let cell = next === -1 ? B : zone[next];
switch (cell) {
case F: eat(); break; // food
case S: collision(S); break; // self‑collision
case B: collision(B); break; // wall
default: move(next); break; // normal move
}Source code
The complete implementation is available at https://github.com/leeenx/snake.
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.
