CAPYCOMPOST - Denisse Rojas - Flora Weil
Our project focuses on the topic of environmental sustainability, specifically the role of waste management in agriculture.
The idea of the game came from our desire to emphasise composting as a safer, more sustainable way to fertilize soil. Currently, agricultural practices such as slash-and-burn are common and quick, but they can be extremely damaging to the environment. For instance, they have been linked as one of the main causes of the September 2024 wildfires in Latin America. By stressing the use of carbon-rich (brown) and nitrogen-rich (green) resources, our game teaches players how to make compost while encouraging viable and safe agricultural practices.
Our game, which draws inspiration from the mechanics of Overcooked, requires players to gather ingredients and prepare compost under a time restriction of one minute and thirty seconds.
The players produce compost by selecting the right materials, processing them, and applying the compost to a plant, which grows as more compost is made. This approach teaches real composting methods to the audience and keeps them engaged while doing so.
Our game was created for young adults and children and aims to raise awareness of sustainable fertilizing methods interactively. By combining education and entertainment, we show how composting benefits plant growth, ultimately inspiring players to adopt eco-friendly habits in their everyday lives.
In this stage, we had planned to use servos, buttons, LEDs, a slider, DC motors, a timer, and a buzzer. Our choices were based on our understanding of how to effectively use these components and their capabilities in conjunction with each other. Other materials like cardboard and paper were selected based on how moldable and stable they were to construct the prototype.
We wanted to build a game that was both unpredictable and enjoyable when developing our product proposal. We planned to achieve this by randomizing the recipe assignments and ingredient choices, adding excitement to each round and encouraging quick thinking. Constant feedback would also be given to the user for each action like the pot lighting up when the correct ingredient is chosen and the plant growing as compost gets made. These visual signals would reinforce the player actions and guide them through the game.
* These sketches are from the very first version of the game. Since then, the design has constantly changed as well as the components used. These decisions were made after talking to different members of the IMA faculty and evaluating what was best for our product.
USER TESTING
The following picture shows the first version of the prototype presented at the user testing session. My selected user is trying the prototype for the first time, allowing me to see what modifications I could make to the product.
Observation: I discovered several faults with the mechanism of picking the ingredients. As people were testing it, they became frustrated by how the wrong ingredients were picked by the computer even when they had selected the right one at the right time.
MODIFICATIONS AFTER USER TESTING
MODIFICATIONS AFTER THE SECOND USER TESTING SESSION
FINAL VERSION OF THE PROTOTYPE
VIDEOS OF PEOPLE PLAYING CAPYCOMPOST
CONCLUSIONS
The purpose of my project is to re-emphasize the importance of composting by teaching people how to do it and displaying it as a fun and sustainable agricultural practice.
Following the user testing sessions and watching people play with my game, I realized how my goal was more than partially achieved. People were able to subconsciously learn the steps needed to make quick compost. They were also exposed to different recipes that followed the ideal ratio (3 brown materials: 1 green material) for preparing an effective compost pile.
In terms of interactivity, the players not only followed the composting steps, but the final phase provided a clear, transformed outcome that made the playthrough more meaningful and memorable. The product also produced visual signals as feedback to show the user which actions were correct and which steps weren't. However, I believe I could have added more sensory responses to make the feedback more varied. Also, highlight the ingredients used to make the compost, giving more information on why they were chosen.
If I had more time I would add more sensory components, replacing actions like pressing a button to start the game and choose the ingredients. I would also increase the duration of the game so the user can make more than one recipe per game.
I believe that every step back in this process taught me to always look for people to test the product to identify errors I cannot see, evaluate feedback before applying it, and how important it is to organize the time I will dedicate to each part of the process.
This experience has solidified my knowledge of what we have seen in class and has given me the opportunity to work on something I consider personally meaningful.
FINAL VERSION OF THE CODE
// A countdown that displays the time the user has left
// This was adapted from a tutorial found here:
//https://www.youtube.com/watch?v=WtwSIfccals
#define numberOfSeconds(_time_) ((_time_ / 1000) % 60)
#define numberOfMinutes(_time_) (((_time_ / 1000) / 60) % 60)
#include <Servo.h>
#include <TM1637Display.h> // for countdown
const uint8_t OFF[] = { 0, 0, 0, 0 };
const uint8_t PLAY[] = { B01110011, B00111000, B01011111, B01101110 };
TM1637Display display(13, 12); //pins in which the digital timer display is connected to
long startTime = 0;
const long endTime = 90000; // This marks the time duration of the game (1 minute and thirty seconds)
const int buttonpin_start = A2; // Start button
int buttonState_start = 0;
bool start = false;
// this keeps track of the amount of each ingredients being selected per round
int appleCount = 0;
int carrotCount = 0;
int leafCount = 0;
int cardboardCount = 0;
int coffeeCount = 0;
// this settles the position in which the servo will travel between
int appleDeg = 45;
int cardboardDeg = 75;
int leafDeg = 100;
int coffeeDeg = 125;
int carrotDeg = 145;
// Servo for picking the ingredient
Servo servo_ing;
int pos = 0;
int savedPos = 0;
int selectedRecipe = 0;
// Servos for the 3 banners
Servo banner1;
Servo banner2;
Servo banner3;
Servo plant; // Servo for the plant
int posPlant = 0;
const int paddleSwitchPin = 2; // Paddle switch pin (represents the pot and the lid)
int paddleSwitchState = 0;
//Cooking Servo
Servo cookingServo;
int cookingServo_pin = 8;
unsigned long cookingStartTime = 0;
bool cookingStarted = false;
bool paddleSwitchClosed = false;
long heatStartTime = 0;
long paddlePressTime = 0;
const int buttonpin_si = A1; // button for selecting the ingredients
int buttonState_si = 0;
// LED lights inside the pot (Red = apple, Orange = carrot, Leaf = green, Cardboard = blue, Coffee Beans = yellow)
int led_pinRed = 10;
int led_pinGreen = 11;
int led_pinBlue = 9;
int red;
int green;
int blue;
bool wrongIng = false;
// for managing time (replacement to delay())
long previousMillis = millis();
long interval = 1000;
long previousBlinkMillis = 0;
long blinkIntevral = 500;
const int debounceDelay = 50;
long lastDebounceTime = 0;
int lastSensorValue = LOW;
long lastPressTime = 0;
int lastSavedPos = -1;
int completedRecipesCount = 0; // evaluates if a recipe was completed
int completedCookingCount = 0; // evaluates is a recipe was cooked correctly
// led's next to pot
int led_redCook = A3;
int led_greenCook = A4;
bool lastButtonState = LOW;
bool recipeReadyToCook = false;
bool recipeJustCompleted = false;
void setup() {
servo_ing.attach(6);
servo_ing.write(45);
banner1.attach(3);
banner2.attach(4);
banner3.attach(5);
cookingServo.attach(cookingServo_pin);
cookingServo.write(90);
plant.attach(7);
plant.write(posPlant);
banner1.write(180);
banner2.write(180);
banner3.write(180);
digitalWrite(led_redCook, LOW);
digitalWrite(led_greenCook, LOW);
// from countdown tutorial
display.setBrightness(0x0c);
display.setSegments(OFF);
Serial.begin(9600);
pinMode(led_pinRed, OUTPUT);
pinMode(led_pinGreen, OUTPUT);
pinMode(led_pinBlue, OUTPUT);
pinMode(led_greenCook, OUTPUT);
pinMode(led_redCook, OUTPUT);
pinMode(paddleSwitchPin, INPUT);
pinMode(buttonpin_si, INPUT);
pinMode(buttonpin_start, INPUT);
}
void loop() {
long currentMillis = millis();
recipeJustCompleted = false;
buttonState_start = digitalRead(buttonpin_start);
buttonState_si = digitalRead(buttonpin_si);
paddleSwitchState = digitalRead(paddleSwitchPin);
if (buttonState_start == HIGH && start == false) {
start = true;
startTime = millis();
Serial.println("Game started!");
pickRandomRecipe(); // at this point, one of the three recipes is chosen for the user to followe
}
if (start == true) {
if (millis() - startTime <= endTime) { // as long as the time is less than 1 minute 30 seconds
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
randomSeed(analogRead(A5));
pos = random(1, 6);
// this if statement helps define to which position the servo should move among the 5 positions previously established.
if (pos == 1) {
servo_ing.write(appleDeg);
} else if (pos == 2) {
servo_ing.write(cardboardDeg);
} else if (pos == 3) {
servo_ing.write(leafDeg);
} else if (pos == 4) {
servo_ing.write(coffeeDeg);
} else if (pos == 5) {
servo_ing.write(carrotDeg);
}
Serial.print("Current counts - Apple: ");
Serial.print(appleCount);
Serial.print(", Carrot: ");
Serial.print(carrotCount);
Serial.print(", Leaf: ");
Serial.print(leafCount);
Serial.print(", Cardboard: ");
Serial.print(cardboardCount);
Serial.print(", Coffee: ");
Serial.println(coffeeCount);
Serial.print("Position: ");
Serial.println(pos);
}
countdown();
if (recipeReadyToCook == false) {
if (buttonState_si == HIGH && lastButtonState == LOW) {
if (millis() - lastPressTime > debounceDelay) {
lastPressTime = millis();
if (pos != lastSavedPos) {
savedPos = pos;
lastSavedPos = savedPos;
Serial.print("Saved position: ");
Serial.println(savedPos);
selectIngredients();
checkIfRecipeIsComplete();
if (wrongIng == true) {
offLED();
resetGameWrongIngredient();
previousBlinkMillis = millis();
wrongIng = false;
}
}
}
}
} else if (recipeReadyToCook == true){
handleCooking();
}
}
}
}
// the function starts to work once the recipe is ready
void handleCooking() {
setColor(255, 255, 255);
digitalWrite(led_redCook, LOW);
digitalWrite(led_greenCook, LOW);
if (recipeReadyToCook == true) {
digitalWrite(led_redCook, HIGH);
}
if (recipeReadyToCook == true && paddleSwitchClosed == false && paddleSwitchState == HIGH) {
paddleSwitchClosed = true;
cookingStartTime = millis();
cookingStarted = true;
Serial.println("Paddle switch closed. Cooking can start!");
}
// once the paddle switch it closed, it counts how much time the recipe is cooking
if (paddleSwitchClosed) {
long currentTime = millis();
long elapsedTime = currentTime - cookingStartTime;
if (paddleSwitchState == HIGH && elapsedTime <= 6000) {
digitalWrite(led_greenCook, HIGH);
digitalWrite(led_redCook, LOW);
int servoSpeed = map(elapsedTime, 0, 6000, 0, 89);
cookingServo.write(servoSpeed); // Rotate clockwise, slowing down over time
} else {
cookingServo.write(90); // Stop the servo
digitalWrite(led_greenCook, LOW);
if (cookingStarted) {
// Evaluate cooking result
if (elapsedTime >= 3000 && elapsedTime <= 5000) {
Serial.println("Recipe cooked correctly!");
completedCookingCount++;
resetGameCompleteRecipe();
plantGrows();
} else if (elapsedTime < 3000) {
Serial.println("Recipe not cooked correctly!");
resetGameWithPenalty();
} else {
Serial.println("Recipe burned");
resetGameWithPenalty();
}
recipeReadyToCook = false;
recipeJustCompleted = false;
cookingStarted = false;
}
paddleSwitchClosed = false; // Reset for the next cooking cycle
}
}
}
// https://www.thegeekpub.com/277872/arduino-rgb-led-tutorial/
// this is a tutorial on how to make the RGB light work and change colours
void setColor(int R, int G, int B) {
analogWrite(led_pinRed, R);
analogWrite(led_pinGreen, G);
analogWrite(led_pinBlue, B);
}
// Turn off LED lights
void offLED() {
digitalWrite(led_pinRed, LOW);
digitalWrite(led_pinGreen, LOW);
digitalWrite(led_pinBlue, LOW);
}
// Select ingredients based on servo position
void selectIngredients() { // READY
int currentPosition = servo_ing.read();
String selectedIngredient = "";
if (currentPosition >= appleDeg - 7 && currentPosition <= appleDeg + 7) {
selectedIngredient = "apple";
} else if (currentPosition >= cardboardDeg - 7 && currentPosition <= cardboardDeg + 7) {
selectedIngredient = "cardboard";
} else if (currentPosition >= leafDeg - 7 && currentPosition <= leafDeg + 7) {
selectedIngredient = "leaf";
} else if (currentPosition >= coffeeDeg - 7 && currentPosition <= coffeeDeg + 7) {
selectedIngredient = "coffee";
} else if (currentPosition >= carrotDeg - 7 && currentPosition <= carrotDeg + 7) {
selectedIngredient = "carrot";
}
if (selectedIngredient != "") {
Serial.println("Selected Ingredient: " + selectedIngredient);
checkIngredientSelection(selectedIngredient);
} else {
Serial.println("No ingredient selected at this position");
}
}
void wrongSelection() {
Serial.println("Wrong ingredient");
wrongIng = true;
previousBlinkMillis = millis();
ledState = HIGH;
}
void checkIngredientSelection(String selectedIngredient) {
bool isCorrect = false;
if (selectedRecipe == 1) {
if (selectedIngredient == "apple" && appleCount < 1) {
appleCount++;
isCorrect = true;
} else if (selectedIngredient == "leaf" && leafCount < 1) {
leafCount++;
isCorrect = true;
} else if (selectedIngredient == "cardboard" && cardboardCount < 1){
cardboardCount++;
isCorrect = true;
} else if (selectedIngredient == "coffee" && coffeeCount < 1){
coffeeCount++;
isCorrect = true;
}
} else if (selectedRecipe == 2) {
if (selectedIngredient == "carrot" && carrotCount < 1) {
carrotCount++;
isCorrect = true;
} else if (selectedIngredient == "leaf" && leafCount < 2) {
leafCount++;
isCorrect = true;
} else if (selectedIngredient == "cardboard" && cardboardCount < 1) {
cardboardCount++;
isCorrect = true;
}
} else if (selectedRecipe == 3) {
if (selectedIngredient == "apple" && appleCount < 1) {
appleCount++;
isCorrect = true;
} else if (selectedIngredient == "cardboard" && cardboardCount < 2) {
cardboardCount++;
isCorrect = true;
} else if (selectedIngredient == "leaf" && leafCount < 1) {
leafCount++;
isCorrect = true;
}
}
if (isCorrect) {
Serial.println("Correct ingredient! " + selectedIngredient + " added.");
Serial.println("Current counts - Apple: " + String(appleCount) + ", Carrot: " + String(carrotCount) + ", Leaf: " + String(leafCount) + ", Cardboard: " + String(cardboardCount) + ", Coffee: " + String(coffeeCount));
if (selectedIngredient == "apple") setColor(255, 0, 0);
else if (selectedIngredient == "cardboard") setColor(0, 0, 255);
else if (selectedIngredient == "leaf") setColor(0, 255, 0);
else if (selectedIngredient == "coffee") setColor(255, 230, 0);
else if (selectedIngredient == "carrot") setColor(255, 165, 0);
} else {
wrongSelection();
}
checkIfRecipeIsComplete();
}
// Pick a random recipe
void pickRandomRecipe() {
randomSeed(analogRead(A5));
int randomBanner = random(1, 4);
Serial.print("Random banner selected: ");
Serial.println(randomBanner);
if (randomBanner == 1) {
Serial.println("Recipe 1: 1 Apple, 1 Leaf, 1 Cardboard, 1 Coffee Beans");
selectedRecipe = 1;
banner1.write(90);
} else if (randomBanner == 2) {
Serial.println("Recipe 2: 1 Carrot, 2 Leafs, 1 Cardboard");
selectedRecipe = 2;
banner2.write(90);
} else if (randomBanner == 3) {
Serial.println("Recipe 3: 1 Apple, 2 Cardboard, 1 Leaf");
selectedRecipe = 3;
banner3.write(90);
}
}
void checkIfRecipeIsComplete() {
if (recipeReadyToCook == false && recipeJustCompleted == false) {
if ((selectedRecipe == 1 && appleCount == 1 && leafCount == 1 && cardboardCount == 1 & coffeeCount == 1) || (selectedRecipe == 2 && carrotCount == 1 && leafCount == 2 && cardboardCount == 1) || (selectedRecipe == 3 && appleCount == 1 && cardboardCount == 2 && leafCount == 1)) {
Serial.println("Recipe " + String(selectedRecipe) + " completed!");
if (selectedRecipe == 1) banner1.write(180);
else if (selectedRecipe == 2) banner2.write(180);
else if (selectedRecipe == 3) banner3.write(180);
setColor(255, 255, 255);
recipeJustCompleted = true;
Serial.println("Recipe ready to cook! Please use the paddle to start cooking.");
}
}
if (recipeJustCompleted == true) {
recipeReadyToCook = true;
}
}
// Reset - initial state
void resetGameWrongIngredient() {
Serial.println("Resetting ingredient counts due to wrong ingredient.");
appleCount = 0;
carrotCount = 0;
leafCount = 0;
cardboardCount = 0;
coffeeCount = 0;
wrongIng = false; // Reset wrongIng after resetting counts
offLED();
}
// reset - when cooking goes wrong
void resetGameWithPenalty() {
appleCount = 0;
carrotCount = 0;
leafCount = 0;
cardboardCount = 0;
coffeeCount = 0;
Serial.println("Resetting ingredient counts...");
Serial.print("Apple Count: ");
Serial.println(appleCount);
Serial.print("Carrot Count: ");
Serial.println(carrotCount);
Serial.print("Leaf Count: ");
Serial.println(leafCount);
Serial.print("Cardboard Count: ");
Serial.println(cardboardCount);
Serial.print("Coffee Beans Count: ");
Serial.println(coffeeCount);
offLED();
completedRecipesCount--;
wrongIng = true;
recipeReadyToCook = false;
digitalWrite(led_redCook, LOW);
digitalWrite(led_greenCook, LOW);
}
void resetGameCompleteRecipe() {
banner1.write(180);
banner2.write(180);
banner3.write(180);
completedRecipesCount++;
appleCount = 0;
carrotCount = 0;
leafCount = 0;
cardboardCount = 0;
coffeeCount = 0;
offLED();
wrongIng = false;
recipeReadyToCook = false; // Allow ingredient selection for the next recipe
digitalWrite(led_redCook, LOW);
digitalWrite(led_greenCook, LOW);
}
// from countdown tutorial
void countdown() {
long timeRemaining = endTime - (millis() - startTime);
int seconds = numberOfSeconds(timeRemaining);
int minutes = numberOfMinutes(timeRemaining);
display.showNumberDecEx(seconds, 0, true, 2, 2);
display.showNumberDecEx(minutes, 0x80 >> 3, true, 2, 0);
}
// from countdown tutorial
void displayText() {
display.setSegments(PLAY);
delay(2000);
}
void plantGrows(){
if(completedRecipesCount == 1){
int newPos = posPlant + 45;
plant.write(newPos);
Serial.println("Your plant is growing");
}
}