For my midterm project, I decided to create a memory game. Given my unhelpable sweet tooth, I based my game around a bakery. I was partly inspired by the game Cooking Mama, which was a game on the Nintendo DS that I played frequently. My game involves the user picking a recipe, then having to memorise and select the right ingredients for that recipe within the given timeframe. The player is then “rewarded” with the dish if they are successful.
I wanted my game to have a cohesive theme to it, so I ended up creating all the graphics myself. This was by far one of the most time consuming aspects, as I was my own worst critic when it came to the quality of the images. Overall though, I am very satisfied with the way the graphics came out; I think they give the game a more polished and homey look.
One aspect I am particularly proud of is the Game class. Taking Professor Mang’s advice, I implemented a state machine to make the flow of the game easier to manage, as my game consisted of multiple “stages” that it would need to flow through in order to work coherently. This helped organise the code into more manageable sections, rather than a mess of code I’d need to sort through if I needed to change anything.
background(255); // Set background to white
if (this.state === "selection") {
image(shelfImage, 0, 0, width, height); // Display shelf as background
textAlign(CENTER, CENTER);
this.allIngredients.forEach((ingredient) => ingredient.display());
if (this.state === "start") {
image(titleImage, 0, 0, width, height); // Display title screen background image
text("Sweet Treats Bakery", width / 2, height / 2 - 50); // Title Text
text("Click to Start", width / 2, height / 2 + 50); // Subtitle Text
} else if (this.state === "menu") {
image(menuImage, 0, 0, width, height); // Display menu screen background image
text("MENU", width / 2, 100);
buttons.forEach((button) => button.display());
} else if (this.state === "memory") {
image(recipeImage, 0, 0, width, height); // Display recipe image as background
text("Memorize These Ingredients!", width / 2, 80);
this.correctIngredients.forEach((ing, i) =>
text(ing, width / 2, 300 + i * 50)
if (millis() - this.memoryTimer > 3000) {
this.state = "selection";
this.selectionTimer = millis();
} else if (this.state === "selection") {
text("Select the Ingredients!", width / 2, 50);
5 - floor((millis() - this.selectionTimer) / 1000)
if (millis() - this.selectionTimer > 5000) {
this.bakingTimer = millis();
} else if (this.state === "baking") {
let elapsedTime = millis() - this.bakingTimer;
let index = floor(elapsedTime / 1500); // Change image every 1.5 seconds
// Play ticking sound only when baking starts
if (!tickingSound.isPlaying()) {
tickingSound.setVolume(0.8);
// Display the oven images
image(ovenImages[index], 0, 0, width, height);
text("Baking...", width / 2, height / 2);
// Stop ticking and move to result after 4.5 seconds
if (elapsedTime > 4500) {
} else if (this.state === "result") {
image(finalImage, 0, 0, width, height); // Display title screen background image
text(`Your ${this.selectedDish} is now ready!`, width / 2, 200);
dishImages[this.selectedDish],
width / 2 - dishImages[this.selectedDish].width / 2,
text("Oh no! Those were the wrong ingredients.", width / 2, height / 2);
text("Click to Play Again", width / 2, height / 2 + 250);
if (this.state === "start") {
} else if (this.state === "menu") {
buttons.forEach((button) => {
if (button.checkClick(mouseX, mouseY)) {
this.selectedDish = button.label;
this.correctIngredients = {
// Storing the correct ingredients of the recipes
Cupcake: ["Flour", "Eggs", "Sugar", "Milk", "Vanilla"],
Pie: ["Flour", "Sugar", "Eggs", "Butter", "Apples"],
this.memoryTimer = millis();
} else if (this.state === "selection") {
this.allIngredients.forEach((ingredient) => {
if (ingredient.checkClick(mouseX, mouseY)) {
} else if (this.state === "result") {
display() {
background(255); // Set background to white
if (this.state === "selection") {
image(shelfImage, 0, 0, width, height); // Display shelf as background
}
textAlign(CENTER, CENTER);
textFont(font);
this.allIngredients.forEach((ingredient) => ingredient.display());
stroke(0);
strokeWeight(1);
fill(0);
if (this.state === "start") {
image(titleImage, 0, 0, width, height); // Display title screen background image
textSize(48);
text("Sweet Treats Bakery", width / 2, height / 2 - 50); // Title Text
textSize(32);
text("Click to Start", width / 2, height / 2 + 50); // Subtitle Text
} else if (this.state === "menu") {
image(menuImage, 0, 0, width, height); // Display menu screen background image
fill("#332211");
textSize(64);
text("MENU", width / 2, 100);
buttons.forEach((button) => button.display());
} else if (this.state === "memory") {
image(recipeImage, 0, 0, width, height); // Display recipe image as background
textSize(32);
text("Memorize These Ingredients!", width / 2, 80);
textFont(honeySaltFont);
textSize(35);
this.correctIngredients.forEach((ing, i) =>
text(ing, width / 2, 300 + i * 50)
);
if (millis() - this.memoryTimer > 3000) {
this.state = "selection";
this.selectionTimer = millis();
}
} else if (this.state === "selection") {
textSize(32);
text("Select the Ingredients!", width / 2, 50);
textSize(24);
text(
`Time left: ${Math.max(
0,
5 - floor((millis() - this.selectionTimer) / 1000)
)}s`,
width / 2,
110
);
if (millis() - this.selectionTimer > 5000) {
this.state = "baking";
this.bakingTimer = millis();
}
} else if (this.state === "baking") {
let elapsedTime = millis() - this.bakingTimer;
let index = floor(elapsedTime / 1500); // Change image every 1.5 seconds
index = min(index, 2);
// Play ticking sound only when baking starts
if (!tickingSound.isPlaying()) {
tickingSound.loop();
tickingSound.setVolume(0.8);
}
// Display the oven images
image(ovenImages[index], 0, 0, width, height);
textSize(32);
fill("#332211");
text("Baking...", width / 2, height / 2);
// Stop ticking and move to result after 4.5 seconds
if (elapsedTime > 4500) {
this.state = "result";
tickingSound.stop();
}
} else if (this.state === "result") {
image(finalImage, 0, 0, width, height); // Display title screen background image
textSize(40);
if (this.checkWin()) {
text(`Your ${this.selectedDish} is now ready!`, width / 2, 200);
image(
dishImages[this.selectedDish],
width / 2 - dishImages[this.selectedDish].width / 2,
300
);
} else {
text("Oh no! Those were the wrong ingredients.", width / 2, height / 2);
}
textSize(20);
text("Click to Play Again", width / 2, height / 2 + 250);
}
}
handleClick() {
if (this.state === "start") {
this.state = "menu";
} else if (this.state === "menu") {
buttons.forEach((button) => {
if (button.checkClick(mouseX, mouseY)) {
this.selectedDish = button.label;
this.correctIngredients = {
// Storing the correct ingredients of the recipes
Brownie: [
"Flour",
"Sugar",
"Eggs",
"Butter",
"Baking Powder",
"Chocolate",
],
Cupcake: ["Flour", "Eggs", "Sugar", "Milk", "Vanilla"],
Pie: ["Flour", "Sugar", "Eggs", "Butter", "Apples"],
}[this.selectedDish];
this.memoryTimer = millis();
this.state = "memory";
}
});
} else if (this.state === "selection") {
this.allIngredients.forEach((ingredient) => {
if (ingredient.checkClick(mouseX, mouseY)) {
ingredient.select();
}
});
} else if (this.state === "result") {
this.resetGame();
}
}
display() {
background(255); // Set background to white
if (this.state === "selection") {
image(shelfImage, 0, 0, width, height); // Display shelf as background
}
textAlign(CENTER, CENTER);
textFont(font);
this.allIngredients.forEach((ingredient) => ingredient.display());
stroke(0);
strokeWeight(1);
fill(0);
if (this.state === "start") {
image(titleImage, 0, 0, width, height); // Display title screen background image
textSize(48);
text("Sweet Treats Bakery", width / 2, height / 2 - 50); // Title Text
textSize(32);
text("Click to Start", width / 2, height / 2 + 50); // Subtitle Text
} else if (this.state === "menu") {
image(menuImage, 0, 0, width, height); // Display menu screen background image
fill("#332211");
textSize(64);
text("MENU", width / 2, 100);
buttons.forEach((button) => button.display());
} else if (this.state === "memory") {
image(recipeImage, 0, 0, width, height); // Display recipe image as background
textSize(32);
text("Memorize These Ingredients!", width / 2, 80);
textFont(honeySaltFont);
textSize(35);
this.correctIngredients.forEach((ing, i) =>
text(ing, width / 2, 300 + i * 50)
);
if (millis() - this.memoryTimer > 3000) {
this.state = "selection";
this.selectionTimer = millis();
}
} else if (this.state === "selection") {
textSize(32);
text("Select the Ingredients!", width / 2, 50);
textSize(24);
text(
`Time left: ${Math.max(
0,
5 - floor((millis() - this.selectionTimer) / 1000)
)}s`,
width / 2,
110
);
if (millis() - this.selectionTimer > 5000) {
this.state = "baking";
this.bakingTimer = millis();
}
} else if (this.state === "baking") {
let elapsedTime = millis() - this.bakingTimer;
let index = floor(elapsedTime / 1500); // Change image every 1.5 seconds
index = min(index, 2);
// Play ticking sound only when baking starts
if (!tickingSound.isPlaying()) {
tickingSound.loop();
tickingSound.setVolume(0.8);
}
// Display the oven images
image(ovenImages[index], 0, 0, width, height);
textSize(32);
fill("#332211");
text("Baking...", width / 2, height / 2);
// Stop ticking and move to result after 4.5 seconds
if (elapsedTime > 4500) {
this.state = "result";
tickingSound.stop();
}
} else if (this.state === "result") {
image(finalImage, 0, 0, width, height); // Display title screen background image
textSize(40);
if (this.checkWin()) {
text(`Your ${this.selectedDish} is now ready!`, width / 2, 200);
image(
dishImages[this.selectedDish],
width / 2 - dishImages[this.selectedDish].width / 2,
300
);
} else {
text("Oh no! Those were the wrong ingredients.", width / 2, height / 2);
}
textSize(20);
text("Click to Play Again", width / 2, height / 2 + 250);
}
}
handleClick() {
if (this.state === "start") {
this.state = "menu";
} else if (this.state === "menu") {
buttons.forEach((button) => {
if (button.checkClick(mouseX, mouseY)) {
this.selectedDish = button.label;
this.correctIngredients = {
// Storing the correct ingredients of the recipes
Brownie: [
"Flour",
"Sugar",
"Eggs",
"Butter",
"Baking Powder",
"Chocolate",
],
Cupcake: ["Flour", "Eggs", "Sugar", "Milk", "Vanilla"],
Pie: ["Flour", "Sugar", "Eggs", "Butter", "Apples"],
}[this.selectedDish];
this.memoryTimer = millis();
this.state = "memory";
}
});
} else if (this.state === "selection") {
this.allIngredients.forEach((ingredient) => {
if (ingredient.checkClick(mouseX, mouseY)) {
ingredient.select();
}
});
} else if (this.state === "result") {
this.resetGame();
}
}
The design of the game is fairly simple in concept, though I still struggled when making the intial base of the code. I had started by creating a prototype version, which I experimented on until I was satisfied with the game flow. That version was plain, with no graphics, and circles used as placeholders for the actual ingredients.

I like to think the final game looks better than this.
Not to say I didn’t struggle even after adjusting the prototype. While inserting the backgrounds and graphics, I realised that I had just simply hardcoded values for the recipes on the recipe selection screen, and that I should use buttons instead in my final code.

Making these buttons turned out to be challenging, as I had to make another class for them. I kept facing an error that would not let me adjust the y and x coordinates of the buttons, leading them to overlap. Several lines of code carefully inspected later, I found the error within the display function of the Game class, which was simultaneously calling the buttons alongside the original button class, leading to the code refreshing everytime to use the wrong coordinates for the buttons. After that hiccup, it was mostly smooth sailing.
I also implemented background music (see if you can recognise the instrumental) and a sound effect to my game. The sound effect is a ticking sound that plays for the duration of the “baking…” scene, which I thought would add a nice little touch to make the game a little more interactive.
Overall, I am very satisfied with my midterm project, and it turned out very close to what I had originally envisioned it to be, making the end product all the more rewarding to see.
Link to full screen version