//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);
}
}
Comments