Classic snake game. Grow your snake using Queue data structure.
The snake's body is implemented as a Queue. When the snake moves, we enqueue a new head at the front and dequeue the tail from the back. When food is eaten, we skip the dequeue โ making the snake grow. This gives us O(1) move operations regardless of snake length.
interface Point { x: number; y: number; }
class SnakeQueue {
private body: Point[] = [];
enqueue(point: Point): void {
this.body.unshift(point);
}
dequeue(): Point | undefined {
return this.body.pop();
}
get head(): Point { return this.body[0]; }
get length(): number { return this.body.length; }
includes(point: Point): boolean {
return this.body.some(p => p.x === point.x && p.y === point.y);
}
}Wall collision is a simple boundary check. Self-collision uses Array.some() to check if the next head position already exists in the body array โ O(n) time.
function isWallCollision(point: Point): boolean {
return point.x < 0 || point.x >= GRID_SIZE ||
point.y < 0 || point.y >= GRID_SIZE;
}
function isSelfCollision(snake: SnakeQueue, next: Point): boolean {
return snake.some(seg =>
seg.x === next.x && seg.y === next.y
);
}The game loop runs on a fixed interval (tick rate). Each tick: calculate next position โ check collisions โ update snake โ check food โ re-render canvas. React state is kept minimal; the canvas is updated imperatively via useRef for performance.
const intervalRef = useRef<NodeJS.Timeout>();
useEffect(() => {
if (gameStatus !== 'playing') return;
intervalRef.current = setInterval(tick, speed);
return () => clearInterval(intervalRef.current);
}, [gameStatus, speed]);