#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Wire.h>
#include <Adafruit_FT6206.h>
// --- Configuration Écran et Tactile (CYD ESP32-S3) ---
Adafruit_FT6206 ctp = Adafruit_FT6206();
#define TFT_CS 15
#define TFT_DC 2
// RETOUR A LA FORMULE QUI MARCHE : On laisse l'ESP32 gérer son SPI par défaut
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// --- La Toile Invisible (Double Buffering Adafruit) ---
GFXcanvas16* canvas;
// --- Nouvelles Couleurs "Kawaii / Pixel Art" ---
#define COLOR_SKY 0x5E3F // Bleu ciel lumineux
#define COLOR_GRASS 0x9F35 // Vert herbe
#define COLOR_MOUNTAIN1 0x356A // Montagne sombre
#define COLOR_MOUNTAIN2 0x4DCA // Montagne claire
#define COLOR_SUN 0xFFE0 // Jaune soleil
#define COLOR_OUTLINE 0x0000 // Noir pur
// Couleurs bicolores pour les boutons
#define C_FAIM_L 0xFBAE // Saumon
#define C_FAIM_R 0xCA6B // Rose foncé
#define C_JOIE_L 0xFD97 // Rose vif
#define C_JOIE_R 0xD415 // Violet/Rose sombre
#define C_PROP_L 0x7E3F // Bleu clair
#define C_PROP_R 0x34DF // Bleu profond
#define C_ENER_L 0xFFE6 // Jaune
#define C_ENER_R 0x5E08 // Vert clair
// --- Structures de données du Tamagotchi ---
enum Humeur { HEUREUX, AFFAME, FATIGUE, SALE, TRISTE, MORT };
enum EtatJeu { ECRAN_PRINCIPAL, ECRAN_EDITEUR };
struct Pixel {
uint8_t x;
uint8_t y;
uint16_t couleur;
};
struct Tamagotchi {
int generation;
int faim;
int joie;
int proprete;
int energie;
Humeur humeur;
int pixels_utilises;
Pixel corps[500];
};
Tamagotchi monPerso;
EtatJeu etatActuel = ECRAN_PRINCIPAL;
// --- Timers et Variables Système ---
unsigned long dernierTickAnim = 0;
unsigned long dernierTickVie = 0;
const int INTERVALLE_ANIM = 400;
const int INTERVALLE_VIE = 3000;
int frameAnim = 0;
int decalageY = 0;
// --- Configuration de la Grille ---
const int TAILLE_GRILLE = 64;
const int TAILLE_MACRO_PIXEL = 3;
const int OFFSET_X_JEU = 24;
const int OFFSET_Y_JEU = 40;
// --- Déclarations des fonctions ---
void dessinerInterface();
void dessinerBarreEtatTop();
void dessinerMenuBas();
void mettreAJourCanvas();
void dessinerDecor(GFXcanvas16* c);
void gestionTactile();
void boucleDeVie();
void boucleAnimation();
// --- Initialisation du Monstre ---
void creerSlimeDeBase() {
monPerso.generation = 1;
monPerso.faim = 70;
monPerso.joie = 90;
monPerso.proprete = 60;
monPerso.energie = 80;
monPerso.humeur = HEUREUX;
monPerso.pixels_utilises = 0;
uint16_t corpsColor = tft.color565(150, 180, 220); // Un bleu robot mignon
uint16_t blanc = ILI9341_WHITE;
for(int y = 30; y < 46; y++) {
for(int x = 24; x < 40; x++) {
if ((y == 30 && (x < 28 || x > 36)) || (y == 31 && (x < 26 || x > 38))) continue;
if ((y == 45 && (x < 26 || x > 38))) continue;
uint16_t couleurActuelle = corpsColor;
// Yeux
if ((y >= 34 && y <= 38) && ((x >= 26 && x <= 30) || (x >= 34 && x <= 38))) {
couleurActuelle = blanc;
if ((y >= 35 && y <= 37) && (x == 28 || x == 36)) couleurActuelle = ILI9341_BLACK;
}
monPerso.corps[monPerso.pixels_utilises] = {(uint8_t)x, (uint8_t)y, couleurActuelle};
monPerso.pixels_utilises++;
}
}
}
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("\n--- Démarrage Tamagotchi OS ---");
Wire.setPins(10, 8);
tft.begin();
tft.setRotation(0);
tft.fillScreen(ILI9341_RED);
delay(200);
Serial.println("Recherche de l'écran tactile...");
if (!ctp.begin(40)) {
Serial.println("ERREUR: Ecran tactile FT6206 introuvable !");
while (1) delay(10);
}
Serial.println("Tactile OK !");
canvas = new GFXcanvas16(192, 220);
if (!canvas->getBuffer()) {
Serial.println("ERREUR: Mémoire insuffisante !");
while (1) delay(10);
}
creerSlimeDeBase();
dessinerInterface();
Serial.println("Système prêt et lancé !");
}
void loop() {
gestionTactile();
boucleDeVie();
boucleAnimation();
delay(10);
}
// --- Moteur Tactile ---
void gestionTactile() {
if (!ctp.touched()) return;
TS_Point p = ctp.getPoint();
p.x = map(p.x, 0, 240, 240, 0);
p.y = map(p.y, 0, 320, 320, 0);
if (etatActuel == ECRAN_PRINCIPAL) {
if (p.y > 230) {
if (p.x > 10 && p.x < 58) { // FAIM
monPerso.faim = min(100, monPerso.faim + 15);
dessinerMenuBas(); delay(200);
}
else if (p.x > 68 && p.x < 116) { // JOIE
monPerso.joie = min(100, monPerso.joie + 15);
dessinerMenuBas(); delay(200);
}
else if (p.x > 126 && p.x < 174) { // PROPRETE
monPerso.proprete = 100;
dessinerMenuBas(); delay(200);
}
else if (p.x > 184 && p.x < 232) { // ENERGIE
monPerso.energie = min(100, monPerso.energie + 20);
dessinerMenuBas(); delay(200);
}
}
else if (p.y > OFFSET_Y_JEU && p.y < OFFSET_Y_JEU + 192) {
etatActuel = ECRAN_EDITEUR;
tft.fillScreen(COLOR_SKY);
dessinerInterface();
delay(200);
}
}
else if (etatActuel == ECRAN_EDITEUR) {
if (p.y > 280) { // Bouton Retour
etatActuel = ECRAN_PRINCIPAL;
tft.fillScreen(COLOR_SKY);
dessinerInterface();
delay(200);
}
else if (p.y > OFFSET_Y_JEU && p.y < (OFFSET_Y_JEU + (TAILLE_GRILLE * TAILLE_MACRO_PIXEL)) &&
p.x > OFFSET_X_JEU && p.x < (OFFSET_X_JEU + (TAILLE_GRILLE * TAILLE_MACRO_PIXEL))) {
int caseX = (p.x - OFFSET_X_JEU) / TAILLE_MACRO_PIXEL;
int caseY = (p.y - OFFSET_Y_JEU) / TAILLE_MACRO_PIXEL;
if (monPerso.pixels_utilises < 500) {
monPerso.corps[monPerso.pixels_utilises] = {(uint8_t)caseX, (uint8_t)caseY, ILI9341_RED};
monPerso.pixels_utilises++;
tft.fillRect(OFFSET_X_JEU + (caseX * TAILLE_MACRO_PIXEL), OFFSET_Y_JEU + (caseY * TAILLE_MACRO_PIXEL), TAILLE_MACRO_PIXEL, TAILLE_MACRO_PIXEL, ILI9341_RED);
delay(10);
}
}
}
}
// --- Moteur de Jeu ---
void boucleDeVie() {
if (millis() - dernierTickVie >= INTERVALLE_VIE) {
dernierTickVie = millis();
if (monPerso.humeur != MORT) {
monPerso.faim -= 3;
monPerso.joie -= 2;
monPerso.proprete -= 1;
monPerso.energie -= 2;
}
if (monPerso.faim < 0) monPerso.faim = 0;
if (monPerso.joie < 0) monPerso.joie = 0;
if (monPerso.proprete < 0) monPerso.proprete = 0;
if (monPerso.energie < 0) monPerso.energie = 0;
if (monPerso.faim == 0 && monPerso.energie == 0) monPerso.humeur = MORT;
else if (monPerso.joie < 30) monPerso.humeur = TRISTE;
else if (monPerso.proprete < 30) monPerso.humeur = SALE;
else if (monPerso.faim <= 30) monPerso.humeur = AFFAME;
else if (monPerso.energie <= 30) monPerso.humeur = FATIGUE;
else monPerso.humeur = HEUREUX;
if (etatActuel == ECRAN_PRINCIPAL) dessinerMenuBas();
}
}
void boucleAnimation() {
if (millis() - dernierTickAnim >= INTERVALLE_ANIM) {
dernierTickAnim = millis();
if (monPerso.humeur == MORT) decalageY = 0;
else {
if (frameAnim == 0) { frameAnim = 1; decalageY = -6; }
else { frameAnim = 0; decalageY = 0; }
}
mettreAJourCanvas();
}
}
// --- Affichage Double Buffering ---
void dessinerDecor(GFXcanvas16* c) {
// 1. Ciel
c->fillRect(0, 0, 192, 110, COLOR_SKY);
// 2. Soleil
c->fillCircle(160, 25, 15, COLOR_SUN);
// 3. Nuages
c->fillCircle(30, 45, 12, ILI9341_WHITE);
c->fillCircle(45, 40, 16, ILI9341_WHITE);
c->fillCircle(60, 45, 12, ILI9341_WHITE);
c->fillCircle(130, 60, 10, ILI9341_WHITE);
c->fillCircle(145, 55, 14, ILI9341_WHITE);
c->fillCircle(160, 60, 10, ILI9341_WHITE);
// 4. Montagnes
c->fillTriangle(-10, 110, 30, 60, 70, 110, COLOR_MOUNTAIN1); // Gauche
c->fillTriangle(130, 110, 170, 50, 210, 110, COLOR_MOUNTAIN1); // Droite
c->fillTriangle(60, 110, 110, 65, 150, 110, COLOR_MOUNTAIN2); // Milieu
// 5. Herbe
c->fillRect(0, 110, 192, 110, COLOR_GRASS);
}
void mettreAJourCanvas() {
// Dessin du fond (Paysage)
dessinerDecor(canvas);
// Ombre du personnage (Foreground)
if (etatActuel == ECRAN_PRINCIPAL) {
canvas->fillEllipse(96, 170, 25, 6, 0x18E3);
}
// Personnage (Foreground)
for (int i = 0; i < monPerso.pixels_utilises; i++) {
int xCanvas = monPerso.corps[i].x * TAILLE_MACRO_PIXEL;
int yCanvas = (monPerso.corps[i].y * TAILLE_MACRO_PIXEL) + decalageY + 10;
canvas->fillRect(xCanvas, yCanvas, TAILLE_MACRO_PIXEL, TAILLE_MACRO_PIXEL, monPerso.corps[i].couleur);
}
tft.drawRGBBitmap(OFFSET_X_JEU, OFFSET_Y_JEU, canvas->getBuffer(), 192, 220);
}
// --- Interface Graphique (UI) ---
void dessinerInterface() {
tft.fillRect(0, 0, 240, 320, COLOR_GRASS);
tft.fillRect(0, 0, 240, 150, COLOR_SKY); // Raccord du ciel en dehors du canvas
dessinerBarreEtatTop();
if (etatActuel == ECRAN_PRINCIPAL) {
dessinerMenuBas();
} else {
tft.fillRoundRect(10, 280, 220, 35, 8, C_JOIE_L);
tft.drawRoundRect(10, 280, 220, 35, 8, COLOR_OUTLINE);
tft.setTextColor(COLOR_OUTLINE);
tft.setTextSize(1);
tft.setCursor(65, 293);
tft.print("< RETOUR AU JEU");
}
}
void dessinerBarreEtatTop() {
tft.fillRect(0, 0, 240, 20, 0x114A); // Bleu marin foncé
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(5, 6);
tft.print("14:38");
tft.setCursor(180, 6);
tft.print("85%");
tft.drawRect(210, 5, 20, 10, ILI9341_WHITE);
tft.fillRect(212, 7, 13, 6, 0x07E0); // Batterie verte
}
void dessinerMenuBas() {
tft.setTextColor(COLOR_OUTLINE);
tft.setTextSize(1);
// Titres
tft.setCursor(18, 238); tft.print("FAIM");
tft.setCursor(80, 238); tft.print("JOIE");
tft.setCursor(125, 238); tft.print("PROPRETE");
tft.setCursor(185, 238); tft.print("ENERGIE");
// Espacement parfait : Boutons de 48x48. X: 10, 68, 126, 184
// --- Bouton 1: FAIM ---
int x = 10, y = 250;
tft.fillRect(x, y, 24, 48, C_FAIM_L);
tft.fillRect(x+24, y, 24, 48, C_FAIM_R);
tft.drawRect(x, y, 48, 48, COLOR_OUTLINE);
tft.drawRect(x+1, y+1, 46, 46, ILI9341_WHITE);
// Icône Bol
tft.fillRoundRect(x+12, y+18, 24, 12, 4, 0x3333); // Bol bleu foncé
tft.fillCircle(x+24, y+18, 9, ILI9341_WHITE); // Riz blanc
// Texte
tft.setCursor(x+10, y+35); tft.print(monPerso.faim); tft.print("%");
// --- Bouton 2: JOIE ---
x = 68;
tft.fillRect(x, y, 24, 48, C_JOIE_L);
tft.fillRect(x+24, y, 24, 48, C_JOIE_R);
tft.drawRect(x, y, 48, 48, COLOR_OUTLINE);
tft.drawRect(x+1, y+1, 46, 46, ILI9341_WHITE);
// Icône Coeur
tft.fillCircle(x+17, y+15, 6, ILI9341_RED);
tft.fillCircle(x+31, y+15, 6, ILI9341_RED);
tft.fillTriangle(x+12, y+17, x+36, y+17, x+24, y+28, ILI9341_RED);
// Texte
tft.setCursor(x+10, y+35); tft.print(monPerso.joie); tft.print("%");
// --- Bouton 3: PROPRETÉ ---
x = 126;
tft.fillRect(x, y, 24, 48, C_PROP_L);
tft.fillRect(x+24, y, 24, 48, C_PROP_R);
tft.drawRect(x, y, 48, 48, COLOR_OUTLINE);
tft.drawRect(x+1, y+1, 46, 46, ILI9341_WHITE);
// Icône Bulles
tft.fillCircle(x+20, y+18, 6, ILI9341_WHITE); tft.drawCircle(x+20, y+18, 6, 0x0476);
tft.fillCircle(x+32, y+24, 4, ILI9341_WHITE); tft.drawCircle(x+32, y+24, 4, 0x0476);
tft.fillCircle(x+28, y+10, 3, ILI9341_WHITE); tft.drawCircle(x+28, y+10, 3, 0x0476);
// Texte
tft.setCursor(x+10, y+35); tft.print(monPerso.proprete); tft.print("%");
// --- Bouton 4: ÉNERGIE ---
x = 184;
tft.fillRect(x, y, 24, 48, C_ENER_L);
tft.fillRect(x+24, y, 24, 48, C_ENER_R);
tft.drawRect(x, y, 48, 48, COLOR_OUTLINE);
tft.drawRect(x+1, y+1, 46, 46, ILI9341_WHITE);
// Icône Éclair
tft.fillTriangle(x+25, y+10, x+15, y+20, x+27, y+20, COLOR_SUN);
tft.fillTriangle(x+18, y+20, x+30, y+20, x+20, y+32, COLOR_SUN);
// Texte
tft.setCursor(x+10, y+35); tft.print(monPerso.energie); tft.print("%");
}Loading
esp32-s3-devkitc-1
esp32-s3-devkitc-1