#include <TFT_eSPI.h> // Bibliothèque pour l'écran TFT
#include <SPI.h> // Bibliothèque SPI
#include <Adafruit_GFX.h> // Bibliothèque de graphismes
#include <ESP32Servo.h> // Bibliothèque pour le joystick
// Configuration de l'écran TFT
#define TFT_CS 15
#define TFT_RST 4
#define TFT_DC 2
#define TFT_LED 5
TFT_eSPI tft = TFT_eSPI(); // Déclaration de l'objet TFT
// Configuration du joystick
#define JOY_X_PIN 34
#define JOY_Y_PIN 35
#define JOY_BTN_PIN 32
// Configuration du buzzer
#define BUZZER_PIN 13
// Variables globales pour Pac-Man
int pacmanX = 60;
int pacmanY = 60;
int pacmanSize = 10;
int pacmanSpeed = 2;
int direction = 0; // 0: up, 1: right, 2: down, 3: left
int pacmanMouthState = 0;
unsigned long lastPacmanUpdate = 0;
const int pacmanUpdateInterval = 100;
bool speedBoostActive = false;
unsigned long speedBoostEndTime = 0;
// Variables globales pour les fantômes
struct Ghost {
int x;
int y;
int direction;
int speed;
bool frightened;
int animationState;
unsigned long lastUpdate;
uint16_t color;
};
Ghost ghosts[4] = {
{30, 30, 1, 1, false, 0, 0, TFT_RED},
{90, 90, 3, 1, false, 0, 0, TFT_BLUE},
{30, 90, 0, 1, false, 0, 0, TFT_GREEN},
{90, 30, 2, 1, false, 0, 0, TFT_MAGENTA}
};
// Niveaux prédéfinis avec des thèmes spécifiques
const int numLevels = 3;
const int numWalls = 10;
struct Level {
uint16_t wallColor;
uint16_t backgroundColor;
int walls[numWalls][4];
int teleporters[2][2]; // Deux téléporteurs par niveau
int speedBoost[2]; // Position du boost de vitesse
};
Level levels[numLevels] = {
{TFT_BLUE, TFT_BLACK, {{20, 20, 60, 10}, {20, 40, 60, 10}, {20, 60, 60, 10}, {20, 80, 60, 10}, {20, 100, 60, 10}, {80, 20, 10, 90}, {100, 20, 10, 90}, {120, 20, 10, 90}, {140, 20, 10, 90}, {160, 20, 10, 90}}, {{10, 110}, {110, 10}}, {50, 50}},
{TFT_RED, TFT_BLACK, {{20, 20, 100, 10}, {20, 40, 100, 10}, {20, 60, 100, 10}, {20, 80, 100, 10}, {20, 100, 100, 10}, {120, 20, 10, 90}, {140, 20, 10, 90}, {160, 20, 10, 90}, {180, 20, 10, 90}, {200, 20, 10, 90}}, {{20, 100}, {100, 20}}, {60, 60}},
{TFT_GREEN, TFT_BLACK, {{30, 30, 60, 10}, {30, 50, 60, 10}, {30, 70, 60, 10}, {30, 90, 60, 10}, {30, 110, 60, 10}, {90, 30, 10, 90}, {110, 30, 10, 90}, {130, 30, 10, 90}, {150, 30, 10, 90}, {170, 30, 10, 90}}, {{30, 90}, {90, 30}}, {70, 70}}
};
int currentLevel = 0;
// Positions des points et super points à collecter
const int numPoints = 20;
int points[numPoints][2];
bool pointsCollected[numPoints] = {false};
// Positions des super points
const int numSuperPoints = 5;
int superPoints[numSuperPoints][2];
bool superPointsCollected[numSuperPoints] = {false};
// Variables globales pour le joystick
int joyX = 0;
int joyY = 0;
bool joyBtn = false;
// Variables pour le score et les vies
int score = 0;
int lives = 3;
unsigned long frightenedModeEndTime = 0;
// Variables pour gérer l'état du jeu
enum GameState { MENU, PLAYING, PAUSED, GAME_OVER, TRANSITION };
GameState gameState = MENU;
void setup() {
// Initialisation de l'écran TFT
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
// Initialisation du joystick
pinMode(JOY_BTN_PIN, INPUT_PULLUP);
// Initialisation du buzzer
pinMode(BUZZER_PIN, OUTPUT);
// Génération des points et des super points
generatePoints();
// Dessin initial des éléments
drawWalls();
drawPacman(pacmanX, pacmanY);
drawPoints();
drawSuperPoints();
for (int i = 0; i < 4; i++) {
drawGhost(ghosts[i].x, ghosts[i].y, ghosts[i].color);
}
}
void loop() {
// Lire les entrées du joystick
joyX = analogRead(JOY_X_PIN);
joyY = analogRead(JOY_Y_PIN);
joyBtn = digitalRead(JOY_BTN_PIN);
// Gérer les états du jeu
switch (gameState) {
case MENU:
showMenu();
if (joyBtn == LOW) {
gameState = PLAYING;
}
break;
case PLAYING:
if (joyBtn == LOW) {
gameState = PAUSED;
delay(200); // Debounce button press
} else {
updateGame();
}
break;
case PAUSED:
showPauseScreen();
if (joyBtn == LOW) {
gameState = PLAYING;
delay(200); // Debounce button press
}
break;
case GAME_OVER:
showGameOverScreen();
if (joyBtn == LOW) {
resetGame();
gameState = MENU;
delay(200); // Debounce button press
}
break;
case TRANSITION:
transitionEffect();
break;
}
}
void updateGame() {
unsigned long currentTime = millis();
// Mettre à jour la direction en fonction du joystick
if (joyX < 1000) {
direction = 3; // gauche
} else if (joyX > 3000) {
direction = 1; // droite
} else if (joyY < 1000) {
direction = 0; // haut
} else if (joyY > 3000) {
direction = 2; // bas
}
// Mettre à jour la position de Pac-Man
if (currentTime - lastPacmanUpdate > pacmanUpdateInterval) {
updatePacman();
lastPacmanUpdate = currentTime;
}
// Mettre à jour les positions des fantômes
updateGhosts();
// Dessiner les éléments
tft.fillScreen(levels[currentLevel].backgroundColor);
drawWalls();
drawPoints();
drawSuperPoints();
drawPacman(pacmanX, pacmanY);
for (int i = 0; i < 4; i++) {
drawGhost(ghosts[i].x, ghosts[i].y, ghosts[i].color);
}
// Dessiner les téléporteurs et le boost de vitesse
drawTeleporters();
drawSpeedBoost();
// Afficher le score et les vies
tft.setCursor(0, 0);
tft.setTextColor(TFT_WHITE, levels[currentLevel].backgroundColor);
tft.setTextSize(2);
tft.printf("Score: %d Lives: %d", score, lives);
// Vérifier la fin du mode effrayé
if (currentTime > frightenedModeEndTime) {
for (int i = 0; i < 4; i++) {
ghosts[i].frightened = false;
}
}
// Vérifier la fin du boost de vitesse
if (speedBoostActive && currentTime > speedBoostEndTime) {
pacmanSpeed = 2;
speedBoostActive = false;
}
// Délai pour contrôler la vitesse de rafraîchissement
delay(20); // Réduction du délai pour des animations plus fluides
}
void updatePacman() {
// Calculer la nouvelle position de Pac-Man
int newX = pacmanX;
int newY = pacmanY;
switch (direction) {
case 0:
newY -= pacmanSpeed;
break;
case 1:
newX += pacmanSpeed;
break;
case 2:
newY += pacmanSpeed;
break;
case 3:
newX -= pacmanSpeed;
break;
}
// Vérifier les collisions avec les murs
if (!checkCollision(newX, newY, pacmanSize)) {
pacmanX = newX;
pacmanY = newY;
}
// Vérifier les collisions avec les points
for (int i = 0; i < numPoints; i++) {
if (!pointsCollected[i] && abs(pacmanX - points[i][0]) < pacmanSize && abs(pacmanY - points[i][1]) < pacmanSize) {
pointsCollected[i] = true;
score += 10;
playSound(2000, 100); // Son pour collecter un point
}
}
// Vérifier les collisions avec les super points
for (int i = 0; i < numSuperPoints; i++) {
if (!superPointsCollected[i] && abs(pacmanX - superPoints[i][0]) < pacmanSize && abs(pacmanY - superPoints[i][1]) < pacmanSize) {
superPointsCollected[i] = true;
score += 50;
frightenedModeEndTime = millis() + 10000; // Mode effrayé pendant 10 secondes
for (int j = 0; j < 4; j++) {
ghosts[j].frightened = true;
}
playSound(1500, 300); // Son pour collecter un super point
}
}
// Vérifier les collisions avec les téléporteurs
for (int i = 0; i < 2; i++) {
if (abs(pacmanX - levels[currentLevel].teleporters[i][0]) < pacmanSize && abs(pacmanY - levels[currentLevel].teleporters[i][1]) < pacmanSize) {
pacmanX = levels[currentLevel].teleporters[(i + 1) % 2][0];
pacmanY = levels[currentLevel].teleporters[(i + 1) % 2][1];
playSound(2500, 200); // Son pour utiliser un téléporteur
}
}
// Vérifier les collisions avec le boost de vitesse
if (abs(pacmanX - levels[currentLevel].speedBoost[0]) < pacmanSize && abs(pacmanY - levels[currentLevel].speedBoost[1]) < pacmanSize) {
pacmanSpeed = 4;
speedBoostActive = true;
speedBoostEndTime = millis() + 5000; // Boost de vitesse pendant 5 secondes
playSound(3000, 200); // Son pour le boost de vitesse
}
// Vérifier si tous les points sont collectés pour passer au niveau suivant
bool allPointsCollected = true;
for (int i = 0; i < numPoints; i++) {
if (!pointsCollected[i]) {
allPointsCollected = false;
break;
}
}
if (allPointsCollected) {
gameState = TRANSITION;
}
}
void updateGhosts() {
unsigned long currentTime = millis();
for (int i = 0; i < 4; i++) {
// Vérifier si c'est le moment de mettre à jour le fantôme
if (currentTime - ghosts[i].lastUpdate > 100) {
// Calculer la nouvelle position du fantôme
int newX = ghosts[i].x;
int newY = ghosts[i].y;
switch (ghosts[i].direction) {
case 0:
newY -= ghosts[i].speed;
break;
case 1:
newX += ghosts[i].speed;
break;
case 2:
newY += ghosts[i].speed;
break;
case 3:
newX -= ghosts[i].speed;
break;
}
// Vérifier les collisions avec les murs
if (checkCollision(newX, newY, pacmanSize)) {
// Changer de direction en cas de collision
ghosts[i].direction = (ghosts[i].direction + 1) % 4;
} else {
ghosts[i].x = newX;
ghosts[i].y = newY;
}
// Logique de poursuite de Pac-Man
if (!ghosts[i].frightened) {
if (abs(pacmanX - ghosts[i].x) > abs(pacmanY - ghosts[i].y)) {
ghosts[i].direction = (pacmanX > ghosts[i].x) ? 1 : 3;
} else {
ghosts[i].direction = (pacmanY > ghosts[i].y) ? 2 : 0;
}
}
// Vérifier les collisions avec Pac-Man
if (abs(ghosts[i].x - pacmanX) < pacmanSize && abs(ghosts[i].y - pacmanY) < pacmanSize) {
if (ghosts[i].frightened) {
// Fantôme mangé
ghosts[i].x = 60;
ghosts[i].y = 60;
score += 200;
playSound(1000, 500); // Son pour manger un fantôme
} else {
// Pac-Man perd une vie
lives--;
if (lives <= 0) {
// Game over, réinitialiser le jeu
gameState = GAME_OVER;
} else {
// Réinitialiser Pac-Man et les fantômes
pacmanX = 60;
pacmanY = 60;
for (int j = 0; j < 4; j++) {
ghosts[j].x = 60;
ghosts[j].y = 60;
}
// Réinitialiser les points collectés
for (int j = 0; j < numPoints; j++) {
pointsCollected[j] = false;
}
for (int j = 0; j < numSuperPoints; j++) {
superPointsCollected[j] = false;
}
playSound(500, 500); // Son pour perdre une vie
}
}
}
// Mettre à jour l'état d'animation du fantôme
ghosts[i].animationState = (ghosts[i].animationState + 1) % 2;
ghosts[i].lastUpdate = currentTime;
}
}
}
bool checkCollision(int x, int y, int size) {
for (int i = 0; i < numWalls; i++) {
int wx = levels[currentLevel].walls[i][0];
int wy = levels[currentLevel].walls[i][1];
int ww = levels[currentLevel].walls[i][2];
int wh = levels[currentLevel].walls[i][3];
if (x + size > wx && x - size < wx + ww && y + size > wy && y - size < wy + wh) {
return true;
}
}
return false;
}
void drawPacman(int x, int y) {
int mouthAngle = 0;
if (pacmanMouthState == 1) {
mouthAngle = 30;
} else if (pacmanMouthState == 2) {
mouthAngle = 60;
}
tft.fillCircle(x, y, pacmanSize, TFT_YELLOW);
tft.fillTriangle(x, y, x + pacmanSize * cos(mouthAngle * PI / 180), y + pacmanSize * sin(mouthAngle * PI / 180), x + pacmanSize * cos(-mouthAngle * PI / 180), y + pacmanSize * sin(-mouthAngle * PI / 180), levels[currentLevel].backgroundColor);
}
void drawGhost(int x, int y, uint16_t color) {
int animationOffset = (ghosts[0].animationState == 0) ? 0 : 2;
tft.fillCircle(x, y, pacmanSize, color);
tft.fillRect(x - pacmanSize, y, pacmanSize * 2, pacmanSize, color);
for (int i = -pacmanSize; i < pacmanSize; i += 4) {
tft.fillRect(x + i, y + pacmanSize, 2, 2 + animationOffset, levels[currentLevel].backgroundColor);
}
if (ghosts[0].frightened) {
tft.fillCircle(x, y, pacmanSize, TFT_WHITE);
tft.fillRect(x - pacmanSize, y, pacmanSize * 2, pacmanSize, TFT_WHITE);
for (int i = -pacmanSize; i < pacmanSize; i += 4) {
tft.fillRect(x + i, y + pacmanSize, 2, 2 + animationOffset, levels[currentLevel].backgroundColor);
}
}
}
void drawWalls() {
for (int i = 0; i < numWalls; i++) {
tft.fillRect(levels[currentLevel].walls[i][0], levels[currentLevel].walls[i][1], levels[currentLevel].walls[i][2], levels[currentLevel].walls[i][3], levels[currentLevel].wallColor);
}
}
void drawPoints() {
for (int i = 0; i < numPoints; i++) {
if (!pointsCollected[i]) {
tft.fillCircle(points[i][0], points[i][1], 3, TFT_WHITE);
}
}
}
void drawSuperPoints() {
for (int i = 0; i < numSuperPoints; i++) {
if (!superPointsCollected[i]) {
tft.fillCircle(superPoints[i][0], superPoints[i][1], 5, TFT_GREEN);
}
}
}
void drawTeleporters() {
for (int i = 0; i < 2; i++) {
tft.drawCircle(levels[currentLevel].teleporters[i][0], levels[currentLevel].teleporters[i][1], 6, TFT_PURPLE);
}
}
void drawSpeedBoost() {
tft.fillCircle(levels[currentLevel].speedBoost[0], levels[currentLevel].speedBoost[1], 4, TFT_CYAN);
}
void nextLevel() {
currentLevel = (currentLevel + 1) % numLevels;
gameState = PLAYING;
// Réinitialiser Pac-Man et les fantômes
pacmanX = 60;
pacmanY = 60;
for (int j = 0; j < 4; j++) {
ghosts[j].x = 60;
ghosts[j].y = 60;
}
// Réinitialiser les points collectés
for (int j = 0; j < numPoints; j++) {
pointsCollected[j] = false;
}
for (int j = 0; j < numSuperPoints; j++) {
superPointsCollected[j] = false;
}
}
void generatePoints() {
// Générer des points aléatoires
for (int i = 0; i < numPoints; i++) {
points[i][0] = random(10, 110);
points[i][1] = random(10, 110);
}
// Générer des super points aléatoires
for (int i = 0; i < numSuperPoints; i++) {
superPoints[i][0] = random(10, 110);
superPoints[i][1] = random(10, 110);
}
}
void showMenu() {
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 50);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(3);
tft.println("PAC-MAN");
tft.setTextSize(2);
tft.setCursor(20, 90);
tft.println("Press button to start");
}
void showPauseScreen() {
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 50);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(3);
tft.println("PAUSED");
tft.setTextSize(2);
tft.setCursor(20, 90);
tft.println("Press button to resume");
}
void showGameOverScreen() {
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 50);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(3);
tft.println("GAME OVER");
tft.setTextSize(2);
tft.setCursor(20, 90);
tft.println("Press button to restart");
}
void resetGame() {
score = 0;
lives = 3;
currentLevel = 0;
generatePoints();
pacmanX = 60;
pacmanY = 60;
for (int j = 0; j < 4; j++) {
ghosts[j].x = 60;
ghosts[j].y = 60;
}
for (int j = 0; j < numPoints; j++) {
pointsCollected[j] = false;
}
for (int j = 0; j < numSuperPoints; j++) {
superPointsCollected[j] = false;
}
}
void playSound(int frequency, int duration) {
tone(BUZZER_PIN, frequency, duration);
delay(duration);
noTone(BUZZER_PIN);
}
void transitionEffect() {
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 50);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(3);
tft.println("Level Complete!");
delay(1000);
nextLevel();
}