feat: add proof-of-concept

This commit is contained in:
2026-04-29 00:11:35 +02:00
parent d9b72aa8b5
commit 0f807a1867
6 changed files with 216 additions and 58 deletions
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <LibPrintf.h>
#include "joystick.hpp"
#include "display.hpp"
#include "menu.hpp"
#define ACTION_INTERVAL 60000 // 1 minute
#define MAXIMUM_STAT 100
typedef struct {
bool dead;
bool menuOpen;
int hunger;
int joy;
int energy;
int cleanliness;
uint64_t lastActionTime;
bool shouldClear;
} GameState;
class Game {
public:
Game();
void begin();
void update();
void render();
protected:
void forceUpdate(String reason);
void feed();
void play();
void sleep();
void clean();
private:
GameState state;
Joystick joystick;
Display display;
String* items;
Menu menu;
};
+5 -10
View File
@@ -3,20 +3,15 @@
#include <Arduino.h> #include <Arduino.h>
#include "joystick.hpp" #include "joystick.hpp"
typedef struct {
String name;
void (*action)();
} Item;
class Menu { class Menu {
public: public:
Menu(Item* items); Menu();
void updateCurrentItem(JoystickDirection& direction); void setItems(String* items);
void execute(); bool updateCurrentItem(JoystickDirection& direction);
Item& getItemAt(size_t index); String& getItemAt(size_t index);
size_t getCurrentItemIndex() const; size_t getCurrentItemIndex() const;
private: private:
Item items[MENU_ITEM_COUNT]; String items[MENU_ITEM_COUNT];
int currentItem; int currentItem;
}; };
+3 -3
View File
@@ -17,12 +17,12 @@ void Display::drawMenu(Menu &menu) {
size_t currentItemIndex = menu.getCurrentItemIndex(); size_t currentItemIndex = menu.getCurrentItemIndex();
for (size_t i = 0; i < MENU_ITEM_COUNT; i++) { for (size_t i = 0; i < MENU_ITEM_COUNT; i++) {
lcd.setCursor(0, i); lcd.setCursor(0, i);
Item& item = menu.getItemAt(i); String item = menu.getItemAt(i);
if (i == currentItemIndex) { if (i == currentItemIndex) {
lcd.print("> " + item.name); lcd.print("> " + item);
} else { } else {
lcd.print(" " + item.name); lcd.print(" " + item);
} }
} }
} }
+151
View File
@@ -0,0 +1,151 @@
#include "game.hpp"
Game::Game() : joystick(), display(), menu() {
state = (GameState) {
.dead = false,
.menuOpen = false,
.hunger = 0,
.joy = 100,
.energy = 100,
.cleanliness = 100,
.lastActionTime = 0,
.shouldClear = false,
};
String items[] = {
"Feed",
"Play",
"Sleep",
"Clean"
};
menu.setItems(items);
}
void Game::begin() {
Serial.begin(9600);
while (!Serial) {
delay(10);
}
display.begin();
}
void Game::update() {
// If the pet is dead, we don't need to update its state
if (state.dead) {
return;
}
bool isPressed = joystick.isPressed();
// If the menu is open, we don't need to update the pet's state
if (state.menuOpen) {
JoystickDirection direction = joystick.getDirection();
if (menu.updateCurrentItem(direction)) {
forceUpdate("Menu navigation");
}
// If the joystick is pressed, execute the current menu item
if (isPressed) {
state.menuOpen = false;
// Based on the current menu item, perform the corresponding action
switch (menu.getCurrentItemIndex()) {
case 0:
feed();
break;
case 1:
play();
break;
case 2:
sleep();
break;
case 3:
clean();
break;
}
forceUpdate("Menu action");
}
return;
}
// If the joystick is pressed and the menu is not open, open the menu
if (isPressed) {
state.menuOpen = true;
forceUpdate("Opening menu");
return;
}
// Update the pet's stats based on the time elapsed since the last action
uint64_t currentTime = millis();
if (currentTime - state.lastActionTime >= ACTION_INTERVAL) {
state.hunger += 10;
state.joy -= 5;
state.energy -= 5;
state.cleanliness -= 5;
state.lastActionTime = currentTime;
forceUpdate("Time-based stat update");
}
// Check if the pet has died from any of the conditions
bool starvedToDeath = state.hunger >= MAXIMUM_STAT;
bool diedOfBoredom = state.joy <= -MAXIMUM_STAT;
bool diedOfExhaustion = state.energy <= -MAXIMUM_STAT;
bool diedOfFilth = state.cleanliness <= -MAXIMUM_STAT;
state.dead = starvedToDeath || diedOfBoredom || diedOfExhaustion || diedOfFilth;
}
void Game::render() {
if (state.shouldClear) {
display.clear();
state.shouldClear = false;
if (state.menuOpen) {
display.drawMenu(menu);
Serial.println("Rendering menu");
return;
}
}
if (state.menuOpen) {
return;
}
if (state.dead) {
display.getLCD().print("Your pet has died.");
return;
}
display.getLCD().setCursor(0, 0);
display.getLCD().print("H: " + String(state.hunger));
display.getLCD().setCursor(0, 1);
display.getLCD().print("J: " + String(state.joy));
display.getLCD().setCursor(0, 2);
display.getLCD().print("E: " + String(state.energy));
display.getLCD().setCursor(0, 3);
display.getLCD().print("C: " + String(state.cleanliness));
}
void Game::forceUpdate(String reason) {
if (!state.shouldClear) {
Serial.println("Forcing update: " + reason);
state.shouldClear = true;
}
}
void Game::feed() {
state.hunger += 20;
}
void Game::play() {
state.joy += 20;
}
void Game::sleep() {
state.energy += 20;
}
void Game::clean() {
state.cleanliness += 20;
}
+6 -33
View File
@@ -1,40 +1,13 @@
#include <Arduino.h> #include "game.hpp"
#include <LibPrintf.h>
#include "joystick.hpp"
#include "display.hpp"
Joystick joystick; Game game;
Display display;
Item items[MENU_ITEM_COUNT] = {
{"Feed", []() { Serial.println("Feeding..."); }},
{"Play", []() { Serial.println("Playing..."); }},
{"Sleep", []() { Serial.println("Sleeping..."); }},
{"Clean", []() { Serial.println("Cleaning..."); }}
};
Menu menu(items);
void setup() { void setup() {
Serial.begin(9600); game.begin();
while (!Serial) {
delay(10);
}
display.begin();
pinMode(LED_BUILTIN, OUTPUT);
display.drawMenu(menu);
} }
void loop() { void loop() {
JoystickDirection direction = joystick.getDirection(); game.update();
if (direction != JoystickDirection::CENTER) { delay(100); // Small delay to prevent overwhelming the CPU
menu.updateCurrentItem(direction); game.render();
display.drawMenu(menu);
}
if (joystick.isPressed()) {
menu.execute();
}
delay(250);
} }
+9 -12
View File
@@ -1,31 +1,28 @@
#include "menu.hpp" #include "menu.hpp"
Menu::Menu(Item* items) : currentItem(0) { Menu::Menu() : currentItem(0) {}
void Menu::setItems(String* items) {
this->currentItem = 0;
for (int i = 0; i < MENU_ITEM_COUNT; i++) { for (int i = 0; i < MENU_ITEM_COUNT; i++) {
this->items[i] = items[i]; this->items[i] = items[i];
} }
} }
void Menu::updateCurrentItem(JoystickDirection &direction) { bool Menu::updateCurrentItem(JoystickDirection &direction) {
switch (direction) { switch (direction) {
case JoystickDirection::UP: case JoystickDirection::UP:
currentItem = (currentItem - 1 + MENU_ITEM_COUNT) % MENU_ITEM_COUNT; currentItem = (currentItem - 1 + MENU_ITEM_COUNT) % MENU_ITEM_COUNT;
break; return true;
case JoystickDirection::DOWN: case JoystickDirection::DOWN:
currentItem = (currentItem + 1) % MENU_ITEM_COUNT; currentItem = (currentItem + 1) % MENU_ITEM_COUNT;
break; return true;
default: default:
break; return false;
} }
} }
void Menu::execute() { String& Menu::getItemAt(size_t index) {
if (items[currentItem].action) {
items[currentItem].action();
}
}
Item& Menu::getItemAt(size_t index) {
return items[index]; return items[index];
} }