Week 8: Unusual Switch

For this weeks assignment, we were asked to create an unusual switch. Passing by the NYU sports gyms, inspiration struck. I decided to create a switch that looked like a basketball hoop, and would light an LED when the ball was in the hoop, thus “completing” the circuit.

For this circuit, I created a basketball hoop within which I implemented wires. I wanted a balance between it actually looking like a hoop, but also still being functional. I wrapped a styrofoam ball with aluminium foil to make sure it would conduct electricity, and thus complete the circuit when the ball was dropped into the hoop, making it the catalyst for completing the circuit.

Upon the successful completion of the circuit, an LED would then light up.

I struggled a bit having the wires stay in place, and ensuring that the ball made successful contact with them everytime. My setup was very rough and could probably have used more aesthetic features, but i was more focused on making sure the idea actually worked. Maybe in the future, it might be worth it to try using copper tape to make sure the switch is completed regardless of the balls position.

Overall, I had fun coming up with an idea, and then implementing it to see whether the circuit would be completed or not.

Video of the switch: https://drive.google.com/file/d/1bRdIRVIdDPK5BbK1ZsrdfVIz8_hRawbn/view?usp=sharing

Week 8 – Reading Response 2 (Her Code Got Humans On The Moon)

The remarkable contributions made by Margaret Hamilton to the Apollo moon mission and the advancement of contemporary software engineering are highlighted in the article “Her Code Got Humans on the Moon – and Invented Software Itself”. I had heard about the Apollo missions before reading this, but I had never given much thought to the code and problem-solving that enabled them. It is amazing to see that Hamilton’s contributions not only aided in the successful landing of astronauts on the moon but also significantly influenced the creation of software as a profession. She demonstrated how important software is in high-stakes situations by emphasizing error detection and rigorous programming, which saved the mission when an excess of data nearly led to a failure. This helps me understand how difficult programming is and how much accountability software professionals have to ensure everything functions properly.

Reading about Hamilton’s accomplishments also got me to thinking about how underappreciated certain technological trailblazers are, particularly women in STEM fields. She was doing more than just coding, at a period when there were no official rules or accepted practices, she was establishing the groundwork for software engineering. The difficulty of gaining attention in a male-dominated sector is demonstrated by the fact that she created the phrase “software engineering” in order to have her work considered more seriously. I find myself wondering how many other pioneers have gone unnoticed over the years. Her experience motivates me to believe that programming is about more than simply creating code; it is also about solving problems, coming up with creative solutions, and making sure things work, especially when lives are on the line. I now have a greater regard for the history of computing and the people behind major technological advancements.

Assignment 8 – Reading Response 1 (Attractive Things Work Better)

The Nielsen Norman Group’s assertion that “attractive things operate better,” truly got me to consider how much design affects how we interact with products. Even before I have tried something, I usually think it will work well if it looks good. This makes me think of sleek programs like Instagram or Apple products, where the simple layout makes things seem easier to use and more pleasurable. It is intriguing how aesthetics may influence how usable something is and increase our tolerance for small imperfections. Even if a website or app functions well, I have found that when it appears busy or out-of-date, I get annoyed right away. This helps me understand that good design influences how people interact with and feel about technology, thus it is not just about appearances.

However, I believe it is critical to strike a balance between usability and aesthetics. Something does not necessarily work well just because it looks wonderful. I have used apps with stunning designs but were difficult to use, leading me to quickly give them up. This demonstrates that although beauty can improve an experience, it should not take the place of usefulness. A well-designed product should be simple to use and aesthetically pleasing. I now understand why businesses work so hard on user experience design, it is not just about making things seem good, it is also about making interactions feel seamless and pleasurable. I am now more conscious of how design affects my personal expectations and preferences when utilizing technology as a result of this reflection.

Midterm Project – Sweet Treats

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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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(); } }
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


 

Assignment 5: Midterm Progress

For the upcoming midterm project, I decided to code an interactive game. Now, when it came to choosing a game, I was reminded of a game I used to play when I was younger, Cooking Mama, which revolved around making different kinds of recipes. That, along with my own personal bias towards baked goods, gave me an idea. I am planning to create a memory game that is set in a bakery. The game itself is quite striaghtforward, with the user selecting a recipe, then having to select the right ingredients within the given time frame in order to make it

The game flow starts with a menu from which the user has to select an item to bake. They are then shown the ingredients required for a few seconds in order to memorize them, then are required to select those ingredients from the shelf (within a given time limit). If they manage, the item is baked, and they are shown the winning end screen. If they fail to select all the ingredients, or select the wrong ingredients, they are shown the failure end screen.

I have managed so far to create a base version of the game, just to see whether I would actually be able to code it or not. My current code consists of a Game class, which keeps track of which dish is chosen, which ingredients are needed, and whether you won or lost. In order to play the game, the Game class also stores the different “stages” of the game, namely the start screen, menu, memory phase, selection phase, and the result phase.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Game {
constructor() {
this.state = "start"; // start, menu, memory, selection, result, baking, end
this.selectedDish = null;
this.correctIngredients = [];
this.ingredientShelf = [];
this.selectedIngredients = [];
this.timer = 500; // Updated timer for selection
this.showPlayAgainButton = false; // Flag to show "Play Again" button
this.showWinningMessage = false; // Flag to show winning message
this.showFailureMessage = false; // Flag to show failure message
}
display() {
if (this.state === "start") {
this.showStartScreen();
} else if (this.state === "menu") {
this.showMenu();
} else if (this.state === "memory") {
this.showMemory();
} else if (this.state === "selection") {
this.showSelection();
} else if (this.state === "result") {
this.showResult();
} else if (this.state === "baking") {
this.showBaking();
} else if (this.state === "end") {
this.showEndScreen();
}
}
handleClick() {
if (this.state === "start") {
this.state = "menu";
} else if (this.state === "menu") {
this.handleMenuSelection();
} else if (this.state === "selection") {
this.handleIngredientSelection();
} else if (this.state === "end" && this.showPlayAgainButton) {
this.resetGame();
this.state = "menu"; // Ensure it goes back to the menu
}
}
showStartScreen() {
textAlign(CENTER, CENTER);
textSize(32);
text("Press Anywhere to Start", width / 2, height / 2);
}
showMenu() {
textAlign(CENTER, CENTER);
textSize(24);
text("Choose a Dish:", width / 2, height / 4);
let y = 200;
for (let dish of Object.keys(ingredientsData)) {
text(dish, width / 2, y);
y += 50;
}
}
handleMenuSelection() {
let y = 200;
let index = 0;
for (let dish of Object.keys(ingredientsData)) {
if (mouseY > y - 15 && mouseY < y + 15) {
this.selectedDish = dish;
this.correctIngredients = ingredientsData[dish];
this.state = "memory";
setTimeout(() => this.state = "selection", 3000); // Show memory phase briefly
this.createIngredientShelf();
}
y += 50;
index++;
}
}
showMemory() {
textAlign(CENTER, CENTER);
textSize(24);
text("Memorize These Ingredients!", width / 2, 50);
let x = 100;
for (let ingredient of this.correctIngredients) {
fill(200, 100, 100);
ellipse(x, height / 2, 50);
textSize(14);
text(ingredient, x, height / 2 + 40);
x += 150;
}
}
createIngredientShelf() {
let allIngredients = ["Flour", "Egg", "Chocolate", "Sugar", "Butter", "Milk", "Vanilla", "Apple", "Salt", "Baking Powder", "Lemon", "Cinnamon"];
this.ingredientShelf = [];
let x = 100;
for (let ingredient of allIngredients) {
this.ingredientShelf.push(new Ingredient(x, height - 100, ingredient));
x += 100;
}
}
showSelection() {
textSize(24);
text(`Select the ingredients for ${this.selectedDish}`, width / 2, 50);
text(`Time Left: ${Math.ceil(this.timer / 60)}s`, width - 100, 50);
this.timer--;
if (this.timer <= 0) {
this.state = "result";
}
this.showShelf();
}
showShelf() {
fill(200, 200, 200); // Shelf background
rect(0, height - 150, width, 150);
for (let ingredient of this.ingredientShelf) {
ingredient.display();
}
}
handleIngredientSelection() {
for (let ingredient of this.ingredientShelf) {
if (ingredient.isClicked(mouseX, mouseY)) {
ingredient.selected = !ingredient.selected;
if (ingredient.selected) {
this.selectedIngredients.push(ingredient.name);
} else {
this.selectedIngredients = this.selectedIngredients.filter(i => i !== ingredient.name);
}
}
}
if (this.selectedIngredients.length === this.correctIngredients.length) {
this.state = "result";
}
}
showResult() {
let isCorrect = this.selectedIngredients.sort().toString() === this.correctIngredients.sort().toString();
textAlign(CENTER, CENTER);
textSize(32);
if (isCorrect) {
this.showWinningMessage = true;
this.showFailureMessage = false;
this.state = "baking";
setTimeout(() => {
this.state = "end";
this.showPlayAgainButton = true; // Show play again button
}, 3000);
} else {
this.showFailureMessage = true;
this.showWinningMessage = false;
this.state = "end";
this.showPlayAgainButton = true; // Show play again button immediately
}
}
showBaking() {
textAlign(CENTER, CENTER);
textSize(32);
text("Baking...", width / 2, height / 2);
setTimeout(() => {
text("Ding! Your dish is ready!", width / 2, height / 2 + 50);
}, 2000);
}
showEndScreen() {
if (this.showWinningMessage) {
textAlign(CENTER, CENTER);
textSize(32);
text(`Your ${this.selectedDish} is now ready!`, width / 2, height / 2);
this.showPlayAgainButton = true; // Immediately show the Play Again button
}
if (this.showFailureMessage) {
textAlign(CENTER, CENTER);
textSize(32);
text("Oh no! Those were the wrong ingredients!", width / 2, height / 2);
}
if (this.showPlayAgainButton) {
// Display "Play Again" button
fill(200);
rect(width / 2 - 100, height / 2 + 80, 200, 50);
fill(0);
textSize(24);
text("Play Again", width / 2, height / 2 + 105);
}
}
resetGame() {
this.selectedDish = null;
this.correctIngredients = [];
this.ingredientShelf = [];
this.selectedIngredients = [];
this.timer = 310; // Reset timer
this.showPlayAgainButton = false; // Reset play again button
this.showWinningMessage = false; // Reset winning message
this.showFailureMessage = false; // Reset failure message
}
}
class Game { constructor() { this.state = "start"; // start, menu, memory, selection, result, baking, end this.selectedDish = null; this.correctIngredients = []; this.ingredientShelf = []; this.selectedIngredients = []; this.timer = 500; // Updated timer for selection this.showPlayAgainButton = false; // Flag to show "Play Again" button this.showWinningMessage = false; // Flag to show winning message this.showFailureMessage = false; // Flag to show failure message } display() { if (this.state === "start") { this.showStartScreen(); } else if (this.state === "menu") { this.showMenu(); } else if (this.state === "memory") { this.showMemory(); } else if (this.state === "selection") { this.showSelection(); } else if (this.state === "result") { this.showResult(); } else if (this.state === "baking") { this.showBaking(); } else if (this.state === "end") { this.showEndScreen(); } } handleClick() { if (this.state === "start") { this.state = "menu"; } else if (this.state === "menu") { this.handleMenuSelection(); } else if (this.state === "selection") { this.handleIngredientSelection(); } else if (this.state === "end" && this.showPlayAgainButton) { this.resetGame(); this.state = "menu"; // Ensure it goes back to the menu } } showStartScreen() { textAlign(CENTER, CENTER); textSize(32); text("Press Anywhere to Start", width / 2, height / 2); } showMenu() { textAlign(CENTER, CENTER); textSize(24); text("Choose a Dish:", width / 2, height / 4); let y = 200; for (let dish of Object.keys(ingredientsData)) { text(dish, width / 2, y); y += 50; } } handleMenuSelection() { let y = 200; let index = 0; for (let dish of Object.keys(ingredientsData)) { if (mouseY > y - 15 && mouseY < y + 15) { this.selectedDish = dish; this.correctIngredients = ingredientsData[dish]; this.state = "memory"; setTimeout(() => this.state = "selection", 3000); // Show memory phase briefly this.createIngredientShelf(); } y += 50; index++; } } showMemory() { textAlign(CENTER, CENTER); textSize(24); text("Memorize These Ingredients!", width / 2, 50); let x = 100; for (let ingredient of this.correctIngredients) { fill(200, 100, 100); ellipse(x, height / 2, 50); textSize(14); text(ingredient, x, height / 2 + 40); x += 150; } } createIngredientShelf() { let allIngredients = ["Flour", "Egg", "Chocolate", "Sugar", "Butter", "Milk", "Vanilla", "Apple", "Salt", "Baking Powder", "Lemon", "Cinnamon"]; this.ingredientShelf = []; let x = 100; for (let ingredient of allIngredients) { this.ingredientShelf.push(new Ingredient(x, height - 100, ingredient)); x += 100; } } showSelection() { textSize(24); text(`Select the ingredients for ${this.selectedDish}`, width / 2, 50); text(`Time Left: ${Math.ceil(this.timer / 60)}s`, width - 100, 50); this.timer--; if (this.timer <= 0) { this.state = "result"; } this.showShelf(); } showShelf() { fill(200, 200, 200); // Shelf background rect(0, height - 150, width, 150); for (let ingredient of this.ingredientShelf) { ingredient.display(); } } handleIngredientSelection() { for (let ingredient of this.ingredientShelf) { if (ingredient.isClicked(mouseX, mouseY)) { ingredient.selected = !ingredient.selected; if (ingredient.selected) { this.selectedIngredients.push(ingredient.name); } else { this.selectedIngredients = this.selectedIngredients.filter(i => i !== ingredient.name); } } } if (this.selectedIngredients.length === this.correctIngredients.length) { this.state = "result"; } } showResult() { let isCorrect = this.selectedIngredients.sort().toString() === this.correctIngredients.sort().toString(); textAlign(CENTER, CENTER); textSize(32); if (isCorrect) { this.showWinningMessage = true; this.showFailureMessage = false; this.state = "baking"; setTimeout(() => { this.state = "end"; this.showPlayAgainButton = true; // Show play again button }, 3000); } else { this.showFailureMessage = true; this.showWinningMessage = false; this.state = "end"; this.showPlayAgainButton = true; // Show play again button immediately } } showBaking() { textAlign(CENTER, CENTER); textSize(32); text("Baking...", width / 2, height / 2); setTimeout(() => { text("Ding! Your dish is ready!", width / 2, height / 2 + 50); }, 2000); } showEndScreen() { if (this.showWinningMessage) { textAlign(CENTER, CENTER); textSize(32); text(`Your ${this.selectedDish} is now ready!`, width / 2, height / 2); this.showPlayAgainButton = true; // Immediately show the Play Again button } if (this.showFailureMessage) { textAlign(CENTER, CENTER); textSize(32); text("Oh no! Those were the wrong ingredients!", width / 2, height / 2); } if (this.showPlayAgainButton) { // Display "Play Again" button fill(200); rect(width / 2 - 100, height / 2 + 80, 200, 50); fill(0); textSize(24); text("Play Again", width / 2, height / 2 + 105); } } resetGame() { this.selectedDish = null; this.correctIngredients = []; this.ingredientShelf = []; this.selectedIngredients = []; this.timer = 310; // Reset timer this.showPlayAgainButton = false; // Reset play again button this.showWinningMessage = false; // Reset winning message this.showFailureMessage = false; // Reset failure message } }
class Game {
  constructor() {
    this.state = "start"; // start, menu, memory, selection, result, baking, end
    this.selectedDish = null;
    this.correctIngredients = [];
    this.ingredientShelf = [];
    this.selectedIngredients = [];
    this.timer = 500; // Updated timer for selection
    this.showPlayAgainButton = false; // Flag to show "Play Again" button
    this.showWinningMessage = false; // Flag to show winning message
    this.showFailureMessage = false; // Flag to show failure message
  }

  display() {
    if (this.state === "start") {
      this.showStartScreen();
    } else if (this.state === "menu") {
      this.showMenu();
    } else if (this.state === "memory") {
      this.showMemory();
    } else if (this.state === "selection") {
      this.showSelection();
    } else if (this.state === "result") {
      this.showResult();
    } else if (this.state === "baking") {
      this.showBaking();
    } else if (this.state === "end") {
      this.showEndScreen();
    }
  }

  handleClick() {
    if (this.state === "start") {
      this.state = "menu";
    } else if (this.state === "menu") {
      this.handleMenuSelection();
    } else if (this.state === "selection") {
      this.handleIngredientSelection();
    } else if (this.state === "end" && this.showPlayAgainButton) {
      this.resetGame();
      this.state = "menu"; // Ensure it goes back to the menu
    }
  }

  showStartScreen() {
    textAlign(CENTER, CENTER);
    textSize(32);
    text("Press Anywhere to Start", width / 2, height / 2);
  }

  showMenu() {
    textAlign(CENTER, CENTER);
    textSize(24);
    text("Choose a Dish:", width / 2, height / 4);
    let y = 200;
    for (let dish of Object.keys(ingredientsData)) {
      text(dish, width / 2, y);
      y += 50;
    }
  }

  handleMenuSelection() {
    let y = 200;
    let index = 0;
    for (let dish of Object.keys(ingredientsData)) {
      if (mouseY > y - 15 && mouseY < y + 15) {
        this.selectedDish = dish;
        this.correctIngredients = ingredientsData[dish];
        this.state = "memory";
        setTimeout(() => this.state = "selection", 3000); // Show memory phase briefly
        this.createIngredientShelf();
      }
      y += 50;
      index++;
    }
  }

  showMemory() {
    textAlign(CENTER, CENTER);
    textSize(24);
    text("Memorize These Ingredients!", width / 2, 50);
    let x = 100;
    for (let ingredient of this.correctIngredients) {
      fill(200, 100, 100);
      ellipse(x, height / 2, 50);
      textSize(14);
      text(ingredient, x, height / 2 + 40);
      x += 150;
    }
  }

  createIngredientShelf() {
    let allIngredients = ["Flour", "Egg", "Chocolate", "Sugar", "Butter", "Milk", "Vanilla", "Apple", "Salt", "Baking Powder", "Lemon", "Cinnamon"];
    this.ingredientShelf = [];
    let x = 100;
    for (let ingredient of allIngredients) {
      this.ingredientShelf.push(new Ingredient(x, height - 100, ingredient));
      x += 100;
    }
  }

  showSelection() {
    textSize(24);
    text(`Select the ingredients for ${this.selectedDish}`, width / 2, 50);
    text(`Time Left: ${Math.ceil(this.timer / 60)}s`, width - 100, 50);
    this.timer--;
    if (this.timer <= 0) {
      this.state = "result";
    }
    this.showShelf();
  }

  showShelf() {
    fill(200, 200, 200); // Shelf background
    rect(0, height - 150, width, 150);
    for (let ingredient of this.ingredientShelf) {
      ingredient.display();
    }
  }

  handleIngredientSelection() {
    for (let ingredient of this.ingredientShelf) {
      if (ingredient.isClicked(mouseX, mouseY)) {
        ingredient.selected = !ingredient.selected;
        if (ingredient.selected) {
          this.selectedIngredients.push(ingredient.name);
        } else {
          this.selectedIngredients = this.selectedIngredients.filter(i => i !== ingredient.name);
        }
      }
    }
    if (this.selectedIngredients.length === this.correctIngredients.length) {
      this.state = "result";
    }
  }

  showResult() {
    let isCorrect = this.selectedIngredients.sort().toString() === this.correctIngredients.sort().toString();
    textAlign(CENTER, CENTER);
    textSize(32);
    if (isCorrect) {
      this.showWinningMessage = true;
      this.showFailureMessage = false;
      this.state = "baking";
      setTimeout(() => {
        this.state = "end";
        this.showPlayAgainButton = true; // Show play again button
      }, 3000);
    } else {
      this.showFailureMessage = true;
      this.showWinningMessage = false;
      this.state = "end";
      this.showPlayAgainButton = true; // Show play again button immediately
    }
  }

  showBaking() {
    textAlign(CENTER, CENTER);
    textSize(32);
    text("Baking...", width / 2, height / 2);
    setTimeout(() => {
      text("Ding! Your dish is ready!", width / 2, height / 2 + 50);
    }, 2000);
  }

  showEndScreen() {
    if (this.showWinningMessage) {
      textAlign(CENTER, CENTER);
      textSize(32);
      text(`Your ${this.selectedDish} is now ready!`, width / 2, height / 2);
      this.showPlayAgainButton = true; // Immediately show the Play Again button
    }

    if (this.showFailureMessage) {
      textAlign(CENTER, CENTER);
      textSize(32);
      text("Oh no! Those were the wrong ingredients!", width / 2, height / 2);
    }

    if (this.showPlayAgainButton) {
      // Display "Play Again" button
      fill(200);
      rect(width / 2 - 100, height / 2 + 80, 200, 50);
      fill(0);
      textSize(24);
      text("Play Again", width / 2, height / 2 + 105);
    }
  }

  resetGame() {
    this.selectedDish = null;
    this.correctIngredients = [];
    this.ingredientShelf = [];
    this.selectedIngredients = [];
    this.timer = 310; // Reset timer
    this.showPlayAgainButton = false; // Reset play again button
    this.showWinningMessage = false; // Reset winning message
    this.showFailureMessage = false; // Reset failure message
  }
}

While building the game, I am currently and further expecting to face challenges. The most prominant being:

  • Managing the game states: the game has multiple stages (as I mentioned earlier), and I want to ensure that there are smooth transitions between these stages
  • User interaction: the player’s selection should be correctly registered, and the game must check if the chosen ingredients match the correct sets already stored.
  • Game reset: the game should clear the previous players seections and reset for a new round automatically
  • Graphics creation and implementation: The graphics need to be created, and then also smoothly implemented within the game, not looking patchy or out of place.

The game in it’s current format works, but doesn’t look very appealing. I am currently using circles as placeholders for the ingredients, though I plan to create graphics for the backgrounds, the ingredients, and the start and end screens, as well as an animation for the “baking” scene if the user succeeds. There will also be background music implemented, with a “ding” sound effect if the user succeeds and the item successfully is baked.

 

Assignment 5 – Reading Response

This week’s reading was quite fascinating. It explored the way computers “see” pictures or videos. I had always known that a computer would not have the holistic knowledge to interpret images like we do, but this article helped explain this vision in a more in-depth way, bringing up questions I really hadn’t considered before. As humans, our brains are automatically wired to understand what we’re looking at as we already have the necessary context to understand the item and its use. Computers, however, only see pixels without any specific meaning. Our brains are capable of so much that we take for granted, such as easy recognition, adapting to different lighting, and naturally filtering out unnecessary information. On the other hand, computers need to be specially designed to carry out any of the above-mentioned tasks. They just simply lack the holistic approach that we are so attuned to.

This article also goes on to mention several ways we can aid computers to make their vision more effective, such as the use of specialized cameras and lenses to help their tracking needs. Moreover, this article grapples with the relationship between these tracking capabilities and interactive artwork. It explores the dual nature of this technology, as it can create wonderful interactive experiences, while still enabling surveilamce, a duality which is uncomfortable for many to accept. The article gives examples of several artists who decided to commentate on this dual nature, effectively creating art that uses technology to comment on surveillance itself, creating artowrks that serve as both an interactive piece, but also an extensive thought problem.

Assignment 4 – Reading Response

While reading Don Norman’s “The Design of Everyday Things”, I was heavily agreeing with his notes on poor design making everyday tasks unnecessarily difficult. An example I found myself thinking of was of the commonplace microwave. Every student lounge in the NYUAD dorms has been provided with one, but each has a slightly different usability, making it an irritating chore to relearn where all the controls are. Many have excessive buttons with controls such as “Auto-Defrost”, “Popcorn”, or have random food symbols with no clear indication of what that setting does. The button layout is unintuitive, and can make tasks like setting a power level or temperature more tedious than it needs to be. This design could very easily be improved, by removing unnecessary additions, and making more-utilized features like the power-level or the timer more prominent. The arrangement of buttons should logically reflect their use. If there’s a sequence required (such as first setting a temperature, then a timer), the layout should guide users intuitively from left to right or top to bottom.

Terrible design aside, I found many of Norman’s points relevant in terms of interactive media. A key aspect of IM is the user interaction, and a user will not fully appreciate the functionality of a design if it is very unintuitive to use or tedious to learn. Websites, apps, video games, and even art pieces, should have visual cues that are easy to understand and distinguishable. Design shouldn’t suffer due to functionality, but that doesn’t mean that it shouldn’t be functional at all. Users should be able to recieve immediate, meaningful responses to their actions, not a frustrating mess.

Assignment 4: In The Shadow

This week’s assignment tasked us with creating a generative text piece. I decided to take a slightly different, more artwork centered piece. I used one of my favorite books, “Before The Coffee Gets Cold” by Toshikazu Kawaguchi as an inspiration behind my piece. The book details may different relationships, and I used that as an inspiration for my piece. The code will generate a random paragraph from some predetermined sentences, and display it in the background, establishing a situation. The viewer can then click through the possible relationships it applies to, and try to see how the nature of the relationship may alter the meaning of the generated paragraph.

A section I am particularly proud of is the generation of said sentences, as they are constructed using the random() function, and as such provides different situations each time the page is refreshed.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//generating the text
function generateText() {
let openings = [
"'Oh gosh, is that the time? Sorry, I have to go,' the man mumbled evasively.",
"'I didn't expect this conversation to go this way,' she admitted hesitantly.",
"'Are you really leaving?' he asked, his voice barely above a whisper."
];
let middles = [
"She looked at him with uncertainty. The clocks in the café all showed different times, adding to the confusion.",
"His fingers absentmindedly traced the rim of his coffee cup, avoiding her stare.",
"The sepia-toned café seemed frozen in time, yet everything was changing for them."
];
let endings = [
"'Don’t I deserve an explanation?' she finally asked, breaking the silence.",
"He sighed, glancing at his watch, knowing there was no easy answer.",
"Without another word, he stood up and walked toward the exit, leaving her behind."
];
// Construct generative text
generatedText = random(openings) + "\n\n" + random(middles) + "\n\n" + random(endings);
}
//generating the text function generateText() { let openings = [ "'Oh gosh, is that the time? Sorry, I have to go,' the man mumbled evasively.", "'I didn't expect this conversation to go this way,' she admitted hesitantly.", "'Are you really leaving?' he asked, his voice barely above a whisper." ]; let middles = [ "She looked at him with uncertainty. The clocks in the café all showed different times, adding to the confusion.", "His fingers absentmindedly traced the rim of his coffee cup, avoiding her stare.", "The sepia-toned café seemed frozen in time, yet everything was changing for them." ]; let endings = [ "'Don’t I deserve an explanation?' she finally asked, breaking the silence.", "He sighed, glancing at his watch, knowing there was no easy answer.", "Without another word, he stood up and walked toward the exit, leaving her behind." ]; // Construct generative text generatedText = random(openings) + "\n\n" + random(middles) + "\n\n" + random(endings); }
//generating the text
function generateText() {
  let openings = [
    "'Oh gosh, is that the time? Sorry, I have to go,' the man mumbled evasively.",
    "'I didn't expect this conversation to go this way,' she admitted hesitantly.",
    "'Are you really leaving?' he asked, his voice barely above a whisper."
  ];
  
  let middles = [
    "She looked at him with uncertainty. The clocks in the café all showed different times, adding to the confusion.",
    "His fingers absentmindedly traced the rim of his coffee cup, avoiding her stare.",
    "The sepia-toned café seemed frozen in time, yet everything was changing for them."
  ];
  
  let endings = [
    "'Don’t I deserve an explanation?' she finally asked, breaking the silence.",
    "He sighed, glancing at his watch, knowing there was no easy answer.",
    "Without another word, he stood up and walked toward the exit, leaving her behind."
  ];
  
  // Construct generative text
  generatedText = random(openings) + "\n\n" + random(middles) + "\n\n" + random(endings);
}

The most frustrating part of this assignment definitely was creating each silhouette for the relationships. I wanted to make them seem organic and not blocky-looking, so I had to hardcode each shape into position, which was not difficult, but was definitely tedious. I want to research into whether there is a way to directly import unique shapes into p5.js  so that I can save myself any future struggle similar to this. While the end result is good, this part by far took the longest and most concentration to complete.

Assignment 3 – Reading Response

Reading this chapter made me realize how often we misuse the word “interactive.” Before, I thought anything that let me click, tap, or control something was interactive. But Crawford argues that real interactivity is more than just pressing buttons—it’s about a meaningful exchange between the user and the system. A slideshow where I click “next” isn’t really interactive because it doesn’t respond in a thoughtful way. It just follows a script. This made me think about how many things we call interactive today—like online quizzes or simple video games—are actually just reacting, not truly engaging with the user. It kind of changed how I look at apps and games because I started noticing which ones actually respond to my choices and which ones just make me feel like they do.

One of the most interesting points Crawford makes is his comparison between interactivity and conversation. A real conversation isn’t just one person talking while the other nods—it’s about listening, responding, and adapting to what the other person says. I never really thought about technology in that way, but it makes sense. The best apps, games, or even websites feel like they “listen” to you and react in a way that makes sense, while others just follow a script no matter what you do. It made me appreciate good design more, and it also made me think about how this applies to real life. Even with people, there’s a difference between actually engaging with someone and just waiting for your turn to talk. Crawford’s ideas stuck with me because they apply to more than just interactive design—they apply to how we communicate and connect in general.

Assignment 3: Following the Crowd

For this week’s assignment, we were tasked with creating a piece of generative artwork. Fortunately, I was hit with inspiration pretty soon after leaving class. This week was candidate weekend for new potential students, and I got to watch them crowd up around volunteers. This inspired me to create this artwork, which has a grid of blocks, with an interactive feature of all the blocks pointing towards the mouse.

We were asked to use Object-Oriented Programming for this assignment, so I started by first programming first block to see whether I could actually get the triangle to follow the mouse around or not. In my previous assignment, I had learnt how to use the rotate() and translate() functions, but this time, I needed to have it rotate according to angle of my mouse. This proved to be the most challenging part of this task. I struggled with calculating the angle (research eventually led me to the atan2() function), and then actually having the triangle rotate about its center. I did end having to hardcode some values into the function, but its functionality felt satisfying to achieve.

A different part of the code that I am especially proud of is the random rotation of the blocks towards the bottom half of the artwork. I used the map() function we had discussed in class to adjust for the intensity of the rotation depending on the row the blocks were in. I think it turned out quite organic-looking!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//rotating the rows at the bottom half
if (this.row >= rows - 4) {
//making the level of rotation more intense as the grid goes on using map() function
let intensity = map(this.row, rows - 4, rows - 1, 5, 25); //increasign rotation for lower rows
//converting to radians for the rotate() function
this.fixedRotation = radians(random(-intensity, intensity));
} else {
this.fixedRotation = 0; //no rotation for the top rows
}
//rotating the rows at the bottom half if (this.row >= rows - 4) { //making the level of rotation more intense as the grid goes on using map() function let intensity = map(this.row, rows - 4, rows - 1, 5, 25); //increasign rotation for lower rows //converting to radians for the rotate() function this.fixedRotation = radians(random(-intensity, intensity)); } else { this.fixedRotation = 0; //no rotation for the top rows }
//rotating the rows at the bottom half
    if (this.row >= rows - 4) {
      //making the level of rotation more intense as the grid goes on using map() function
      let intensity = map(this.row, rows - 4, rows - 1, 5, 25); //increasign rotation for lower rows
      //converting to radians for the rotate() function
      this.fixedRotation = radians(random(-intensity, intensity));
    } else {
      this.fixedRotation = 0; //no rotation for the top rows
    }

Overall, I am quite glad with the end result of my piece. Though in the future, I would like play a bit more with the colors. Currently, it is black and white, as I couldn’t quite get a colored gradient without it looking odd. I want to also figure out how to fix the center of rotation without hardcoding it. Regardless, I am still very happy with the interactive feature of the piece.