Slides/Reference Code
Day 1Day 2Day 3Day 4
Instructor Reference · Day 4

Day 4 — Polish & Make It Yours

Students add lives, level progression (maze resets, ghosts speed up), a HUD bar, and put their own design stamp on the game.

New today:Deep copy array: ORIGINAL_MAZE.map(row => [...row])Multiple game states: gameOver, wonLives system with respawnLevel counter — speed increases each levelHUD with score, level, and life icons
sketch.js▶ Run Output
// ════════════════════════════════════════════════════════════
//  DAY 4 — Polish & Make It Yours
//  Adds: 3 lives, level reset, HUD bar, speed scaling
// ════════════════════════════════════════════════════════════

const TILE_SIZE = 20;
const COLS = 21, ROWS = 21;
const PATH = 0, WALL = 1, PELLET = 2, POWER = 3;

// Keep the original so we can reset each level
const ORIGINAL_MAZE = [
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
  [1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
  [1,2,1,1,2,1,1,1,1,2,1,2,1,1,1,1,2,1,1,2,1],
  [1,3,1,1,2,1,1,1,1,2,1,2,1,1,1,1,2,1,1,3,1],
  [1,2,1,1,2,2,2,1,1,2,2,2,1,1,2,2,2,1,1,2,1],
  [1,2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,2,1],
  [1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
  [1,1,1,1,2,1,1,1,0,0,1,0,0,1,1,1,2,1,1,1,1],
  [1,1,1,1,2,1,0,0,0,0,1,0,0,0,0,1,2,1,1,1,1],
  [1,1,1,1,2,1,0,1,1,0,0,0,1,1,0,1,2,1,1,1,1],
  [1,2,2,2,2,0,0,1,0,0,0,0,0,1,0,0,2,2,2,2,1],
  [1,1,1,1,2,1,0,1,1,1,1,1,1,1,0,1,2,1,1,1,1],
  [1,1,1,1,2,1,0,0,0,0,1,0,0,0,0,1,2,1,1,1,1],
  [1,1,1,1,2,1,1,1,0,0,1,0,0,1,1,1,2,1,1,1,1],
  [1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
  [1,2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,2,1],
  [1,2,1,1,2,2,2,1,1,2,2,2,1,1,2,2,2,1,1,2,1],
  [1,3,1,1,2,1,1,1,1,2,1,2,1,1,1,1,2,1,1,3,1],
  [1,2,1,1,2,1,1,1,1,2,1,2,1,1,1,1,2,1,1,2,1],
  [1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
];

let maze;
function resetMaze() { maze = ORIGINAL_MAZE.map(r => [...r]); }

let pacRow, pacCol, pacDir, mouthAngle, mouthOpen;
let score = 0, lives = 3, level = 1;
let gameOver = false;
let scaredTimer = 0, ghostMoveTimer = 0, ghostSpeed;
let ghosts;

// ── GHOST SPAWN POSITIONS ─────────────────────────────────────
// Change row and col to wherever YOU want each ghost to start.
// Must be a PATH tile (0 in your maze — not a wall).
let ghostSpawns = [
  { row: 9,  col: 9  },   // Ghost 1 — red (Blinky)
  { row: 9,  col: 11 },   // Ghost 2 — pink (Pinky)
  { row: 11, col: 9  },   // Ghost 3 — cyan (Inky)
  { row: 11, col: 11 },   // Ghost 4 — orange (Clyde)
];

function resetPositions() {
  pacRow=16; pacCol=10; pacDir=0; mouthAngle=0; mouthOpen=true;
  scaredTimer=0; ghostMoveTimer=0;
  ghostSpeed = Math.max(5, 15 - (level-1)*2);  // faster each level
  ghosts = [
    { row: ghostSpawns[0].row, col: ghostSpawns[0].col, color:[255,0,0],    dr:0,  dc:1  },
    { row: ghostSpawns[1].row, col: ghostSpawns[1].col, color:[255,184,255],dr:0,  dc:-1 },
    { row: ghostSpawns[2].row, col: ghostSpawns[2].col, color:[0,255,255],  dr:-1, dc:0  },
    { row: ghostSpawns[3].row, col: ghostSpawns[3].col, color:[255,184,81], dr:1,  dc:0  },
  ];
}

function setup() {
  createCanvas(COLS*TILE_SIZE, ROWS*TILE_SIZE+24);  // +24 for HUD
  frameRate(60); noSmooth();
  resetMaze(); resetPositions();
}

function draw() {
  background(0);
  push(); translate(0, 24);  // shift maze down for HUD
  drawMaze();
  if (!gameOver) {
    ghostMoveTimer++;
    if (ghostMoveTimer >= ghostSpeed) { moveGhosts(); ghostMoveTimer=0; }
    if (scaredTimer > 0) scaredTimer--;
    checkGhostCollision();
    checkWin();
  }
  drawGhosts(); animateMouth(); drawPacman();
  pop();
  drawHUD();
  if (gameOver) { fill(0,0,0,160); rect(0,0,width,height); fill(255,0,0); textSize(28); textAlign(CENTER,CENTER); text('GAME OVER',width/2,height/2); fill(255); textSize(14); text('Press R',width/2,height/2+28); }
}

// NEW: HUD bar at top — score, level, lives
function drawHUD() {
  fill(20); noStroke(); rect(0,0,width,24);
  fill(255); textSize(12); textAlign(LEFT,CENTER);  text('SCORE: '+score, 8, 12);
  textAlign(CENTER,CENTER); text('LVL '+level, width/2, 12);
  textAlign(RIGHT,CENTER);  text('LIVES:', width-8-lives*16, 12);
  fill(255,220,0); noStroke();
  for (let i=0;i<lives;i++) arc(width-8-i*16, 12, 12, 12, radians(30), radians(330), PIE);
}

function drawMaze() {
  for (let row=0;row<ROWS;row++) for (let col=0;col<COLS;col++) {
    let cell=maze[row][col], x=col*TILE_SIZE, y=row*TILE_SIZE;
    if (cell===WALL)        { fill(0,0,180); noStroke(); rect(x,y,TILE_SIZE,TILE_SIZE); }
    else if (cell===PELLET) { fill(0); rect(x,y,TILE_SIZE,TILE_SIZE); fill(255,255,200); noStroke(); circle(x+TILE_SIZE/2,y+TILE_SIZE/2,4); }
    else if (cell===POWER)  { fill(0); rect(x,y,TILE_SIZE,TILE_SIZE); let p=map(sin(frameCount*0.15),-1,1,8,14); fill(255,180,0); noStroke(); circle(x+TILE_SIZE/2,y+TILE_SIZE/2,p); }
    else { fill(0); rect(x,y,TILE_SIZE,TILE_SIZE); }
  }
}

function drawGhosts() {
  for (let g of ghosts) {
    let x=g.col*TILE_SIZE,y=g.row*TILE_SIZE,cx=x+TILE_SIZE/2,cy=y+TILE_SIZE/2;
    let col=g.color;
    if (scaredTimer>0) col=(scaredTimer<60&&frameCount%20<10)?[255,255,255]:[0,0,200];
    noStroke(); fill(col[0],col[1],col[2]);
    arc(cx,cy,TILE_SIZE-2,TILE_SIZE-2,PI,0,CHORD); rect(x+1,cy,TILE_SIZE-2,TILE_SIZE/2-1);
    let w=(TILE_SIZE-2)/3;
    for (let i=0;i<3;i++) arc(x+1+i*w+w/2,y+TILE_SIZE-2,w,w*1.2,0,PI,PIE);
    if (scaredTimer<=0) { fill(255); ellipse(cx-3,cy-1,5,6); ellipse(cx+3,cy-1,5,6); fill(0,0,180); ellipse(cx-2,cy-1,2,3); ellipse(cx+4,cy-1,2,3); }
  }
}

function moveGhosts() {
  const dirs=[{dr:-1,dc:0},{dr:1,dc:0},{dr:0,dc:-1},{dr:0,dc:1}];
  for (let g of ghosts) {
    if (maze[g.row+g.dr][g.col+g.dc]===WALL) {
      let s=[...dirs].sort(()=>random(-1,1));
      for (let d of s) { if (maze[g.row+d.dr][g.col+d.dc]!==WALL){g.dr=d.dr;g.dc=d.dc;break;} }
    }
    let nr=g.row+g.dr,nc=g.col+g.dc;
    if (maze[nr][nc]!==WALL){g.row=nr;g.col=nc;}
  }
}

// NEW: respawn on death, lose game when lives = 0
function checkGhostCollision() {
  for (let g of ghosts) {
    if (g.row===pacRow&&g.col===pacCol) {
      if (scaredTimer>0) { g.row=9+floor(random(3)); g.col=9+floor(random(3)); score+=200; }
      else { lives--; if (lives<=0) gameOver=true; else resetPositions(); }
    }
  }
}

// NEW: all pellets gone → next level
function checkWin() {
  for (let r of maze) for (let c of r) if (c===PELLET||c===POWER) return;
  level++; score+=1000; resetMaze(); resetPositions();
}

function animateMouth() {
  if (mouthOpen){mouthAngle+=4;if(mouthAngle>=40)mouthOpen=false;}
  else          {mouthAngle-=4;if(mouthAngle<=0) mouthOpen=true; }
}

function drawPacman() {
  let x=pacCol*TILE_SIZE+TILE_SIZE/2, y=pacRow*TILE_SIZE+TILE_SIZE/2;
  fill(255,220,0); noStroke();
  arc(x,y,TILE_SIZE-2,TILE_SIZE-2,radians(pacDir+mouthAngle),radians(pacDir+360-mouthAngle),PIE);
}

function keyPressed() {
  if (key==='r'||key==='R'){window.location.reload();return;}
  if (gameOver) return;
  let nr=pacRow,nc=pacCol;
  if (keyCode===UP_ARROW)    {nr-=1;pacDir=270;}
  if (keyCode===DOWN_ARROW)  {nr+=1;pacDir=90; }
  if (keyCode===LEFT_ARROW)  {nc-=1;pacDir=180;}
  if (keyCode===RIGHT_ARROW) {nc+=1;pacDir=0;  }
  if (maze[nr][nc]!==WALL) {
    pacRow=nr;pacCol=nc;
    if (maze[pacRow][pacCol]===PELLET){maze[pacRow][pacCol]=PATH;score+=10;}
    else if (maze[pacRow][pacCol]===POWER){maze[pacRow][pacCol]=PATH;score+=50;scaredTimer=300;}
  }
}
← Back to SlidesRun This ▶