Link to sketch (press ‘f’ for fullscreen):
Concept
For my concept, I wanted to create a cozy cafe game called Midnight Cafe, but with a twist. The user is a barista at a 24/7 cafe, however they specifically work the night shift. As a result, the customers they get are a bit unusual (in terms of both appearance and behavior). Instead of asking for certain items off the menu, they describe their mood and feelings. The player then has to figure out which ingredients to serve in their drink. There is a total of five different ingredients: espresso, milk, ice, honey and lavender, with each ingredient corresponding to one or more tags, i.e: bold, energizing, calming, refreshing, etc. Each customer secretly wants a subset of these tags. Once the drink is served, the customer gives a reaction based off how many tags were fulfilled, so the reaction is either satisfactory, an okay reaction, or a disappointed one and the tip received reflects these reactions. Once they have served all 5 customers, their shift is over and they can choose to play again or exit to the main menu.
How the project works
The first thing I built was the title screen. Here the user can see the title of the game “Midnight Cafe”, a start button, instructions and settings where the user can turn on music (I recommend doing this!). Each button is an object from the class Button which has certain attributes such as: x, y, width, height and string (what is displayed on the button). It has two methods, a display() method and a isHovering(). The second method is used in two cases, the first is enlarging the button slightly in display() when the user hovers over it, and the second is to switch between different states when the user presses the button. To switch between game states I used a variable called gameState with the following possible states: “title”, “instructions”, “settings”, “cafe”, “kitchen”, “end”. When the user presses on certain buttons, the state will change and in the draw function, a different backdrop will be drawn such as: drawTitleScreen(), drawInstructions(), drawSettings(), drawCafe(), drawKitchen(), drawEndScreen().
When the user clicks Start, it takes them to the cafe. The cafe has a cafe image in the backdrop, I then drew the counter, tip jar and time in the top right corner as well as the customers themselves. On top of the background and counter (but not the customer, tip jar and time) I added a translucent overlay which changes color as each customer is served to mimic the sun rising. Each customer is an object from the class Customer. Each customer has the following attributes: x, y, name, order, wants, result (and a couple more for the speech bubble). It has a method called serve(cup) which takes an object from the Cup class, this calls the method score(cup) and stores the time at which the drink was served. This allowed me to create a slight animation. Once a drink is served, the text box will switch from the order to “*sip*…” the function checks if time since serving has elapsed 1.5 seconds, then it will display the users result and the tip will be added to the tip jar. After 4 seconds, the next customer arrives and the time moves by an hour. The score(cup) method counts how many of the customer’s wanted tags are in the drink and returns the result. There is also the display function, the function calls each character’s draw function (e.g. drawGhost()) to display each customer. These customers were drawn in p5 on a separate sketch, I then moved them to this project. Since all these values were hardcoded, I had to translate and scale each drawing. This was done by choosing a point from each drawing as my center point, and translating by this.x – centerX and this.y – centerY. For drawings where I had to scale, I translate by just the x and y, scaled, then translated by -centerX and -centerY.
Here is an example for the drawGhost() :
drawGhost(x, y) {
push();
translate(this.x, this.y);
scale(1.5);
translate(-140, -188); // this is my center point
// ...drawing code
pop();
}
The display() function calls the character draw function and displays a speech bubble with the user’s order, clicking on this takes you to the kitchen. In the kitchen, we use the same cafe backdrop but zoom into the back since it has shelves and counters:

I display the customer’s order in the top left to serve as a reminder, as well as the time, counter, cup and ingredients (under each ingredient I display its name and tags) with a clear button (for the cup) and serve button.
Each ingredient is an object of the class Ingredient which contains functions to draw each ingredient (this also uses the same translate logic as the customers), and isHovering(), this was used to make each item “levitate” slightly when hovered to indicate that it is being selected.
In the Cup class, it has attributes: x,y, an array of ingredients, and the max number of ingredients which is 3. There is a method called add(ingredient) which pushes an ingredient to the cup class if the user presses on it (unless the array is full). The display method draws the cup as well as the ingredients inside. I used paletteLerp to make it seem like the ingredients are mixing together.
Here is the code below:
display() {
// width, height, ellipse height
let w = 90;
let h = 120;
let ellipseH = 22;
let top = this.y;
let bottom = this.y + h;
// height of each layer
let layerH = 30;
// glass body
noStroke();
fill(255, 255, 255, 80);
rectMode(CORNER);
rect(this.x - w / 2, top, w, h);
ellipse(this.x, bottom, w, ellipseH);
// fill cup if there are ingredients selected
let n = this.ingredients.length;
if (n > 0) {
let fillH = n * layerH;
// blend colors into 1 palette
let palette = [];
for (let i = 0; i < n; i++) {
palette.push([this.ingredients[i].color, (i + 0.5) / n]);
}
// bottom ellipse
fill(palette[0][0]);
ellipse(this.x, bottom, w, ellipseH);
// draw thin slices to create a blended effect
for (let y = 0; y < fillH; y++) {
fill(paletteLerp(palette, y / fillH));
rect(this.x - w / 2, bottom - y - 2, w, 2);
}
// top ellipse
fill(palette[n - 1][0]);
ellipse(this.x, bottom - fillH, w, ellipseH);
}
// glass outline
noFill();
stroke("#7384ab");
strokeWeight(3);
line(this.x - w / 2, this.y, this.x - w / 2, this.y + h);
line(this.x + w / 2, this.y, this.x + w / 2, this.y + h);
arc(this.x, bottom, w, ellipseH, 0, PI);
ellipse(this.x, top, w, ellipseH);
}
Once all customers have been served, I change gameState to “end” which displays the “Shift complete!” message as well as the tips received, the play again button and back to main button. When the player clicks Play Again or Back to Main, I call resetGame(), which resets the tips, clock, cup and each customer’s result, so the game can be replayed without restarting the sketch.
I used multiple sounds throughout my project: music, pouring sound when the user presses on an ingredient, sip for when the customer tries the drink, finish for when the shift is over and a bell for when each customer arrives.

Pressing ‘f’ scales the game to fullscreen mode.
Parts I am proud of
When starting this project, I planned on implementing all the technical details first, then finding characters, ingredients and backgrounds that I can use. However, when the time came, I wasn’t able to find any characters or ingredients. Therefore, I had to draw them myself. I was really worried about this since I was afraid that it wouldn’t turn out like I imagined. However, I was really satisfied with how each character came out, I really like their cute, cartoonish appearance and I think it really matches the concept for my game. For the background, I also really struggled finding one that matched with my characters. But after some time, I was able to find one that I used in both the cafe and the kitchen (I zoomed in on a certain part of the image).
Problems I ran into
One bug that confused me was the “Back to Main” button. When I went to the instructions or settings screen and pressed Back, it worked once, but then clicking those buttons again seemed to do nothing. The problem was that each button only updates its hovering value while it’s being drawn. Once I left the instructions screen, the Back button stopped being drawn, so its hovering value stayed stuck on true. The next time I clicked “How to Play,” my mousePressed() changed the state to “instructions” and then, in the same click, saw that the Back button was still “hovering” and sent me straight back to the title screen. It looked like nothing happened, but it was actually switching screens twice in one click. I fixed this by using else if so only one screen’s buttons are checked per click, and by resetting hovering to false after a button is pressed.
Another problem was that screens would affect each other’s drawings. For example, after visiting the kitchen, the title screen’s buttons and shapes would suddenly shift or have thicker outlines. This happened because of settings like rectMode(), strokeWeight() and fill() affect everything below them until they are changed again, even across different screens and frames. So whatever the kitchen set last carried over to the title screen. I fixed this by wrapping the screen drawing in draw() with push() and pop(), which resets all the settings at the end of every frame.
Areas for improvement
I think for improvement I could expand the game further, like adding more customers and ingredients. In addition, for the scoring logic, I only look at what the customer wants, but I could also add an attribute of tags the customer isn’t looking for to make the game harder. In addition, I could add a patience bar for each customer, so the more time you spend making the drink, the more patience they lose and therefore the less happy they are when they receive their drink. Furthermore, when the user replays the game, you get the same customers in the same order. In the future, I could randomize this as well as vary their lines.
AI use
AI was used in the following ways:
- Customer lines: all the customers orders and responses were AI generated.
- Mixing the drinks: I knew I had to use paletteLerp and draw my ingredients in a loop, however I wasn’t getting the results I wanted. Claude helped me realize that I needed to draw very thin rectangles and fill with the colors from the palette to make them look blended in.
- Translation and fullscreen: I initially had errors when try to translate customers and ingredients that were scaled, I didn’t understand that the order of operations (translate(this.x,this.y), scale(), translate(-centerX, -centerY)) mattered. This also applies to fullscreen, I drew the game in 600×400 so I wanted to scale it up proportionally when in fullscreen. I knew to look at the ratios when scaling up, but I struggled calculating the offsets and the new mouse positions on my own. Claude also helped me understand this part.
References/Inspiration:
For the characters, these images were used as inspiration:
Sounds were taken from: https://pixabay.com/sound-effects/
Cafe backdrop taken from: https://www.magnific.com/vectors/modern-cafe-interior
Font: https://fonts.google.com/specimen/Chewy?categoryFilters=Feeling:%2FExpressive%2FCute&preview.script=Latn















