โ† Back to Blog Apr 23, 2026 ยท 12:00 PM ยท 6 min read

Pac-Man Levels: Speed Up Ghosts and Change the Map Each Level

In Part 4 we added power pellets. Now our game needs progression โ€” each level should feel harder than the last. We will increase ghost speed, reduce power pellet duration, and add a level transition screen.

What Changes Per Level

Step 1: Level Configuration Table

Define a configuration table that maps each level to its difficulty parameters. This makes it easy to tune the game balance.

let level = 1;

const LEVEL_CONFIG = [
  // level 1
  { ghostSpeed: 150, frightenedTime: 8000,
    scatterTime: 7000, chaseTime: 20000 },
  // level 2
  { ghostSpeed: 130, frightenedTime: 6000,
    scatterTime: 5000, chaseTime: 20000 },
  // level 3
  { ghostSpeed: 110, frightenedTime: 4000,
    scatterTime: 4000, chaseTime: 25000 },
  // level 4
  { ghostSpeed: 100, frightenedTime: 3000,
    scatterTime: 3000, chaseTime: 25000 },
  // level 5+
  { ghostSpeed: 90, frightenedTime: 2000,
    scatterTime: 2000, chaseTime: 30000 },
];

function getConfig() {
  const idx = Math.min(level - 1, LEVEL_CONFIG.length - 1);
  return LEVEL_CONFIG[idx];
}
Tip: Cap the difficulty at level 5. After that, reuse the hardest config. This prevents the game from becoming literally impossible while still feeling challenging.

Step 2: Apply Config to Game Loop

Use the level config to control the game loop interval and frightened duration. Update these values whenever the level changes.

let gameInterval = null;

function applyLevelConfig() {
  const cfg = getConfig();
  FRIGHTENED_DURATION = cfg.frightenedTime;
  SCATTER_DURATION = cfg.scatterTime;
  CHASE_DURATION = cfg.chaseTime;

  // Restart game loop with new speed
  if (gameInterval) clearInterval(gameInterval);
  gameInterval = setInterval(gameLoop, cfg.ghostSpeed);
}

// Call applyLevelConfig() at game start and level change

Step 3: Detect Level Clear

When all dots are eaten, instead of showing "YOU WIN", advance to the next level. Reset the map but keep the score.

function checkWin() {
  if (dotsLeft <= 0) {
    level++;
    showLevelTransition();
  }
}

function resetMapForNewLevel() {
  // Restore all dots from original map
  for (let r = 0; r < originalMap.length; r++) {
    map[r] = [...originalMap[r]];
  }
  countDots();

  // Reset positions
  pacman.x = 1; pacman.y = 1; pacman.dir = 0;
  ghosts.forEach((g, i) => {
    g.x = 9 + (i % 2);
    g.y = 7 + Math.floor(i / 2);
    g.eaten = false;
  });

  frightened = false;
  ghostMode = 'scatter';
  modeTimer = 0;

  applyLevelConfig();
}

Step 4: Level Transition Screen

Show a brief overlay between levels. This gives the player a moment to breathe and builds anticipation.

let inTransition = false;

function showLevelTransition() {
  inTransition = true;

  // Pause game loop
  if (gameInterval) clearInterval(gameInterval);

  // Draw transition overlay
  function drawTransition() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.fillStyle = '#f0c040';
    ctx.font = 'bold 32px "Segoe UI", sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('Level ' + level,
      canvas.width / 2, canvas.height / 2 - 10);

    ctx.fillStyle = '#888';
    ctx.font = '14px "Segoe UI", sans-serif';
    ctx.fillText('Get ready...',
      canvas.width / 2, canvas.height / 2 + 25);
  }

  drawTransition();

  // After 2 seconds, start the new level
  setTimeout(() => {
    inTransition = false;
    resetMapForNewLevel();
  }, 2000);
}

Step 5: Display Current Level

Show the current level in the score bar so the player always knows their progress.

function drawScore() {
  const barY = map.length * TILE_SIZE;

  ctx.fillStyle = '#111';
  ctx.fillRect(0, barY, canvas.width, SCORE_BAR_HEIGHT);

  // Level (left)
  ctx.fillStyle = '#4cff72';
  ctx.font = '13px "Segoe UI", sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('Lv.' + level, 8, barY + 20);

  // Score (center)
  ctx.fillStyle = '#fff';
  ctx.textAlign = 'center';
  ctx.fillText('Score: ' + score,
    canvas.width / 2, barY + 20);

  // High score (right)
  ctx.fillStyle = '#f0c040';
  ctx.textAlign = 'right';
  ctx.fillText('Best: ' + highScore,
    canvas.width - 8, barY + 20);
}

Step 6: Fruit Bonus (Optional)

In the original Pac-Man, a fruit appears after eating a certain number of dots. Each level has a different fruit worth different points. This is a nice touch for completeness.

const FRUITS = [
  { name: 'Cherry',     points: 100,  emoji: '๐Ÿ’' },
  { name: 'Strawberry', points: 300,  emoji: '๐Ÿ“' },
  { name: 'Orange',     points: 500,  emoji: '๐ŸŠ' },
  { name: 'Apple',      points: 700,  emoji: '๐ŸŽ' },
  { name: 'Melon',      points: 1000, emoji: '๐Ÿˆ' },
];

let fruitActive = false;
let fruitPos = { x: 9, y: 11 };
let fruitTimer = 0;
const FRUIT_DURATION = 10000;
const FRUIT_TRIGGER = 70; // appear after 70 dots eaten

function checkFruitSpawn() {
  const eaten = totalDots - dotsLeft;
  if (!fruitActive && eaten >= FRUIT_TRIGGER) {
    fruitActive = true;
    fruitTimer = Date.now();
  }
  // Despawn after timeout
  if (fruitActive &&
      Date.now() - fruitTimer > FRUIT_DURATION) {
    fruitActive = false;
  }
}

function getFruit() {
  const idx = Math.min(level - 1, FRUITS.length - 1);
  return FRUITS[idx];
}
Tip: Fruit bonuses are optional but they add variety and give skilled players extra scoring opportunities. The fruit position is usually in the center of the map.

What's Next?

Your Pac-Man now has real progression. Each level feels different as ghosts get faster and power pellets last shorter. Next up:

Continue to Part 6: Sound Effects where we add audio feedback to make the game feel alive.

More Articles

๐Ÿ”Š
Tutorial Pac-Man Sound Effects: Waka-Waka and Beyond Tutorial ยท Apr 23, 2026 ยท 01:00 PM
๐Ÿ’Š
Tutorial Pac-Man Power Pellet Mode: Ghosts Turn Blue Tutorial ยท Apr 23, 2026 ยท 11:00 AM
๐Ÿ‘ป
Tutorial Pac-Man Ghost Chase AI: Make Ghosts Hunt the Player Tutorial ยท Apr 23, 2026 ยท 10:00 AM
โ† Back to Blog