Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Make it for less
Published © GPL3+

Macrogatchi : The Next Tamagotchi Evolution

Tamagotchis are super fun and nostalgic but I want to know what is possible with the advances in electronics since they were first produced.

BeginnerWork in progress5 hours600
Macrogatchi : The Next Tamagotchi Evolution

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×1
ILI9488 lcd display
×1
Gravity:Digital Push Button (Yellow)
DFRobot Gravity:Digital Push Button (Yellow)
×3

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

schematic

Wiring diagram for the device

Code

macrogatchi.ino

C/C++
//includes
#include <LittleFS.h>
#define FileSys LittleFS

#include <PNGdec.h>
#include "pitches.h"

#include "SPI.h"
#include <TFT_eSPI.h>              // Hardware-specific library

// #include <algorithm>

//defines
#define MAX_IMAGE_WIDTH 320
//#define DEBUG

#define BUTTON_PIN_1 20
#define BUTTON_PIN_2 21
#define BUTTON_PIN_3 22
#define BUZZER_PIN 5

#define WIDTH  200
#define HEIGHT 200

//glob vars
int16_t xpos = 0;
int16_t ypos = 0;

PNG png;
File pngfile;


TFT_eSPI tft = TFT_eSPI();         // Invoke custom library
TFT_eSprite animal = TFT_eSprite(&tft);  // Declare Sprite object "spr" with pointer to "tft" object

enum moods{
  HAPPY,
  SAD,
  ANGRY,
  HUNGRY,
  SLEEPY,
  DEAD
};

struct food{
  const char* name;
  const char* fullImage;
  const char* oneBiteImage;
  const char* twoBiteImage;
  uint8_t value;
};


uint16_t* drawImageAtLocation(const char* fileName, int xPos, int yPos){
  File file = LittleFS.open(fileName, "r");
  String strname = file.name();
  uint16_t *imageSizes = new uint16_t[2];
  strname = "/" + strname;
  #ifdef DEBUG
    Serial.println(file.name());
  #endif
  // If it is not a directory and filename ends in .png then load it
  if (!file.isDirectory() && strname.endsWith(".png")) {
    xpos = xPos;
    ypos = yPos;
    int16_t rc = png.open(strname.c_str(), pngOpen, pngClose, pngRead, pngSeek, pngDraw);
    if (rc == PNG_SUCCESS) {
      imageSizes[0] = png.getWidth();
      imageSizes[1] = png.getHeight();
      tft.startWrite();
      rc = png.decode(NULL, 0);
      png.close();
      tft.endWrite();
    }else{
      #ifdef DEBUG
        Serial.println(rc);
      #endif
    }
  }
  #ifdef DEBUG
    Serial.println("returning");
  #endif
  return imageSizes;
}

uint8_t addNoOverflow(uint8_t lhs, uint8_t rhs){
  return lhs + std::min((uint8_t)(255-lhs),rhs);
}

uint8_t subtractNoUnderflow(uint8_t lhs, uint8_t rhs){
  return lhs - std::min(lhs,rhs);
}

class creature{
  public:

    void moveCreature(){
      lastX = xPos;
      lastY = yPos;
      if(xPos + lastImageWidth + xVel > 320)
        xVel = -1;
      if(xPos + xVel < 0)
        xVel = 1;
      if(yPos + lastImageHeight + yVel > 405)
        yVel = -1;
      if(yPos + yVel < 75)
        yVel = 1;      
      xPos += xVel;
      yPos += yVel;
    }

    void drawCreature(const char * image){
      uint16_t *tempSizes = drawImageAtLocation(image,xPos,yPos);
      lastImageWidth = tempSizes[0];
      lastImageHeight = tempSizes[1];
      delete tempSizes;
    }

    void feedCreature(food meal){
      drawImageAtLocation(meal.fullImage,xPos - 50, yPos + 50);
      drawCreature("/eatOpen.png");
      tone(BUZZER_PIN,NOTE_B2,200);
      delay(200);
      drawImageAtLocation(meal.oneBiteImage,xPos - 50, yPos + 50);
      drawCreature("/eatClose.png");
      tone(BUZZER_PIN,NOTE_C5,200);
      delay(200);
      drawCreature("/eatOpen.png");
      tone(BUZZER_PIN,NOTE_B2,200);
      delay(200);
//      tft.fillRect(xPos - 50, yPos + 50,50,50,TFT_BLUE);
      drawImageAtLocation(meal.twoBiteImage,xPos - 50, yPos + 50);
      drawCreature("/eatClose.png");
      tone(BUZZER_PIN,NOTE_C5,200);
      delay(200);
      drawImageAtLocation("/emptyTaco.png",xPos - 50, yPos + 50);
      hunger = subtractNoUnderflow(hunger,meal.value);
    }

    void updateCreature(){
      if(millis() - lifeTickCounter > 1000 * 60){
        lifeTickCounter = millis();
        hunger = addNoOverflow(hunger,5);
        cleanliness = subtractNoUnderflow(cleanliness,5);
        if(mood == moods::SLEEPY){
          sleepiness = subtractNoUnderflow(sleepiness,1);
        }else{
          sleepiness = addNoOverflow(sleepiness,5);
        }
        bladder = addNoOverflow(bladder,5);
        if(hunger > 25 | cleanliness < 150 | bladder > 50){
          happiness = subtractNoUnderflow(happiness,5);
        }
        if(happiness > 150){
          mood = moods::HAPPY;
        }else{
          mood = moods::ANGRY;
        }
        if(sleepiness > 25){
          mood = moods::SLEEPY;
        }
        if(hunger > 50){
          mood = moods::HUNGRY;
        }
      }
      tft.fillRect(0,0,100,50,TFT_BLUE);
      tft.setCursor(0,0);
      tft.print("Happiness: ");
      tft.println(happiness);
      tft.print("Hunger: ");
      tft.println(hunger);
      tft.print("Sleepiness: ");
      tft.println(sleepiness);
      drawCreature(images[mood]);
    }

  private:
    long long lifeTickCounter = 0;
    moods mood = moods::HAPPY;
    uint8_t hunger = 0;
    uint8_t happiness = 255;
    uint8_t cleanliness = 255;
    uint8_t bladder = 0;
    uint8_t sleepiness = 0;
    int lastX = 110;
    int lastY = 200;
    int xPos = lastX;
    int yPos = lastY;
    int xVel = -1;
    int yVel = 1;
    uint16_t lastImageWidth = 0;
    uint16_t lastImageHeight = 0;
    const char *images[5] = {"/happy.png","/sad.png","/neutral.png","/eatOpen.png","/sleep.png"};
};

creature g;
food taco;
//====================================================================================
//                                    Setup
//====================================================================================
void setup()
{
  #ifdef DEBUG
    Serial.begin(9600);
    pinMode(15,OUTPUT);
    digitalWrite(15,HIGH);
    while(!Serial);
    digitalWrite(15,LOW);
    delay(1000);
    Serial.println("\n\n Using the PNGdec library");
   #endif
  //Setup inputs
  pinMode(BUTTON_PIN_1,INPUT_PULLUP);
   // Initialise FS
  if (!FileSys.begin()) {
    #ifdef DEBUG
      Serial.println("LittleFS initialisation failed!");
    #endif
    while (1) yield(); // Stay here twiddling thumbs waiting
  }

  // Initialise the TFT
  tft.begin();
  tft.fillScreen(TFT_BLUE);
  #ifdef DEBUG
    Serial.println("\r\nInitialisation done.");
  #endif
  taco.name = "taco";
  taco.value = 10;
  taco.fullImage = "/taco.png";
  taco.oneBiteImage = "/tacoOneBite.png";
  taco.twoBiteImage = "/tacoTwoBite.png";
  drawImageAtLocation("/background.png",0,0);
}



//====================================================================================
//                                    Loop
//====================================================================================
void loop()
{
//  drawImageAtLocation("/background.png",0,0);
  // tft.fillScreen(TFT_BLUE);
  // tft.fillRect(0,480/2,320,480/2,TFT_BLUE);
  g.updateCreature();
  if(!digitalRead(BUTTON_PIN_1)){
    g.feedCreature(taco);
  }
 g.moveCreature();
  // delay(500);
//  drawImageAtLocation("/eatOpen.png",0,0);
//  drawImageAtLocation("/tacoOneBite.png",50,200);
//  drawImageAtLocation("/eatClose.png",0,0);
//  
//  drawImageAtLocation("/eatOpen.png",0,0);
////  tft.fillRect(50,200,100,250,TFT_BLACK);
//  drawImageAtLocation("/tacoTwoBite.png",50,200);
//  drawImageAtLocation("/eatClose.png",0,0);
  
}

//helper funcs
void * pngOpen(const char *filename, int32_t *size) {
  #ifdef DEBUG 
    Serial.printf("Attempting to open %s\n", filename);
  #endif
  pngfile = FileSys.open(filename, "r");
  *size = pngfile.size();
  return &pngfile;
}

void pngClose(void *handle) {
  File pngfile = *((File*)handle);
  if (pngfile) pngfile.close();
}

int32_t pngRead(PNGFILE *page, uint8_t *buffer, int32_t length) {
  if (!pngfile) return 0;
  page = page; // Avoid warning
  return pngfile.read(buffer, length);
}

int32_t pngSeek(PNGFILE *page, int32_t position) {
  if (!pngfile) return 0;
  page = page; // Avoid warning
  return pngfile.seek(position);
}

//=========================================v==========================================
//                                      pngDraw
//====================================================================================
// This next function will be called during decoding of the png file to
// render each image line to the TFT.  If you use a different TFT library
// you will need to adapt this function to suit.
// Callback function to draw pixels to the display
void pngDraw(PNGDRAW *pDraw) {
  uint16_t lineBuffer[MAX_IMAGE_WIDTH];
  uint8_t  maskBuffer[1 + MAX_IMAGE_WIDTH / 8];
  png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
  if (png.getAlphaMask(pDraw, maskBuffer, 255)) {
    tft.pushMaskedImage(xpos, ypos + pDraw->y, pDraw->iWidth, 1, lineBuffer, maskBuffer);
  }else{
    tft.pushImage(xpos, ypos + pDraw->y, pDraw->iWidth, 1, lineBuffer);
  }
}

pitches.h

C Header File
/*************************************************

 * Public Constants

 *************************************************/

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978

Credits

Make it for less

Make it for less

3 projects • 3 followers

Comments