The Problem
Traditional Pac-Man is a single-player game, limiting opportunities for competitive
and cooperative gameplay.
The Goal
Expand Pac-Man into a two-player experience while maintaining the classic feel and mechanics.
My Role
I was responsible for game design and development, from ideation to prototyping and final
implementation, as part of a team of four
The Setup
This group projext was part of the "Frontend Development" Module at the University of
Applied Sciences Grisons. Over one year we learned programming principles and applied them
directly to this game.
To ensure PacMan Battle provided an engaging and competitive multiplayer experience, we conducted a competitive audit, analyzing existing projects to understand what works and what doesn’t. Our goal was to convert the iconic game into a multiplayer format while staying true to its retro aesthetic and mechanics.
To ensure PacMan Battle provided an engaging and competitive multiplayer experience, we conducted a competitive audit, analyzing existing projects to understand what works and what doesn’t. Our goal was to convert the iconic game into a multiplayer format while staying true to its retro aesthetic and mechanics.
Since extensive research already existed on Pac-Man’s target audience, we conducted a secondary analysis to understand our potential users better. Based on the findings, we developed an ad-hoc persona to capture our target group's needs and frustrations.
Lukas is a retro game enthusiast, who needs an engaging multiplayer experience of PacMan, because it allows him to relive the nostalgia of the game while playing socially with friends.
To explore different design concepts, we visualized the game layout through digital sketches. Early iterations helped us quickly refine design choices before programming, ensuring that we can set the focus on JavaScript which was the main part of this module.
Once our initial game layout was in place, we conducted usability testing to validate our design decisions and test the functionality of our code.
The usability test involved a live demonstration of the prototype to a group of 11 participants. They could play freely and explore the mechanics naturally. We observed this real-world user interactions and conducted interviews to identify areas for improvement:
Tunnel Mechanic:
Removed due to a bug where characters could escape the game area
Scoring System:
Adjusted points to create a fairer competitive game
Local Score Storage:
Added a local storage to see how the user compared to previous scores
Interactive Quiz:
Introduced a Pac-Man-themed quiz to maintain engagement
Tunnel Mechanic:
Removed due to a bug where characters could escape the game area
Scoring System:
Adjusted points to create a fairer competitive game
Local Score Storage:
Added a local storage to see how the user compared to previous scores
Interactive Quiz:
Introduced a Pac-Man-themed quiz to maintain engagement
The game board is built using a grid-based system, where each tile represents different objects in an array.
// Map Example
const map = [
['1','a','a','2'], // Walls = a / Corners = Numbers
['b','.','.','b'], // Pellets = . / Vertical Walls = b
['b','p','p','b'], // Power‑ups = p
['4','a','a','3'] // Walls = a / Corners = Numbers
];
// Dynamic Generation
function generateMap () {
map.forEach((row, i) => {
row.forEach((symbol, j) => {
if (symbol === '.') {
pellets.push(new Pellet({ position: { x: j * 32, y: i * 32 }}));
} else if (symbol === 'p') {
powerUps.push(new PowerUp({ position: { x: j * 32, y: i * 32 }}));
} else {
boundaries.push(new Boundary({ position: { x: j * 32, y: i * 32 }}));
}
});
});
}
Pac-Man's movement follows a collision-based system. To avoid conflicts with webpage functions, only W, A, S, D keys were assigned for player controls.
// Player Movement
document.addEventListener("keydown", (event) => {
switch (event.key) {
case 'ArrowUp': player.velocity.y = -3; break;
case 'ArrowDown': player.velocity.y = 3; break;
case 'ArrowLeft': player.velocity.x = -3; break;
case 'ArrowRight': player.velocity.x = 3; break;
}
});
// Collision Detection
boundaries.forEach((boundary) => {
if (circleCollidesWithRectangle({ circle: player, rectangle: boundary })) {
player.velocity.x = 0;
player.velocity.y = 0;
}
});
The ghosts navigate randomly while avoiding walls. At the same time, the AI adjusts movement based on available paths.
// Ghosts Movement
const possibleDirections = ['up', 'down', 'left', 'right'];
const randomDirection = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
switch (randomDirection) {
case 'up': ghost.velocity.y = -3; break;
case 'down': ghost.velocity.y = 3; break;
case 'left': ghost.velocity.x = -3; break;
case 'right': ghost.velocity.x = 3; break;
}
// Ghosts Change Behavior
ghosts.forEach(ghost => {
ghost.scared = true;
setTimeout(() => ghost.scared = false, 5000);
});
Multiplayer turn system
// Round Timer
var timeLeft = 20;
var timerId = setInterval(() => {
timeLeft--;
if (timeLeft === 0) {
clearTimeout(timerId);
switchTurn();
}
}, 1000);
// Switch Turn
function switchTurn() {
currentPlayer = currentPlayer === 1 ? 2 : 1;
resetPlayerPosition();
}
Scoring System and Power-Ups
// Power-Up Collision Effect
if (circleCollidesWithRectangle({ circle: player, rectangle: pellet })) {
pellets.splice(i, 1); // Remove pellet
score += 10; // Increase score
}
// Power to Eat Ghosts
if (circleCollidesWithRectangle({ circle: player, rectangle: powerUp })) {
powerUps.splice(i, 1);
ghosts.forEach(ghost => ghost.scared = true);
}
© 2025 Anthony Zoss. All rights reserved.