In the realm of entertaining carnival games, surely nobody is unfamiliar with the infamous game known as Whack-A-Mole. It is already known as a widespread game that we all surely must have enjoyed at least once during our lives; after all, who doesn’t enjoy bonking moles that pop out of dirt holes in the head with a big hammer? I am happy to make it clear that I myself am among the folks who have always enjoyed the game; hence why I chose to create my own unique take of the Whack-a-Mole game, but with a few twists to make it be a little bit different from the original. The twist to mention, the one that is the elephant in the room, the moles in my game are instead replaced by colored 2D versions of Mickey Mouse’s head! This is because of how not only was he my favorite during my childhood, but also because my major is based in getting involved in digital business with certain theme parks, which does include Disney! Therefore, I would consider this project as my own practice for when the day comes of me hopefully getting the chance to work alongside a major theme park like Disney. Speaking of colored heads, allow me to successfully reveal to you how the game works.
The way my game works is that it takes the classic Whack-a-Mole formula and formulates two rows of three holes, which altogether make up six holes. On top of that, the game would also supposedly take place in a forest, which is apart of my attempt to have the game feel as if you found these six holes somewhere in a special kind-of forest where these six holes have these Mickeys popping up in an act to simply challenge to see how fast your reflexes are; something that just sounds as if it popped right of a literal Disney short film! The only exception, however, would be the scoreboard on the top left corner of the game that will have points (aka your points) increase each time you hit a specific Mickey Head. Now, as for the Mickey heads that will pop out, there are about four different variations of them. The first variation is the Mickey Head whose head is colored cyan and his ears are colored white; simply, he is the variation that you are supposed to hit, since each time you hit one, you earn five points added to your score! Of course, that isn’t the only Mickey Head you are able to hit, for you must also be on the lookout for a yellow Mickey Head that will pop up anywhere between the gaps of the rows or in any of the six holes; it only shows you for one second, so you are going to be tested to see how fast you can click on it being it disappears into a three-minute cooldown. The reward for clicking it on time will add thirty points to your score! However, there are two other Mickey Heads that are best recommended for you to avoid. The first Mickey head to avoid is an orange-colored Mickey head that will be anywhere in either the row gaps or holes by random; this is because if you click on that one, it will reset your scoreback to zero as a penalty! Lastly, the other Mickey head to avoid is a red-colored Mickey head that will be unpredictable and pop up in almost the same spots as the yellow and orange ones, because clicking on it will immediately send you to a game over screen that lasts six seconds before taking you back to the main menu! However, a funny thing that I made sure was possible in my game, was that if a player were to wish to quit the game via being retreated back to the main menu and closing the game from there, they would have to click on a red Mickey head on purpose to do; I simply that since the game over screen is kind-of hilarious on its own without its awkward transition and silly trombone sound effect, it would be funny for the person and thus leave them with a smile on their face.
let mickeys = [];
let lastSpawnTime = 0;
let spawnDuration = 2500;
let score = 0;
let hitSound;
let redMickey = null;
let nextRedSpawn = 0;
let redSpawnTime = 0;
let redDuration = 1500;
let gameOverSound;
let gameOverStartTime = 0;
let yellowMickey = null;
let nextYellowSpawn = 0;
let yellowSpawnTime = 0;
let yellowDuration = 1000;
let yellowCooldown = 180000;
let orangeMickey = null;
let nextOrangeSpawn = 0;
let orangeSpawnTime = 0;
let orangeDuration = 1500;
// Being sure to check whether it's time for a new group to come up
if (millis() - lastSpawnTime >= spawnDuration){
spawnMickeys();
}
// Making sure to draw every Mickey currently in "mickeys=[]" array
for (let mickey of mickeys){
mickey.display();
}
// Spawn in red Mickey Head at the time as scheduled for it
if(
redMickey === null &&
orangeMickey === null &&
millis() >= nextRedSpawn
){
spawnRedMickey();
}
// Take away red Mickey head after 1.5 second timer is up
if (
redMickey != null &&
millis() - redSpawnTime >= redDuration
){
redMickey = null;
scheduleRedMickey();
}
// have the Red Mickey head be drawn
if (redMickey !== null){
redMickey.display();
}
// Spawn in yellow Mickey Head at the time as scheduled for it
if (yellowMickey === null && millis() >= nextYellowSpawn){
spawnYellowMickey();
}
// Put yellow head away after 1 second
if (
yellowMickey !== null &&
millis() - yellowSpawnTime >= yellowDuration
){
yellowMickey = null;
// Begin cooldown (maximum 3 minutes)
scheduleYellowMickey();
}
// Letting the yellow Mickey head be drawn
if (yellowMickey !== null){
yellowMickey.display();
}
// Spawn in orange Mickey Head at the time as scheduled for it
if (
orangeMickey === null &&
redMickey === null &&
millis () >= nextOrangeSpawn
){
spawnOrangeMickey();
}
// Take out orange Mickey Head after 1.5 seconds
if (
orangeMickey !== null &&
millis() - orangeSpawnTime >= orangeDuration
){
orangeMickey = null;
scheduleOrangeMickey();
}
// Let orange Mickey head be drawn and displayed
if (orangeMickey !== null){
orangeMickey.display();
}
function spawnMickeys(){
// Remove any previous Mickey Heads
mickeys = [];
// Making the decision of whether 1, 2, or 3 Mickey Heads appear
let amount = floor(random(1, 4));
// Making sure it remembers which holes have been selected
let usedHoles = [];
for (let i = 0; i < amount; i++){
// Decide on a random hole from 0 to 5
let hole = floor(random(6));
while(usedHoles.includes(hole) ||
(redMickey !== null &&
redMickey.holeIndex === hole
)
){
hole = floor(random(6));
}
// Making sure it remembers that this hole is in-use
usedHoles.push(hole);
// Being sure to calculate the hole's role and column
let row = floor(hole / 3);
let col = hole % 3;
// Being sure to calculate the position of the Mickey Head
let x = width * (0.25 + col * 0.25);
let y = height * (0.45 + row * 0.3);
// Bring the Mickey Head to life above the hole by a tiny bit
let mickey = new MickeyHead(x, y - 30);
// Adding Mickey Head to "let mickeys = []" array
mickeys.push(mickey);
}
// Remembering when this circle group appeared
lastSpawnTime = millis();
// Choosing random duration between 2-3 seconds
spawnDuration = random(2000, 3000);
}
function spawnRedMickey(){
// Choose which of the 9 positions for the red head to go by random
let position = floor(random(9));
let x;
let y;
if (position < 6){
// one of the 6 holes, should the Mickey head appear there
let row = floor(position / 3);
let col = position % 3;
x = width * (0.25 + col * 0.25);
y = height * (0.45 + row * 0.3) - 30;
} else {
// One of the 3 gaps between columns
let col = position - 6;
x = width * (0.25 + col * 0.25);
y = height * 0.60 - 30;
}
redMickey = new RedMickeyHead(x, y);
redMickey.holeIndex = position < 6 ? position : -1;
redSpawnTime = millis();
}
function spawnOrangeMickey(){
let availablePositions = [];
// Be on lookout for all 9 possible positions for orange head to use
for (let position = 0; position < 9; position++){
let x;
let y;
if (position < 6){
let row = floor(position / 3);
let col = position % 3;
x = width * (0.25 + col * 0.25);
y = height *(0.45 + row * 0.3) - 30;
} else {
let col = position - 6;
x = width * (0.25 + col * 0.25);
y = height * 0.60 -30;
}
let occupied = false;
// Tracking of cyan heads (if need be)
for(let mickey of mickeys){
if (dist(x, y, mickey.x, mickey.y) < 65){
occupied = true;
}
}
// Check for yellow head (if need be)
if (yellowMickey !== null){
if (dist(x, y, yellowMickey.x, yellowMickey.y) < 65){
occupied = true;
}
}
if (!occupied){
availablePositions.push({ x: x, y: y, position: position});
}
}
// Orange head should only spawn if there's a free open-position
if (availablePositions.length === 0){
scheduleOrangeMickey();
return;
}
let chosen = random(availablePositions);
orangeMickey = new OrangeMickeyHead(chosen.x, chosen.y);
orangeMickey.holeIndex = chosen.position < 6 ? chosen.position : -1;
orangeSpawnTime = millis();
}
function spawnYellowMickey(){
let availablePositions = [];
// Keep track of yellow Mickey Head's 9 possible spots
for (let position = 0; position < 9; position++){
let occupied = false;
// Check on the cyan Mickey heads
for (let mickey of mickeys){
let row = floor(position / 3);
let col = position % 3;
if (position < 6){
let x = width * (0.25 + col * 0.25);
let y = height *(0.45 + row * 0.3) - 30;
if (mickey.x === x && mickey.y === y){
occupied = true;
}
}
}
// Check on the red heads if necessary on behalf of yellow heads
if (redMickey !== null){
let redPositionX = redMickey.x;
let redPositionY = redMickey.y;
let x;
let y;
if (position < 6){
let row = floor(position / 3);
let col = position % 3;
x = width * (0.25 + col * 0.25);
y = height * (0.45 + row * 0.3) - 30;
} else {
let col = position - 6;
x = width * (0.25 + col * 0.25);
y = height * 0.60 - 30;
}
if ( x === redPositionX && y === redPositionY){
occupied = true;
}
}
if (!occupied){
availablePositions.push(position);
}
}
// decision-making on picking any available position
let position = random(availablePositions);
let x;
let y;
if (position < 6){
let row = floor(position/3);
let col = position % 3;
x = width * (0.25 + col * 0.25);
y = height * (0.45 + row * 0.3) - 30;
} else {
let col = position - 6;
x = width * (0.25 + col * 0.25);
y = height * 0.60 - 30;
}
yellowMickey = new YellowMickeyHead(x, y);
yellowMickey.HoleIndex = position < 6 ? position : -1;
yellowSpawnTime = millis();
}
function scheduleRedMickey(){
// Decide random delay from 4 to 10 seconds
nextRedSpawn = millis() + random(4000, 10000);
}
function scheduleOrangeMickey(){
nextOrangeSpawn = millis() + random(4000, 10000);
}
function scheduleYellowMickey(){
//Reminder to wait a few minutes (3 maximum at least) before next head appears
nextYellowSpawn = millis() + yellowCooldown;
}
function drawGameOver(){
// Set-up of the background color for Game Over screen
background(0);
//Set-up of game over message appearance
textAlign(CENTER, CENTER);
textFont("Press Start 2P");
textSize(30);
fill(255, 80, 180);
text("FAIL!\nSORRY SON!", width / 2, height / 2);
// Keep track of whether 6 seconds have finished being passed
if(millis() - gameOverStartTime >= 6000){
// Bring the player back to the main menu automatically
gameState = "menu";
playMenuMusic();
}
}
function mousePressed(){
if (gameState === "menu") {
console.log("Mouse clicked! Game state:", gameState);
// Making sure that PLAY button is activated
if (
mouseX > width / 2 - 110 &&
mouseX < width / 2 + 110 &&
mouseY > height - 170 &&
mouseY < height - 90
){
// Begin soundtrack after user interacts with menu
userStartAudio();
menuSoundtrack.play();
gameState = "instructions";
}
}
else if (gameState === "instructions"){
// Making sure that GO TIME (start) button gets activated
if (
mouseX > width / 2 - 130 &&
mouseX < width / 2 + 130 &&
mouseY > height * 0.83 - 32.5 &&
mouseY < height - 0.83 + 32.5
){
// Have score be resetted within a new game
score = 0;
// Gameplay start
gameState = "game";
// cease main menu music
menuSoundtrack.stop();
// Begin gameplay music
gameplayMusic.play();
console.log("Attempted to start gameplay music!");
spawnMickeys();
// Reset scheduling of red Mickey head
redMickey = null;
scheduleRedMickey();
// Reset yellow Mickey head for new game
yellowMickey = null;
// First yellow Mickey Head appearing after 10-20 seconds by random
nextYellowSpawn = millis() + random(10000, 20000);
// Reset orange Mickey head for new game
orangeMickey = null;
scheduleOrangeMickey();
// cease menu music upon gameplay beginning
if (menuSoundtrack.isPlaying()){
menuSoundtrack.stop();
}
}
}
else if (gameState === "game"){
// Be on lookout for player clicking on red Mickey head
if (
redMickey !== null &&
redMickey.isClicked(mouseX, mouseY)
){
// have gameplay music stop immediately
gameplayMusic.stop();
// Switch over to game over
gameState = "gameover";
//Setting put of the memory of when the player lost
gameOverStartTime = millis();
// Play sad trombone at precise moment
gameOverSound.play();
// Take away Red Mickey Head for game over to operate with ease
redMickey = null;
return;
}
// Check to see if player clicked on the orange Mickey head
if (
orangeMickey !== null &&
orangeMickey.isClicked(mouseX, mouseY)
){
// bring score back to zero
score = 0;
// Play sound effect indicating loss of score
orangeSound.play();
// Take out Mickey head right away
orangeMickey = null;
// get to scheduling the orange Mickey head's next pop-up moment
scheduleOrangeMickey();
return;
}
// Check to see if player clicked on the yellow Mickey head
if (
yellowMickey !== null &&
yellowMickey.isClicked(mouseX, mouseY)
){
// Award player with +30 points
score += 30;
// Play existing hit sound
bonusSound.play();
// Take away yellow Mickey head instantly
yellowMickey = null;
// Let cooldown (whose maximum is technically 3 minutes) begin
scheduleYellowMickey();
return;
}
//set-up of loop rountine regarding when a head gets hit/clicked
for(let i = mickeys.length - 1; i >= 0; i--){
if (mickeys[i].isClicked(mouseX, mouseY)){
// Letting 5 points be added to scoreboard
score += 5;
// Letting "hit" sound effect be played
hitSound.play();
// Take away Mickey Head upon it being hit
mickeys.splice(i, 1);
// Quit checking the hit was landed and successful
break;
}
}
}
}
class MickeyHead{
// Making sure that the Mickey Heads are positioned
constructor(x, y){
this.x = x;
this.y = y;
}
display(){
// The 2 white Mickey ears set-up
fill(255);
circle(this.x - 25, this.y - 30, 35);
circle(this.x + 25, this.y - 30, 35);
// The cyan head of the Mickey head
fill(0, 255, 255);
circle(this.x, this.y, 65);
}
isClicked(mx, my){
// Measurement of how far is the player from hitting the head
let clickDistance = dist(mx, my, this.x, this.y);
// Check to make sure the player indeed clicked inside of the head
return clickDistance < 32.5;
}
}
// Set-up of the red Mickey Head that borrows elements from the "MickeyHead" class function
class RedMickeyHead extends MickeyHead {
display(){
// Set-up of red Mickey head ears
fill(255, 0, 0);
circle(this.x - 25, this.y - 30, 35);
circle(this.x + 25, this.y - 30, 35);
// The circular head of the Mickey head
fill(255, 0, 0);
circle(this.x, this.y, 65);
}
}
// Set-up of the yellow Mickey Head that borrows elements from the "MickeyHead" class function
class YellowMickeyHead extends MickeyHead{
display(){
// Set-up of yellow Mickey head ears
fill(255, 225, 0);
circle(this.x - 25, this.y - 30, 35);
circle(this.x + 25, this.y - 30, 35);
// Circular head of the Mickey head
fill(255, 225, 0);
circle(this.x, this.y, 65);
}
}
// Set-up of the orange Mickey Head that borrows elements from the "MickeyHead" class function
class OrangeMickeyHead extends MickeyHead{
display(){
// Set-up of orange Mickey head ears
fill(255, 140, 0);
circle(this.x - 25, this.y - 30, 35);
circle(this.x + 25, this.y - 30, 35);
// Orange Mickey's circular head
fill(255, 140, 0);
circle(this.x, this.y, 65);
}
}
If there was a part that I am proud of, it is definitely implementing the Mickey heads themselves, as well as the sound effects that accompany them! It is because of how I understood that most people like it when a game is as goofy as it possibly could be. It’s something about that hilarious energy that would cause people to continue playing for however long they need, especially if there’s sound effects resembling that of memes that almost anybody would recognize. Not to mention that I found myself laughing at the sound effects that each Mickey Head is accompanied with, which already proved to me that what I’m hearing is something that anybody would be delighted to hear as well. After all, a game is all about engagement and it can maybe be convincing for a user to keep on indulging in the humorous nature from within the game.
let menuSoundtrack;
let gameplayMusic;
menuSoundtrack = await loadSound("menuSoundtrack.mp3");
gameplayMusic = await loadSound("gameplay_music.mp3");
// Keep track of whether 6 seconds have finished being passed
if(millis() - gameOverStartTime >= 6000){
// Bring the player back to the main menu automatically
gameState = "menu";
playMenuMusic();
// Begin soundtrack after user interacts with menu
userStartAudio();
menuSoundtrack.play();
gameState = "instructions";
// Be on lookout for player clicking on red Mickey head
if (
redMickey !== null &&
redMickey.isClicked(mouseX, mouseY)
){
// have gameplay music stop immediately
gameplayMusic.stop();
// Switch over to game over
gameState = "gameover";
//Setting put of the memory of when the player lost
gameOverStartTime = millis();
// Play sad trombone at precise moment
gameOverSound.play();
function playMenuMusic(){
// Cease gameplay music if it's playing right now
if (gameplayMusic.isPlaying()){
gameplayMusic.stop();
}
// Start up menu music ONLY if it ain't playing at the moment
if (!menuSoundtrack.isPlaying()){
menuSoundtrack.loop();
}
}
function playGameplayMusic(){
// Cease menu music
if (menuSoundtrack.isPlaying()){
menuSoundtrack.stop();
}
// Start up gameplay music
if (!gameplayMusic.isPlaying()){
gameplayMusic.loop();
}
}
The biggest challenge for me, however, would definitely be the implementation of music in my game; this is often due to how even though I would have my downloaded mp3 songs uploaded within my project fair and square, the music would never seem to be activated. It was almost as if they weren’t recognized enough to even be played where I wanted them to be played in, despite me doing what I thought was correctly defining the variables/functions that would cause the music to be played (such as defining the function that was meant to play music during the gameplay as “gameplayMusic”). This is when I knew that p5.js sure knew a thing or two about being strict with functions/variables and how to activate them properly. Which is why I have had to refer to some of the older example projects that were linked, as well as proofread my mistakes with the assistance of Ai so that I would see if I didn’t write something correctly or whatnot. Eventually, I was able to figure it out via switching certain commands with the ones that my program was able to read, such as how for my “gameplayMusic” variable, i was able to switch out “loop()” with “play()” so that it can understand better on where and how to play said-music. Before you know it, I was able to sort out an evolution of music within my game that would go as follows: main menu (no music) => instructions page (club music that is supposed to get people’s energy up and running for the game) = gameplay itself(another form of techno club music that will boost the already-risen energy of the player as they get to enjoying themselves in the game! It is these simple steps of build-up that I was able to debug and bring to life within my game.
All in all, I believe that despite the complicated strict lines I had to follow in order for p5.js to listen to me, this was quite the eventful project! Now, I still consider myself a giant personified rookie when it comes to game creation, but what I can say is that this may have given me the idea of what the whole process may technically be like. The only thing I wish I could have done is see if there could have been more Mickey heads that could have been added to the game to add even more value to the game. Sadly, I didn’t exactly have enough ideas, but at least four ideas are better than nothing, you know? Overall, these are nothing short but my thoughts of how the project was like for me.