← Back to Blog Apr 23, 2026 · 05:00 PM · 5 min read

Snake Wall Wrapping: Appear on the Other Side

In Part 3, we made the game speed up as the snake grows. Now let us add a friendlier option — wall wrapping. Instead of dying when hitting a wall, the snake passes through and appears on the opposite side. This is the foundation of a "kids mode" where the only way to die is hitting yourself.

By Wuidi · Tutorial · Part 4

What You Will Learn

Step 1: The Concept — Modulo Arithmetic

Wrapping is simple math. When the snake moves past the right edge (x = 20 on a 20-wide grid), we want it to appear at x = 0. When it moves past the left edge (x = -1), we want it at x = 19. The modulo operator handles both cases.

// Wrapping a value within a range [0, max)
function wrap(value, max) {
  return ((value % max) + max) % max;
}

// Examples:
wrap(20, 20);  // 0  — went past right edge
wrap(-1, 20);  // 19 — went past left edge
wrap(5, 20);   // 5  — no wrapping needed

The double-modulo pattern ((value % max) + max) % max is necessary because JavaScript's % operator returns negative values for negative inputs. -1 % 20 gives -1, not 19. Adding max and taking modulo again fixes this.

Tip: This wrapping formula works for any grid-based game — Pac-Man's tunnel, Asteroids' screen edges, and tile-based world maps all use the same principle.

Step 2: Replace Wall Collision with Wrapping

In the original game, checkCollision() returns true when the head goes out of bounds. With wrapping enabled, we remove the wall check entirely — the snake can never go out of bounds because we wrap its position before checking collisions.

let wallWrap = true; // toggle this to switch modes

function checkCollision() {
  const head = snake[0];

  // Wall collision only if wrapping is OFF
  if (!wallWrap) {
    if (head.x < 0 || head.x >= GRID_SIZE ||
        head.y < 0 || head.y >= GRID_SIZE) {
      return true;
    }
  }

  // Self collision (always active)
  for (let i = 1; i < snake.length; i++) {
    if (head.x === snake[i].x &&
        head.y === snake[i].y) {
      return true;
    }
  }

  return false;
}

Self-collision still ends the game. Wall wrapping removes one danger but the snake can still die by running into its own body. This keeps the game challenging even in kids mode.

Step 3: Wrapping in moveSnake()

Apply the wrap function to the new head position right after calculating it. This ensures the head is always within grid bounds before we add it to the snake array.

function moveSnake() {
  direction = { ...nextDirection };

  const head = snake[0];
  let newHead = {
    x: head.x + direction.x,
    y: head.y + direction.y
  };

  // Apply wrapping if enabled
  if (wallWrap) {
    newHead.x = wrap(newHead.x, GRID_SIZE);
    newHead.y = wrap(newHead.y, GRID_SIZE);
  }

  snake.unshift(newHead);
  snake.pop();
}
Key insight: Wrapping happens before the new head is added to the array. This means collision detection always sees valid grid coordinates — no special cases needed for out-of-bounds positions.

Step 4: Visual Wrap Effect

When the snake wraps, add a brief visual flash at the entry and exit points. This helps the player track the snake as it teleports across the screen.

let wrapFlashes = [];

function addWrapFlash(x, y) {
  wrapFlashes.push({
    x: x * CELL_SIZE + CELL_SIZE / 2,
    y: y * CELL_SIZE + CELL_SIZE / 2,
    radius: CELL_SIZE,
    life: 10
  });
}

function detectWrap(oldHead, newHead) {
  // If the head jumped more than 1 cell, it wrapped
  if (Math.abs(oldHead.x - newHead.x) > 1 ||
      Math.abs(oldHead.y - newHead.y) > 1) {
    addWrapFlash(oldHead.x, oldHead.y);  // exit point
    addWrapFlash(newHead.x, newHead.y);  // entry point
  }
}

function drawWrapFlashes() {
  for (let i = wrapFlashes.length - 1; i >= 0; i--) {
    const f = wrapFlashes[i];
    ctx.save();
    ctx.globalAlpha = f.life / 10;
    ctx.strokeStyle = '#4cff72';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(f.x, f.y, f.radius * (1 - f.life / 10), 0, Math.PI * 2);
    ctx.stroke();
    ctx.restore();

    f.life--;
    if (f.life <= 0) wrapFlashes.splice(i, 1);
  }
}

Call detectWrap() in moveSnake() right after calculating the wrapped position, passing the old head and new head. The flash is a green expanding ring that fades out over 10 frames.

Step 5: Toggle Between Modes

Let the player choose between classic (wall death) and wrap mode. A simple key toggle or a menu option works well.

// Toggle with W key before game starts
document.addEventListener('keydown', (e) => {
  if (e.key === 'w' || e.key === 'W') {
    if (gameOver || !gameStarted) {
      wallWrap = !wallWrap;
      drawModeIndicator();
    }
  }
});

function drawModeIndicator() {
  const mode = wallWrap ? 'WRAP' : 'CLASSIC';
  const color = wallWrap ? '#4cff72' : '#ff4040';

  ctx.fillStyle = color;
  ctx.font = '10px "Segoe UI", sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText('Mode: ' + mode, WIDTH / 2, HEIGHT - 6);
}
Tip: Only allow mode switching before the game starts or after game over. Switching mid-game would be confusing and could let the player cheat by toggling wrap right before hitting a wall.

Step 6: Kids Mode

Kids mode combines wall wrapping with other adjustments to make the game friendlier for younger players: slower speed, a larger grid with bigger cells, and brighter colors.

const KIDS_MODE = {
  gridSize: 14,       // fewer cells = less precision needed
  cellSize: 28,       // bigger cells = easier to see
  speed: 200,         // slower = more reaction time
  wallWrap: true,     // no wall death
  colors: {
    snake: '#4cff72',
    food: '#ff6b6b',
    bg: '#1a1a3e'
  }
};

const NORMAL_MODE = {
  gridSize: 20,
  cellSize: 20,
  speed: 150,
  wallWrap: false,
  colors: {
    snake: '#2ad060',
    food: '#ff4040',
    bg: '#0f0f23'
  }
};

function applyMode(mode) {
  GRID_SIZE = mode.gridSize;
  CELL_SIZE = mode.cellSize;
  SPEED = mode.speed;
  wallWrap = mode.wallWrap;

  canvas.width = GRID_SIZE * CELL_SIZE;
  canvas.height = GRID_SIZE * CELL_SIZE;
}

The 14×14 grid with 28px cells gives kids a bigger target area. Combined with 200ms speed and wall wrapping, the game becomes approachable for players as young as 4-5 years old. The only way to lose is running into your own tail, which teaches spatial awareness without the frustration of wall deaths.

Tip: Kids mode is not just about making the game easier — it is about removing frustration. Wall deaths feel unfair to young players because the boundary is invisible. Wrapping feels magical instead.

What's Next?

Wall wrapping opens up the game for younger players and adds a fun twist for experienced ones. In the next part, we add mobile controls — swipe gestures that let players control the snake on touchscreens.

Continue to Part 5: Mobile Controls →

More Articles

📱
Tutorial Snake Mobile Controls: Swipe to Change Direction Tutorial · Apr 23, 2026 · 06:00 PM
🔊
Tutorial Snake Sound Effects: Eat, Die, and Level Up Sounds Tutorial · Apr 23, 2026 · 07:00 PM
Tutorial Snake Speed Increase: Get Faster as You Grow Tutorial · Apr 23, 2026 · 04:00 PM
← Back to Blog