Students add lives, level progression (maze resets, ghosts speed up), a HUD bar, and put their own design stamp on the game.
// ════════════════════════════════════════════════════════════
// 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;}
}
}