#include <Arduino.h>
#include <DFRobot_BMI160.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
DFRobot_BMI160 bmi160;
const int8_t i2c_addr = 0x68;
// --- Broches écran ---
const uint8_t TFT_DC = 9;
const uint8_t TFT_CS = 10;
const uint8_t TFT_MOSI = 11;
const uint8_t TFT_CLK = 13;
const uint8_t TFT_RST = 2;
const uint8_t TFT_MISO = 12;
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO); //paramétrage broches écran
// ------------------ Bande score ------------------------
const int SCORE_ZONE_HEIGHT = 25; // hauteur de la bande du score
// ------------------ Lecture accel X --------------------
int16_t readAccelX() {
uint8_t data[2];
Wire.beginTransmission(i2c_addr);
Wire.write(0x12); // registre DATA_12
if (Wire.endTransmission(false) != 0)
return 0;
Wire.requestFrom(i2c_addr, (uint8_t)2);
if (Wire.available() < 2)
return 0;
data[0] = Wire.read();
data[1] = Wire.read();
return (int16_t)((data[1] << 8) | data[0]);
}
// ------------------ Variables globales --------------------
float posX = 0, targetX = 0, filtered = 0;
float barX = 0;
int oldBarX = -1;
int barWidth = 50;
int barHeight = 10;
int bottomY;
float ballX, ballY;
float ballVx = 2.5;
float ballVy = -2.0;
int ballR = 6;
int lastBallX = -1;
int lastBallY = -1;
bool gameOver = false;
bool gameWon = false;
int score = 0;
unsigned long lastFrameTime = 0; //variable de "temps"
unsigned long lastSpeedIncrease = 0; // pour accélération progressive
float speedMultiplier = 1.0; // facteur d'accélération
const float MAX_SPEED_MULTIPLIER = 2.5; // limite max
// ----------- Définition des briques ---------------------
const int brickHeight = 10;
const int brickRows = 5;
const int brickCols = 8;
bool bricks[brickRows][brickCols];
int brickWidth;
// --------------------------------------------------------
void initBricks() {
for (int row = 0; row < brickRows; row++) {
for (int col = 0; col < brickCols; col++) {
bricks[row][col] = true;
}
}
}
// Dessine toutes les briques (appelé au reset)
void drawBricks() {
for (int row = 0; row < brickRows; row++) {
for (int col = 0; col < brickCols; col++) {
if (bricks[row][col]) {
int brickX = col * brickWidth;
int brickY = SCORE_ZONE_HEIGHT + row * brickHeight; // <-- décalage pour laisser la bande du score
uint16_t brickColor;
switch (row) {
case 0: brickColor = ILI9341_RED; break;
case 1: brickColor = ILI9341_GREEN; break;
case 2: brickColor = ILI9341_BLUE; break;
case 3: brickColor = ILI9341_YELLOW; break;
case 4: brickColor = ILI9341_MAGENTA; break;
default: brickColor = ILI9341_WHITE; break;
}
tft.fillRect(brickX, brickY, brickWidth, brickHeight, brickColor);
} else {
// s'assure qu'on nettoie la zone si la brique est marquée détruite (utile au reset partiel)
int brickX = col * brickWidth;
int brickY = SCORE_ZONE_HEIGHT + row * brickHeight;
tft.fillRect(brickX, brickY, brickWidth, brickHeight, ILI9341_BLACK);
}
}
}
}
// Détruit (et efface à l'écran) une brique précise
void destroyBrick(int row, int col) {
if (row < 0 || row >= brickRows || col < 0 || col >= brickCols) return;
if (!bricks[row][col]) return;
bricks[row][col] = false;
int brickX = col * brickWidth;
int brickY = SCORE_ZONE_HEIGHT + row * brickHeight;
tft.fillRect(brickX, brickY, brickWidth, brickHeight, ILI9341_BLACK);
score++;
}
// Vérifie si toutes les briques sont détruites
bool allBricksDestroyed() {
for (int r = 0; r < brickRows; r++) {
for (int c = 0; c < brickCols; c++) {
if (bricks[r][c]) return false;
}
}
return true;
}
// Vérification de collision balle <-> briques (en tenant compte du décalage)
bool checkBrickCollision() {
for (int row = 0; row < brickRows; row++) {
for (int col = 0; col < brickCols; col++) {
if (bricks[row][col]) {
int brickX = col * brickWidth;
int brickY = SCORE_ZONE_HEIGHT + row * brickHeight; // <-- décalage
if (ballX + ballR > brickX && ballX - ballR < brickX + brickWidth &&
ballY + ballR > brickY && ballY - ballR < brickY + brickHeight) {
destroyBrick(row, col);
// On inverse verticalement sans réinitialiser l'amplitude : on garde la vitesse courante
ballVy = -ballVy;
return true;
}
}
}
}
return false;
}
// Dessine la bande du score (toujours au-dessus)
void drawScore() {
tft.fillRect(0, 0, tft.width(), SCORE_ZONE_HEIGHT, ILI9341_BLACK);
tft.setCursor(5, 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print("Score: ");
tft.print(score);
}
// --------------------------------------------------------
void resetGame() {
tft.fillScreen(ILI9341_BLACK);
score = 0;
gameOver = false;
gameWon = false;
ballX = tft.width() / 2;
ballY = tft.height() / 2;
ballVx = 150.0f; // 150 pixels/seconde
ballVy = -130.0f; // 130 pixels/seconde
barX = tft.width() / 2;
speedMultiplier = 1.0;
lastSpeedIncrease = millis();
initBricks();
drawBricks();
drawScore();
oldBarX = -1; // Pour forcer le dessin de la raquette au premier loop après reset
}
void showVictoryScreen() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(4);
int16_t x1, y1;
uint16_t w, h;
String msg = "Gagne";
tft.getTextBounds(msg, 0, 0, &x1, &y1, &w, &h);
tft.setCursor((tft.width() - w) / 2, (tft.height() - h) / 2);
tft.print(msg);
delay(3000); // affiche le message pendant 3 secondes
resetGame();
}
// --------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(100);
Wire.begin();
lastFrameTime = millis();
if (bmi160.softReset() != BMI160_OK) {
Serial.println("reset false");
while (1);
}
if (bmi160.I2cInit(i2c_addr) != BMI160_OK) {
Serial.println("init false");
while (1);
}
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.println("Casse briques !");
bottomY = tft.height() - barHeight - 5;
brickWidth = tft.width() / brickCols;
delay(1000);
resetGame();
}
// --------------------------------------------------------
void loop() {
if (gameOver) {
tft.setCursor(60, tft.height() / 2);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(3);
tft.println("PERDU !");
delay(2000);
resetGame();
return;
}
if (gameWon) {
showVictoryScreen();
return;
}
// --- Lecture accelX et déplacement de la barre (physique)
int16_t accelX = readAccelX();
float ratio = constrain((float)accelX / 16000.0f, -1.0f, 1.0f);
filtered = 0.8f * filtered + 0.2f * ratio;
targetX = (tft.width() / 2) + (filtered * (tft.width() / 2 - barWidth / 2));
barX += (targetX - barX) * 0.35f;
int newBarX = (int)barX;
// --- Accélération progressive (physique)
unsigned long currentTime = millis();
float dt = (currentTime - lastFrameTime) / 1000.0f; // dt en secondes
if (dt > 0.05f) dt = 0.05f; // limite à 50 ms pour éviter les sauts
lastFrameTime = currentTime;
if (currentTime - lastSpeedIncrease > 3000) {
lastSpeedIncrease = currentTime;
if (speedMultiplier < MAX_SPEED_MULTIPLIER) {
speedMultiplier += 0.08;
if (speedMultiplier > MAX_SPEED_MULTIPLIER) speedMultiplier = MAX_SPEED_MULTIPLIER;
}
}
// --- Efface ANCIENNES positions (une seule fois) si nécessaire
// Efface ancienne balle
if (lastBallX >= 0 && lastBallY >= 0) {
tft.fillCircle(lastBallX, lastBallY, ballR, ILI9341_BLACK);
}
// Efface ancienne raquette seulement si elle existait et que la nouvelle position est différente
const int BAR_MOVE_THRESHOLD = 0.5;
if (oldBarX >= 0 && abs(newBarX - oldBarX) >= BAR_MOVE_THRESHOLD) {
tft.fillRect(oldBarX - barWidth / 2, bottomY, barWidth, barHeight, ILI9341_BLACK);
}
// --- Mise à jour de la balle (physique)
ballX += ballVx * speedMultiplier *dt;
ballY += ballVy * speedMultiplier *dt;
// Rebonds latéraux
if (ballX - ballR <= 0) {
ballX = ballR;
ballVx = -ballVx;
} else if (ballX + ballR >= tft.width()) {
ballX = tft.width() - ballR;
ballVx = -ballVx;
}
// Rebonds sur la ligne du score
if (ballY - ballR <= SCORE_ZONE_HEIGHT) {
ballY = SCORE_ZONE_HEIGHT + ballR;
ballVy = -ballVy;
}
// Collision avec la barre (paddle)
int barTop = bottomY;
if (ballY + ballR >= barTop && ballY + ballR <= barTop + barHeight &&
ballX >= newBarX - barWidth / 2 && ballX <= newBarX + barWidth / 2 &&
ballVy > 0) {
ballVy = -abs(ballVy);
float offset = (ballX - newBarX) / (barWidth / 2.0);
ballVx = constrain(offset * 150.0f, -150.0f, 150.0f);
}
// Si la balle tombe sous la barre -> game over
if (ballY - ballR > tft.height()) {
gameOver = true;
return;
}
static int lastScore = -1;
if (checkBrickCollision()) {
if (score != lastScore) {
// Met à jour juste la zone du score sans effacer toute la bande
tft.fillRect(70, 0, 60, SCORE_ZONE_HEIGHT, ILI9341_BLACK);
tft.setCursor(70, 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(score);
lastScore = score;
}
}
// --- Dessine NOUVELLES positions (une seule fois)
int newBallX = (int)ballX;
int newBallY = (int)ballY;
// Dessine la balle si sa position a changé
if (newBallX != lastBallX || newBallY != lastBallY) {
tft.fillCircle(newBallX, newBallY, ballR, ILI9341_YELLOW);
lastBallX = newBallX;
lastBallY = newBallY;
}
// Dessine la raquette seulement si elle a bougé (ou si c'est la première fois)
if (oldBarX < 0) {
// première fois : dessine sans effacer
tft.fillRect(newBarX - barWidth / 2, bottomY, barWidth, barHeight, ILI9341_WHITE);
oldBarX = newBarX;
} else if (abs(newBarX - oldBarX) >= BAR_MOVE_THRESHOLD) {
// effacement déjà fait plus haut : dessine la nouvelle
tft.fillRect(newBarX - barWidth / 2, bottomY, barWidth, barHeight, ILI9341_WHITE);
oldBarX = newBarX;
}
}
Comments