#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <ArduinoJson.h>
#include "sprites.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define BUTTON_FEED 5
#define BUTTON_PLAY 6
#define BUTTON_OTHER 7
//Переменные статуса персонажа:
float health = 100;
float hunger = 100;
float happiness = 100;
float discipline=100;
float weight=1;
float age=0;
bool isAlive = true; // параметр жизни питомца
float money = 100;
bool isSleep = false;
float sleepometer = 100; // уровень бодрости
float poopometer = 0;
//menu:
int selected = 0;
int entered = -1;
int gameState = 0; //что происходит на экране. 1 - главный экран питомца. 2 - меню. 3 - сцена активности
unsigned long previousMillis = 0; // Переменная для хранения времени последнего обновления
unsigned long animationStartTime = 0; // Переменная для хранения времени начала анимации экшн экрана 3
bool timerLock1 = false;
byte actionType = 0;
int8_t actionTimer = 2; // параметр - таймер экрана смерти в сек
//unsigned long animationInterval = 1000; // период обновления движения питомца (спрайта)
int spriteX = 64;
bool movingRight = true;
//unsigned long previousMillis = 0; // Переменная для хранения времени последнего обновления анимации персонажа (возможно стоит избавится)
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.fillScreen(WHITE);
display.println("Start");
display.display();
// Настройка пинов
pinMode(BUTTON_FEED, INPUT_PULLUP);
pinMode(BUTTON_PLAY, INPUT_PULLUP);
pinMode(BUTTON_OTHER, INPUT_PULLUP);
gameState = 2;
}
void loop() {
unsigned long currentMillis = millis(); // Получаем текущее время
//Обновление значений симуляции раз в секунду
if (currentMillis - previousMillis >= 1000) {
previousMillis = currentMillis;
Serial.print(F("Game state: "));
Serial.print(gameState);
Serial.print(F(" Entered: "));
Serial.println(entered);
Serial.print(F(" Selected: "));
Serial.println(selected);
lifeSimulation();
}
drawMainScreen();
// displaymenu();
}
// Функция для отображения главного экрана
void drawMainScreen() {
if (gameState==2) {
display.clearDisplay();
display.setCursor(0,0);
display.print("Toka");
display.print(" ");
display.write(0x03);
display.print(int(health));
//display.setCursor(0, 20);
display.print(" ");
display.write(0x04);
display.print(int(hunger));
display.print(" ");
display.write(0x02);
display.print(int(happiness));
display.print(" ");
display.write(0x24);
display.print(int(money));
//Отрисовка питомца
//display.drawBitmap(52, 32, dinoWalk0, 48, 24, WHITE);
moveSprite(); //двигает спрайт по экрану
drawSprite(); //рисует спрайт
// Обработка нажатий кнопок для главного экрана
handleButtons();
}
if (gameState==1) {displaymenu();}
if (gameState==3) {actionScreen();}
display.display();
}
// Функция для обработки нажатий кнопок
void handleButtons() {
if (digitalRead(BUTTON_OTHER) == LOW) {
// Покормить
hunger += 20;
if (hunger > 100) {
hunger = 100;
}
// Звук еды
} else if (digitalRead(BUTTON_PLAY) == LOW) {
// Поиграть
happiness += 20;
if (happiness > 100) {
happiness = 100;
}
// Звук игры
} else if (digitalRead(BUTTON_FEED) == LOW) {
Serial.println(F("Button menu"));
gameState =1;
delay(50);
}
}
// Функция обновления значений
void lifeSimulation() {
// Снижение значений со временем
if (isAlive) { //если не спит, то
if (!isSleep) { //если не спит, то
hunger-=0.002;
if (poopometer<=100) {poopometer+=0.001;}
if (happiness>=0) {happiness-=0.0001;}
health-=age*0.0001+poopometer*0.01;
}
else {
if (poopometer<=100) {poopometer+=0.0005;}
if (happiness>=0) {happiness-=0.00005;}
health-=age*0.0001+poopometer*0.01;
}
age+=0.0001;
if (hunger<=0 || health<=0) {
hunger=0;
health=0;
isAlive=false;
}
}
// Проверка на смерть
if (hunger <= 0) {
Serial.println(F("Dead"));
gameState = 3;
actionType = 8; //запускаем экшен сцену 3 с анимацией смерти 8
isAlive = false;
// Отображение экрана смерти
}
}
void displaymenu(void) {
const int menuLength = 10; // Длина меню
const int visibleItems = 6; // Количество видимых пунктов меню
if (digitalRead(BUTTON_OTHER) == LOW) {
entered = -1;
if (selected > 0) {
selected--;
} else {
selected = menuLength - 1; // Возвращаемся к последнему пункту меню
}
delay(20);
} else if (digitalRead(BUTTON_FEED) == LOW) {
selected++;
if (selected >= menuLength) {
selected = 0; // Возвращаемся к началу меню
}
delay(20);
} else if (digitalRead(BUTTON_PLAY) == LOW) {
entered = selected;
} else if (digitalRead(BUTTON_OTHER) == LOW) {
entered = -1;
}
const char *options[10] = {
" Close Menu ",
" Stat ",
" Feed ",
" Wash ",
" Heal ",
" Sleep ",
" Play ",
" Setup",
" Save",
" Reset"
};
int startIndex;
int endIndex;
switch (entered) {
case -1: // Если entered равно -1, отображаем главное меню
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.drawRoundRect(0, 0, 58, 11, 3, WHITE);
display.setCursor(2, 2);
display.println(F("Main Menu"));
display.setCursor(0, 12);
//display.println("");
// Определяем, какие элементы меню отображать
startIndex = max(0, selected - (visibleItems - 1));
endIndex = min(menuLength - 1, startIndex + visibleItems - 1);
for (int i = startIndex; i <= endIndex; i++) {
if (i == selected) {
display.write(0x10); // отображение символа стрелочки у пункта меню, можно изменить цвет
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.println(options[i]);
} else {
display.setTextColor(SSD1306_WHITE);
display.println(options[i]);
}
}
break;
case 0: // Если entered равно 0, переходим на главный экран
gameState = 2;
entered = -1;
selected = 0;
break;
case 1: // Если entered равно 1, отображаем статистику
display.clearDisplay();
//display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Stat"));
display.println("Name: Toka");
display.setCursor(0, 20);
display.write(0x03);
display.print("Health: ");
display.print(health);
display.setCursor(0, 30);
display.write(0x04);
display.print("Hunger: ");
display.print(hunger);
display.setCursor(0, 40);
display.write(0x02);
display.print("Happiness: ");
display.print(happiness);
display.setCursor(0, 50);
display.write(0x24);
display.print("Money: ");
display.print(money);
break;
case 2: // Если entered равно 2, то запускаем actionscreen с параметром 1 eating
gameState = 3; //переходим на стэйт action
if (hunger <= 100) {
actionTimer = 2; //выставляем на 2 секунды таймер
actionType = 1; //т.е. еда
hunger += 20.333;
entered = -1;
selected = 0;
}
else {
actionTimer = 2; //выставляем на 2 секунды таймер
actionType = 0; //говорит нет т.к. не голоден
entered = -1;
selected = 0;
}
break;
// вставлять во всех кейсах, чтобы меню потом открылось с нуля
// entered = -1;
// selected = 0;
}
}
void actionScreen() {
if (timerLock1==false) {
animationStartTime = millis(); // Стартуем таймер для анимации
timerLock1=true;
}
//static unsigned long animationStartTime = 0;
switch(actionType) {
case 0: //Отказ от выполнения действия
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.print("NO!");
break;
case 1: // Если передан параметр 1, запускаем анимацию питания (eating)
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.print("Eating...");
break;
case 2: // Если передан параметр 2, запускаем анимацию питья (drinking)
//
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
//сцена смерти.
// приравниваем к милис, чтобы таймер залипал навсегда на экране смерти
break;
//
default:
gameState = 2;
break;
}
//
if (millis() - animationStartTime >= actionTimer*1000) {
gameState = 2; // Переходим на главный экран
timerLock1=false;
}
}
// Функция для перемещения спрайта
void moveSprite() {
// Если спрайт движется вправо
if (movingRight) {
spriteX++; // Увеличиваем его позицию по X
// Если спрайт достиг правого края экрана
if (spriteX + 48 >= SCREEN_WIDTH) {
movingRight = false; // Меняем направление движения на обратное
}
} else { // Если спрайт движется влево
spriteX--; // Уменьшаем его позицию по X
// Если спрайт достиг левого края экрана
if (spriteX <= 0) {
movingRight = true; // Меняем направление движения на обратное
}
}
}
// Функция для отображения спрайта на текущей позиции
void drawSprite() {
//display.clearDisplay();
//display.display();
if (movingRight) {
display.drawBitmap(spriteX, 32, dinoWalk0, 48, 24, WHITE);
} else {
display.drawBitmap(spriteX, 32, dinoWalkInverted, 48, 24, WHITE);
}
}