Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ac-unefm/snake-game/llms.txt

Use this file to discover all available pages before exploring further.

Every frame, the game clears the 400×400 canvas, draws a dark background grid, draws the pulsing food item, and draws each snake segment as a rounded rectangle with an optional glow. The food pulse animation and shadow intensities respect prefers-reduced-motion — when motion reduction is requested, values are fixed so nothing on screen blinks or oscillates.

Canvas setup

The canvas element is declared in HTML with fixed intrinsic dimensions:
<canvas id="canvas" width="400" height="400"
  role="img" aria-label="Tablero del juego Snake" tabindex="0"></canvas>
CSS scales it to fill its container while preserving the 1:1 aspect ratio:
#canvas {
  width: 100%;    /* scales visually to the container */
  height: auto;   /* maintains 1:1 ratio              */
}
The 2D context is obtained once at startup and reused for every draw call:
const ctx = canvas.getContext('2d');
Because the canvas’s coordinate space is always 400×400 logical pixels, all drawing calculations use pixel coordinates — no scaling transforms are needed in JavaScript.

draw() — main render function

draw() is called once per tick, after update() has advanced the game state:
function draw() {
  ctx.fillStyle = '#111';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  drawGrid();
  drawFood();
  snake.forEach((seg, i) => drawSegment(seg, i === 0));
}
The four operations in order:
  1. Fill background — a solid #111111 rectangle clears the previous frame.
  2. drawGrid() — overlays the faint grid lines.
  3. drawFood() — draws the pulsing food circle.
  4. snake.forEach(drawSegment) — iterates over every snake cell; index 0 is passed as isHead = true.

drawGrid()

drawGrid() renders a 20×20 cell grid over the background:
function drawGrid() {
  ctx.strokeStyle = COLORS.grid;  // #161616
  ctx.lineWidth = 0.5;
  for (let x = 0; x <= canvas.width; x += GRID_SIZE) {
    ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
  }
  for (let y = 0; y <= canvas.height; y += GRID_SIZE) {
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
  }
}
strokeStyle is set to COLORS.grid (#161616), which is only slightly lighter than the #111111 background, producing a subtle grid that is visible without being distracting. lineWidth of 0.5 keeps the lines thin and crisp on high-DPI displays.

drawFood()

The food is rendered as a circle with a glow that pulses in and out using a sine wave:
function drawFood() {
  const pulse = reducedMotion ? 0.5 : (0.5 + 0.5 * Math.sin(Date.now() / 200));
  const fr    = GRID_SIZE * 0.28 + pulse * GRID_SIZE * 0.08;
  ctx.save();
  ctx.fillStyle   = COLORS.food;   // #f87171
  ctx.shadowColor = COLORS.food;
  ctx.shadowBlur  = reducedMotion ? 8 : (8 + pulse * 6);
  ctx.beginPath();
  ctx.arc(food.x * GRID_SIZE + GRID_SIZE / 2,
          food.y * GRID_SIZE + GRID_SIZE / 2, fr, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}
Key points:
  • pulse oscillates between 0 and 1 via 0.5 + 0.5 * Math.sin(Date.now() / 200). When reducedMotion is true, pulse is fixed at 0.5 — the midpoint — so neither radius nor glow changes between frames.
  • fr (food radius) ranges from GRID_SIZE * 0.28 (≈ 5.6 px) to GRID_SIZE * 0.36 (≈ 7.2 px), making the food gently breathe.
  • shadowBlur ranges from 8 to 14, intensifying the glow in sync with the radius. When reduced motion is active it stays at 8.
  • ctx.save() / ctx.restore() isolate the shadowColor and shadowBlur state so the glow does not bleed onto the grid lines or snake segments drawn in the same frame.

drawSegment(seg, isHead)

Each snake cell is drawn as a rounded rectangle constructed manually with arcTo:
function drawSegment(seg, isHead) {
  const pad = isHead ? 1 : 2;
  const r = 4;
  const x = seg.x * GRID_SIZE + pad, y = seg.y * GRID_SIZE + pad;
  const w = GRID_SIZE - pad * 2,     h = GRID_SIZE - pad * 2;
  ctx.save();
  ctx.fillStyle = isHead ? COLORS.head : COLORS.body; // #a855f7 or #7e22ce
  if (isHead) { ctx.shadowColor = COLORS.head; ctx.shadowBlur = 12; }
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.lineTo(x + w - r, y);      ctx.arcTo(x + w, y,     x + w, y + r,     r);
  ctx.lineTo(x + w, y + h - r);  ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
  ctx.lineTo(x + r, y + h);      ctx.arcTo(x,     y + h, x,     y + h - r, r);
  ctx.lineTo(x, y + r);          ctx.arcTo(x,     y,     x + r, y,          r);
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}
Design choices:
  • Paddingpad is 1 for the head and 2 for body segments. A smaller padding on the head makes it slightly larger than the body, giving a visual cue that it is the leading cell.
  • Corner radiusr = 4 px produces a softly rounded rectangle.
  • arcTo path — each corner uses lineTo to approach the corner point, then arcTo to arc around it. The path traces all four sides clockwise: top → right → bottom → left.
  • Head glowshadowColor and shadowBlur = 12 are set only when isHead is true. Body segments have no shadow, which reduces visual noise and slightly improves rendering performance.
  • ctx.save() / ctx.restore() — used on every segment so that head shadow state does not carry forward to the next body segment.

Color palette

ElementColorVariable
Snake head#a855f7COLORS.head
Snake body#7e22ceCOLORS.body
Food#f87171COLORS.food
Grid lines#161616COLORS.grid
Background#111111CSS --color-surface

prefers-reduced-motion

The reducedMotion constant is set once at startup using:
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
When reducedMotion is true:
  • The food pulse value is fixed at 0.5 — the circle neither expands nor contracts between frames.
  • shadowBlur on the food is fixed at 8 — the glow intensity does not change.
In addition, the CSS stylesheet includes a @media (prefers-reduced-motion: reduce) block that removes transition declarations from interactive elements (skip link, session dots, buttons, footer links).

Build docs developers (and LLMs) love