Gra Bitwa Morska | Arduino UNO

Typ_projektu
Arduino
Zdjecie główne
Krótki opis projektu

Gra typu "Space Invaders". Sterując okrętem pokonaj wrogą flotę zanim ona pokona Ciebie. 

Niezbędne elementy

1. Płytka Arduino UNO lub kompatybilna

2. Shield SIC Game Console

3. Ekran

4. Zasilanie

Opis projektu

1. Wprowadzenie

"Bitwa Morska" to gra zręcznościowa. Polega na szybkim eliminowaniu fal nadpływających przeciwników. Są 3 poziomy trudności: łatwy, średni, trudny. Z każdym kolejnym poziomem ilość fal się zwiększa, a przeciwnicy stają się silniejsi. Prowadzisz okręt, który pozwala Ci przetrwać 3 trafienia wrogich armat, ponieważ to co różni tą grę od "Space Invaders", to to że nieprzyjaciel także może odpowiedzieć ogniem. 

2. Początek

Gdy włączysz grę otworzy się przed tobą menu. Będziesz mógł/a wybrać, który poziom trudności Ci odpowiada. Wyższy poziom trudności to groźniejsi przeciwnicy, ale też wyższy wynik końcowy. 

3. Rozgrywka

Będziesz atakowany/a przez fale okrętów, które powoli będą zbliżać się w twoim kierunku. Każdy z okrętów przeciwnika może trafić Cię i wysłać do menu, dlatego poruszając się lewo prawo unikaj ich ataków. Jedynym sposobem, aby ich pokonać jest zatopienie ich poprzez strzelanie. Na poziomie trudnym musisz trafić aż 3 razy, aby zatopić chociaż jednego przeciwnika.

4. Ekran końcowy

Gdy wygrasz bądź przegrasz pojawi się ekran końcowy informujący Cię o Twoim wyniku, a ty będziesz mógł/a powrócić do menu.

Zdjęcia
kod programu
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

#define I2C_ADDRESS   0x3C
#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1

Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ==================== PRZYCISKI ====================
#define BTN_MENU   4
#define BTN_SELECT 5
#define BTN_UP     7
#define BTN_RIGHT  8
#define BTN_DOWN   9
#define BTN_LEFT   10

// ==================== STANY GRY ====================
enum GameState {
  MENU,
  PLAYING,
  GAME_OVER,
  WIN
};

GameState gameState = MENU;

// ==================== LIMITY ====================
#define MAX_ROWS 2
#define MAX_COLS 4

// ==================== POZIOM / TRUDNOŚĆ ====================
int difficulty = 1;   // 0 = łatwy, 1 = średni, 2 = trudny
int level = 1;

// ==================== PARAMETRY GRY ====================
unsigned long moveInterval = 220;
int formationDrop = 2;
int dropEveryBounces = 2;
unsigned long extraDropInterval = 999999UL;

int activeRows = 1;
int activeCols = 3;
int enemyGapX = 11;
int enemyGapY = 6;

// ==================== GRACZ ====================
int playerX = 46;
const int playerY = 49;
const int playerW = 28;
const int playerH = 14;
int playerDir = 1;          // 1 = w prawo, -1 = w lewo
int playerSpeed = 3;
int lives = 3;

// ==================== POCISK GRACZA ====================
bool bulletActive = false;
int bulletX = 0;
int bulletY = 0;
int bulletSpeed = 6;
unsigned long lastShotTime = 0;
unsigned long shotCooldown = 75;

// ==================== POCISK PRZECIWNIKA ====================
bool enemyBulletActive = false;
int enemyBulletX = 0;
int enemyBulletY = 0;
int enemyBulletSpeed = 2;
unsigned long enemyShotInterval = 1800;
unsigned long lastEnemyShotTime = 0;

// ==================== NIETYKALNOŚĆ / EFEKT ====================
bool invulnerable = false;
unsigned long invulnerableStart = 0;
const unsigned long invulnerableTime = 1000;

bool hitEffect = false;
unsigned long hitEffectStart = 0;
const unsigned long hitEffectTime = 120;

// ==================== PRZECIWNICY ====================
const int enemyW = 22;
const int enemyH = 14;

bool enemyAlive[MAX_ROWS][MAX_COLS];
int  enemyHP[MAX_ROWS][MAX_COLS];

int formationX = 10;
int formationY = 4;
int formationDir = 1;
int bounceCounter = 0;

// ==================== CZAS ====================
unsigned long lastMoveTime = 0;
unsigned long extraDropTimer = 0;
unsigned long lastButtonTime = 0;
const unsigned long buttonDebounce = 150;

// ==================== WYNIK ====================
int score = 0;

// ======================================================
// RYSOWANIE - STATEK GRACZA
// ======================================================

void drawPlayerBoatRight() {
  int x = playerX;
  int y = playerY;

  // kadłub
  display.drawLine(x + 2,  y + 9,  x + 22, y + 9,  SH110X_WHITE);
  display.drawLine(x + 4,  y + 10, x + 20, y + 10, SH110X_WHITE);
  display.drawLine(x + 6,  y + 11, x + 18, y + 11, SH110X_WHITE);

  // dziób i rufa
  display.drawLine(x + 0,  y + 7,  x + 2,  y + 9, SH110X_WHITE);
  display.drawLine(x + 22, y + 9,  x + 27, y + 7, SH110X_WHITE);

  // dół kadłuba
  display.drawLine(x + 3,  y + 9,  x + 8,  y + 13, SH110X_WHITE);
  display.drawLine(x + 8,  y + 13, x + 15, y + 13, SH110X_WHITE);
  display.drawLine(x + 15, y + 13, x + 22, y + 9,  SH110X_WHITE);

  // nadbudówka
  display.drawRect(x + 18, y + 5, 4, 4, SH110X_WHITE);

  // 3 maszty
  display.drawLine(x + 8,  y + 3, x + 8,  y + 9, SH110X_WHITE);
  display.drawLine(x + 14, y + 0, x + 14, y + 9, SH110X_WHITE);
  display.drawLine(x + 20, y + 2, x + 20, y + 9, SH110X_WHITE);

  // mały przedni żagiel
  display.drawLine(x + 8, y + 4, x + 5, y + 6, SH110X_WHITE);
  display.drawLine(x + 5, y + 6, x + 8, y + 8, SH110X_WHITE);

  // główny żagiel
  display.drawLine(x + 14, y + 1, x + 8,  y + 4, SH110X_WHITE);
  display.drawLine(x + 8,  y + 4, x + 14, y + 8, SH110X_WHITE);
  display.drawLine(x + 14, y + 2, x + 9,  y + 5, SH110X_WHITE);
  display.drawLine(x + 14, y + 3, x + 10, y + 6, SH110X_WHITE);

  // tylny żagiel
  display.drawLine(x + 20, y + 3, x + 16, y + 5, SH110X_WHITE);
  display.drawLine(x + 16, y + 5, x + 20, y + 8, SH110X_WHITE);

  // flaga
  display.drawLine(x + 14, y + 0, x + 17, y + 1, SH110X_WHITE);
}

void drawPlayerBoatLeft() {
  int x = playerX;
  int y = playerY;

  // lustrzane odbicie prawej wersji
  display.drawLine(x + 5,  y + 9,  x + 25, y + 9,  SH110X_WHITE);
  display.drawLine(x + 7,  y + 10, x + 23, y + 10, SH110X_WHITE);
  display.drawLine(x + 9,  y + 11, x + 21, y + 11, SH110X_WHITE);

  display.drawLine(x + 0,  y + 7,  x + 5,  y + 9, SH110X_WHITE);
  display.drawLine(x + 25, y + 9,  x + 27, y + 7, SH110X_WHITE);

  display.drawLine(x + 5,  y + 9,  x + 12, y + 13, SH110X_WHITE);
  display.drawLine(x + 12, y + 13, x + 19, y + 13, SH110X_WHITE);
  display.drawLine(x + 19, y + 13, x + 24, y + 9,  SH110X_WHITE);

  display.drawRect(x + 6, y + 5, 4, 4, SH110X_WHITE);

  display.drawLine(x + 7,  y + 2, x + 7,  y + 9, SH110X_WHITE);
  display.drawLine(x + 13, y + 0, x + 13, y + 9, SH110X_WHITE);
  display.drawLine(x + 19, y + 3, x + 19, y + 9, SH110X_WHITE);

  display.drawLine(x + 7, y + 3, x + 11, y + 5, SH110X_WHITE);
  display.drawLine(x + 11, y + 5, x + 7, y + 8, SH110X_WHITE);

  display.drawLine(x + 13, y + 1, x + 19, y + 4, SH110X_WHITE);
  display.drawLine(x + 19, y + 4, x + 13, y + 8, SH110X_WHITE);
  display.drawLine(x + 13, y + 2, x + 18, y + 5, SH110X_WHITE);
  display.drawLine(x + 13, y + 3, x + 17, y + 6, SH110X_WHITE);

  display.drawLine(x + 19, y + 4, x + 22, y + 6, SH110X_WHITE);
  display.drawLine(x + 22, y + 6, x + 19, y + 8, SH110X_WHITE);

  display.drawLine(x + 13, y + 0, x + 10, y + 1, SH110X_WHITE);
}

void drawPlayerBoat() {
  if (invulnerable && ((millis() / 100) % 2 == 0)) return;

  if (playerDir >= 0) drawPlayerBoatRight();
  else drawPlayerBoatLeft();
}

// ======================================================
// RYSOWANIE - STATKI WROGA
// ======================================================

void drawEnemyShip1HP(int x, int y) {
  // lżejszy statek
  display.drawLine(x + 2,  y + 9,  x + 18, y + 9,  SH110X_WHITE);
  display.drawLine(x + 4,  y + 10, x + 16, y + 10, SH110X_WHITE);

  display.drawLine(x,      y + 7,  x + 2,  y + 9, SH110X_WHITE);
  display.drawLine(x + 18, y + 9,  x + 21, y + 7, SH110X_WHITE);

  display.drawLine(x + 3,  y + 9,  x + 8,  y + 13, SH110X_WHITE);
  display.drawLine(x + 8,  y + 13, x + 13, y + 13, SH110X_WHITE);
  display.drawLine(x + 13, y + 13, x + 18, y + 9,  SH110X_WHITE);

  display.drawLine(x + 10, y + 1, x + 10, y + 9, SH110X_WHITE);
  display.drawLine(x + 16, y + 3, x + 16, y + 9, SH110X_WHITE);

  display.drawLine(x + 10, y + 2, x + 4,  y + 5, SH110X_WHITE);
  display.drawLine(x + 4,  y + 5, x + 10, y + 8, SH110X_WHITE);
  display.drawLine(x + 10, y + 3, x + 5,  y + 6, SH110X_WHITE);

  display.drawLine(x + 16, y + 4, x + 12, y + 6, SH110X_WHITE);
  display.drawLine(x + 12, y + 6, x + 16, y + 8, SH110X_WHITE);
}

void drawEnemyShip2HP(int x, int y) {
  // cięższy statek
  display.drawLine(x + 1,  y + 8,  x + 19, y + 8,  SH110X_WHITE);
  display.drawLine(x + 3,  y + 9,  x + 18, y + 9,  SH110X_WHITE);
  display.drawLine(x + 5,  y + 10, x + 16, y + 10, SH110X_WHITE);

  display.drawLine(x,      y + 6,  x + 1,  y + 8, SH110X_WHITE);
  display.drawLine(x + 19, y + 8,  x + 21, y + 6, SH110X_WHITE);

  display.drawLine(x + 2,  y + 8,  x + 7,  y + 13, SH110X_WHITE);
  display.drawLine(x + 7,  y + 13, x + 14, y + 13, SH110X_WHITE);
  display.drawLine(x + 14, y + 13, x + 19, y + 8,  SH110X_WHITE);

  display.drawRect(x + 13, y + 4, 4, 4, SH110X_WHITE);

  display.drawLine(x + 7,  y + 3, x + 7,  y + 8, SH110X_WHITE);
  display.drawLine(x + 11, y + 0, x + 11, y + 8, SH110X_WHITE);
  display.drawLine(x + 16, y + 2, x + 16, y + 8, SH110X_WHITE);

  display.drawLine(x + 11, y + 1, x + 5,  y + 4, SH110X_WHITE);
  display.drawLine(x + 5,  y + 4, x + 11, y + 7, SH110X_WHITE);
  display.drawLine(x + 11, y + 2, x + 6,  y + 5, SH110X_WHITE);

  display.drawLine(x + 7, y + 4, x + 4, y + 6, SH110X_WHITE);
  display.drawLine(x + 4, y + 6, x + 7, y + 8, SH110X_WHITE);

  display.drawLine(x + 16, y + 3, x + 13, y + 5, SH110X_WHITE);
  display.drawLine(x + 13, y + 5, x + 16, y + 8, SH110X_WHITE);
}

void drawEnemyShip3HP(int x, int y) {
  // ciężki okręt / boss-like
  display.drawLine(x + 2,  y + 9,  x + 19, y + 9,  SH110X_WHITE);
  display.drawLine(x + 4,  y + 10, x + 17, y + 10, SH110X_WHITE);
  display.drawLine(x + 6,  y + 11, x + 15, y + 11, SH110X_WHITE);

  display.drawLine(x,      y + 7,  x + 2,  y + 9, SH110X_WHITE);
  display.drawLine(x + 19, y + 9,  x + 21, y + 7, SH110X_WHITE);

  display.drawLine(x + 3,  y + 9,  x + 7,  y + 13, SH110X_WHITE);
  display.drawLine(x + 7,  y + 13, x + 14, y + 13, SH110X_WHITE);
  display.drawLine(x + 14, y + 13, x + 18, y + 9,  SH110X_WHITE);

  // centralna nadbudówka
  display.drawRect(x + 9, y + 4, 5, 4, SH110X_WHITE);

  // 3 maszty
  display.drawLine(x + 6,  y + 3, x + 6,  y + 9, SH110X_WHITE);
  display.drawLine(x + 11, y + 1, x + 11, y + 9, SH110X_WHITE);
  display.drawLine(x + 16, y + 3, x + 16, y + 9, SH110X_WHITE);

  // żagle lewe
  display.drawLine(x + 6,  y + 4, x + 3,  y + 6, SH110X_WHITE);
  display.drawLine(x + 3,  y + 6, x + 6,  y + 8, SH110X_WHITE);

  // żagiel środkowy większy
  display.drawLine(x + 11, y + 2, x + 6,  y + 5, SH110X_WHITE);
  display.drawLine(x + 6,  y + 5, x + 11, y + 8, SH110X_WHITE);
  display.drawLine(x + 11, y + 3, x + 7,  y + 6, SH110X_WHITE);

  // żagle prawe
  display.drawLine(x + 16, y + 4, x + 13, y + 6, SH110X_WHITE);
  display.drawLine(x + 13, y + 6, x + 16, y + 8, SH110X_WHITE);

  // dodatkowe działa/ozdoby
  display.drawPixel(x + 5, y + 10, SH110X_WHITE);
  display.drawPixel(x + 11, y + 10, SH110X_WHITE);
  display.drawPixel(x + 17, y + 10, SH110X_WHITE);
}

void drawEnemyShip(int x, int y, int hp) {
  if (hp >= 3) drawEnemyShip3HP(x, y);
  else if (hp == 2) drawEnemyShip2HP(x, y);
  else              drawEnemyShip1HP(x, y);
}

void drawEnemies() {
  for (int r = 0; r < activeRows; r++) {
    for (int c = 0; c < activeCols; c++) {
      if (enemyAlive[r][c]) {
        int x = formationX + c * (enemyW + enemyGapX);
        int y = formationY + r * (enemyH + enemyGapY);
        drawEnemyShip(x, y, enemyHP[r][c]);
      }
    }
  }
}

// ======================================================
// RYSOWANIE - POCISKI / HUD
// ======================================================

void drawBullet() {
  if (bulletActive) {
    display.fillCircle(bulletX, bulletY, 1, SH110X_WHITE);
  }
}

void drawEnemyBullet() {
  if (enemyBulletActive) {
    display.fillCircle(enemyBulletX, enemyBulletY, 1, SH110X_WHITE);
  }
}

void drawLives() {
  display.setCursor(58, 0);
  display.print("Z:");
  display.print(lives);
}

void drawHUD() {
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);

  display.setCursor(0, 0);
  display.print("S:");
  display.print(score);

  display.setCursor(30, 0);
  display.print("L:");
  display.print(level);

  drawLives();

  display.setCursor(98, 0);
  if (difficulty == 0) display.print("E");
  else if (difficulty == 1) display.print("S");
  else display.print("T");
}

void drawMenu() {
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);

  display.setCursor(25, 6);
  display.print("BITWA MORSKA");

  display.setCursor(22, 18);
  display.print("Wybierz trudnosc");

  display.setCursor(28, 32);
  if (difficulty == 0) display.print("> Latwy");
  else                 display.print("  Latwy");

  display.setCursor(28, 42);
  if (difficulty == 1) display.print("> Sredni");
  else                 display.print("  Sredni");

  display.setCursor(28, 52);
  if (difficulty == 2) display.print("> Trudny");
  else                 display.print("  Trudny");
}

void drawGameOver() {
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(34, 20);
  display.print("GAME OVER");
  display.setCursor(28, 32);
  display.print("Wynik: ");
  display.print(score);
}

void drawWin() {
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(40, 20);
  display.print("WYGRANA");
  display.setCursor(28, 32);
  display.print("Wynik: ");
  display.print(score);
}

void drawHitEffect() {
  if (hitEffect) {
    if (millis() - hitEffectStart < hitEffectTime) {
      display.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SH110X_WHITE);
    } else {
      hitEffect = false;
    }
  }
}

// ======================================================
// LOGIKA
// ======================================================

void centerFormation() {
  int formationWidth = activeCols * enemyW + (activeCols - 1) * enemyGapX;
  formationX = (SCREEN_WIDTH - formationWidth) / 2;
  if (formationX < 1) formationX = 1;
}

void applyDifficulty() {
  // na obu levelach ten sam układ: 1 rząd
  activeRows = 1;
  enemyGapY = 6;

  // liczba statków zależna od trudności
  if (difficulty == 0) activeCols = 3;   // łatwy
  else                 activeCols = 4;   // średni i trudny

  if (difficulty == 0) {
    playerSpeed = 3;
    bulletSpeed = 6;
    shotCooldown = 65;
    enemyGapX = 12;

    if (level == 1) {
      moveInterval = 245;
      formationDrop = 1;
      dropEveryBounces = 2;
      enemyShotInterval = 2200;
      enemyBulletSpeed = 2;
    } else {
      moveInterval = 225;
      formationDrop = 1;
      dropEveryBounces = 2;
      enemyShotInterval = 1800;
      enemyBulletSpeed = 2;
    }

    extraDropInterval = 999999UL;
  }
  else if (difficulty == 1) {
    playerSpeed = 3;
    bulletSpeed = 6;
    shotCooldown = 75;
    enemyGapX = 11;

    if (level == 1) {
      moveInterval = 215;
      formationDrop = 2;
      dropEveryBounces = 2;
      enemyShotInterval = 1700;
      enemyBulletSpeed = 2;
    } else {
      moveInterval = 190;
      formationDrop = 2;
      dropEveryBounces = 2;
      enemyShotInterval = 1350;
      enemyBulletSpeed = 2;
    }

    extraDropInterval = 999999UL;
  }
  else {  // trudny
    playerSpeed = 4;
    bulletSpeed = 5;
    shotCooldown = 95;
    enemyGapX = 10;

    if (level == 1) {
      moveInterval = 190;
      formationDrop = 2;
      dropEveryBounces = 1;
      enemyShotInterval = 1450;
      enemyBulletSpeed = 3;
    }
    else if (level == 2) {
      moveInterval = 170;
      formationDrop = 2;
      dropEveryBounces = 1;
      enemyShotInterval = 1150;
      enemyBulletSpeed = 3;
    }
    else {   // level 3 na trudnym
      moveInterval = 155;
      formationDrop = 2;
      dropEveryBounces = 1;
      enemyShotInterval = 900;
      enemyBulletSpeed = 4;
    }

    extraDropInterval = 2200UL;
  }
}

void initEnemies() {
  for (int r = 0; r < MAX_ROWS; r++) {
    for (int c = 0; c < MAX_COLS; c++) {
      enemyAlive[r][c] = false;
      enemyHP[r][c] = 0;
    }
  }

  for (int r = 0; r < activeRows; r++) {
    for (int c = 0; c < activeCols; c++) {
      enemyAlive[r][c] = true;

      if (level == 1) {
        enemyHP[r][c] = 1;
      }
      else if (level == 2) {
        enemyHP[r][c] = 2;
      }
      else if (difficulty == 2 && level == 3) {
        enemyHP[r][c] = 3;
      }
      else {
        enemyHP[r][c] = 2;
      }
    }
  }
}

void resetFormationPosition() {
  formationY = 4;
  formationDir = 1;
  bounceCounter = 0;
  centerFormation();

  enemyBulletActive = false;
  enemyBulletX = 0;
  enemyBulletY = 0;

  lastMoveTime = millis();
  extraDropTimer = millis();
  lastEnemyShotTime = millis();
}

void updateInvulnerability() {
  if (invulnerable && millis() - invulnerableStart > invulnerableTime) {
    invulnerable = false;
  }
}

void loseLife() {
  if (invulnerable) return;

  lives--;

  bulletActive = false;
  bulletX = 0;
  bulletY = 0;

  enemyBulletActive = false;
  enemyBulletX = 0;
  enemyBulletY = 0;

  hitEffect = true;
  hitEffectStart = millis();

  invulnerable = true;
  invulnerableStart = millis();

  if (lives <= 0) {
    gameState = GAME_OVER;
    return;
  }

  resetFormationPosition();
}

void resetGame() {
  level = 1;
  score = 0;
  lives = 3;

  playerX = 46;
  playerDir = 1;

  bulletActive = false;
  bulletX = 0;
  bulletY = 0;

  enemyBulletActive = false;
  enemyBulletX = 0;
  enemyBulletY = 0;

  invulnerable = false;
  hitEffect = false;

  applyDifficulty();
  initEnemies();
  resetFormationPosition();

  lastShotTime = 0;
}

void nextLevel() {
  level++;

  bulletActive = false;
  bulletX = 0;
  bulletY = 0;

  enemyBulletActive = false;
  enemyBulletX = 0;
  enemyBulletY = 0;

  invulnerable = false;
  hitEffect = false;

  applyDifficulty();
  initEnemies();
  resetFormationPosition();
}

bool allEnemiesDead() {
  for (int r = 0; r < activeRows; r++) {
    for (int c = 0; c < activeCols; c++) {
      if (enemyAlive[r][c]) return false;
    }
  }
  return true;
}

void handleMenuInput() {
  if (millis() - lastButtonTime < buttonDebounce) return;

  if (digitalRead(BTN_UP) == LOW) {
    difficulty--;
    if (difficulty < 0) difficulty = 2;
    lastButtonTime = millis();
    return;
  }

  if (digitalRead(BTN_DOWN) == LOW) {
    difficulty++;
    if (difficulty > 2) difficulty = 0;
    lastButtonTime = millis();
    return;
  }

  if (digitalRead(BTN_SELECT) == LOW) {
    resetGame();
    gameState = PLAYING;
    lastButtonTime = millis();
    return;
  }
}

void handleGameInput() {
  if (digitalRead(BTN_LEFT) == LOW) {
    playerX -= playerSpeed;
    playerDir = -1;
    if (playerX < 0) playerX = 0;
  }

  if (digitalRead(BTN_RIGHT) == LOW) {
    playerX += playerSpeed;
    playerDir = 1;
    if (playerX > SCREEN_WIDTH - playerW) playerX = SCREEN_WIDTH - playerW;
  }

  if (digitalRead(BTN_UP) == LOW) {
    if (!bulletActive && millis() - lastShotTime > shotCooldown) {
      bulletActive = true;

      if (playerDir >= 0) bulletX = playerX + 18;
      else                bulletX = playerX + 10;

      bulletY = playerY + 1;
      lastShotTime = millis();
    }
  }

  if (digitalRead(BTN_MENU) == LOW) {
    gameState = MENU;
    delay(180);
  }
}

void updateBullet() {
  if (!bulletActive) return;

  bulletY -= bulletSpeed;

  if (bulletY < 0) {
    bulletActive = false;
  }
}

void checkBulletCollision() {
  if (!bulletActive) return;

  for (int r = 0; r < activeRows; r++) {
    for (int c = 0; c < activeCols; c++) {
      if (!enemyAlive[r][c]) continue;

      int ex = formationX + c * (enemyW + enemyGapX);
      int ey = formationY + r * (enemyH + enemyGapY);

      bool hit =
        bulletX >= ex &&
        bulletX <= ex + enemyW &&
        bulletY >= ey &&
        bulletY <= ey + enemyH;

      if (hit) {
        enemyHP[r][c]--;

        if (enemyHP[r][c] <= 0) {
          enemyAlive[r][c] = false;

          if (difficulty == 0) score += 10;
          else if (difficulty == 1) score += 15;
          else {
            if (level == 3) score += 30;
            else score += 20;
          }
        }

        bulletActive = false;
        return;
      }
    }
  }
}

int getBottomAliveRowInColumn(int col) {
  for (int r = activeRows - 1; r >= 0; r--) {
    if (enemyAlive[r][col]) return r;
  }
  return -1;
}

void spawnEnemyBullet() {
  if (enemyBulletActive) return;

  int aliveCols[MAX_COLS];
  int aliveColsCount = 0;

  for (int c = 0; c < activeCols; c++) {
    int row = getBottomAliveRowInColumn(c);
    if (row != -1) {
      aliveCols[aliveColsCount++] = c;
    }
  }

  if (aliveColsCount == 0) return;

  int chosenIndex = random(aliveColsCount);
  int chosenCol = aliveCols[chosenIndex];
  int chosenRow = getBottomAliveRowInColumn(chosenCol);

  int ex = formationX + chosenCol * (enemyW + enemyGapX);
  int ey = formationY + chosenRow * (enemyH + enemyGapY);

  enemyBulletActive = true;
  enemyBulletX = ex + enemyW / 2;
  enemyBulletY = ey + enemyH - 1;
}

void updateEnemyBullet() {
  if (!enemyBulletActive) return;

  enemyBulletY += enemyBulletSpeed;

  if (enemyBulletY > SCREEN_HEIGHT) {
    enemyBulletActive = false;
    return;
  }

  if (!invulnerable) {
    bool hitPlayer =
      enemyBulletX >= playerX &&
      enemyBulletX <= playerX + playerW &&
      enemyBulletY >= playerY &&
      enemyBulletY <= playerY + playerH;

    if (hitPlayer) {
      enemyBulletActive = false;
      loseLife();
    }
  }
}

void enemyShootLogic() {
  if (enemyBulletActive) return;

  if (millis() - lastEnemyShotTime >= enemyShotInterval) {
    spawnEnemyBullet();
    lastEnemyShotTime = millis();
  }
}

void updateEnemies() {
  if (millis() - lastMoveTime < moveInterval) return;
  lastMoveTime = millis();

  int leftMost = 9999;
  int rightMost = -9999;
  int bottomMost = -9999;

  for (int r = 0; r < activeRows; r++) {
    for (int c = 0; c < activeCols; c++) {
      if (!enemyAlive[r][c]) continue;

      int ex = formationX + c * (enemyW + enemyGapX);
      int ey = formationY + r * (enemyH + enemyGapY);

      if (ex < leftMost) leftMost = ex;
      if (ex + enemyW > rightMost) rightMost = ex + enemyW;
      if (ey + enemyH > bottomMost) bottomMost = ey + enemyH;
    }
  }

  if (leftMost == 9999) return;

  formationX += formationDir;

  bool bounced = false;

  if (rightMost >= SCREEN_WIDTH - 2 && formationDir > 0) {
    formationDir = -1;
    bounced = true;
  }

  if (leftMost <= 1 && formationDir < 0) {
    formationDir = 1;
    bounced = true;
  }

  if (bounced) {
    bounceCounter++;

    if (bounceCounter >= dropEveryBounces) {
      formationY += formationDrop;
      bounceCounter = 0;
    }
  }

  if (difficulty == 2 && millis() - extraDropTimer >= extraDropInterval) {
    formationY += 1;
    extraDropTimer = millis();
  }

  if (!invulnerable && bottomMost >= playerY - 2) {
    loseLife();
  }
}

void handleEndScreenInput() {
  if (digitalRead(BTN_MENU) == LOW) {
    gameState = MENU;
    delay(180);
  }
}

// ======================================================
// SETUP / LOOP
// ======================================================

void setup() {
  delay(250);
  display.begin(I2C_ADDRESS, true);
  display.setRotation(2);
  display.clearDisplay();

  pinMode(BTN_MENU, INPUT_PULLUP);
  pinMode(BTN_SELECT, INPUT_PULLUP);
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_RIGHT, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);
  pinMode(BTN_LEFT, INPUT_PULLUP);

  randomSeed(analogRead(A0));

  applyDifficulty();
  initEnemies();
  resetFormationPosition();
}

void loop() {
  display.clearDisplay();

  if (gameState == MENU) {
    handleMenuInput();
    drawMenu();
  }
  else if (gameState == PLAYING) {
    updateInvulnerability();

    handleGameInput();
    updateBullet();
    checkBulletCollision();

    enemyShootLogic();
    updateEnemyBullet();

    if (gameState == PLAYING) {
      updateEnemies();
    }

    if (gameState == PLAYING && allEnemiesDead()) {
      if (difficulty == 2) {
        // trudny ma 3 levele
        if (level < 3) nextLevel();
        else gameState = WIN;
      } else {
        // łatwy i średni mają 2 levele
        if (level < 2) nextLevel();
        else gameState = WIN;
      }
    }

    if (gameState == PLAYING) {
      drawHUD();
      drawEnemies();
      drawBullet();
      drawEnemyBullet();
      drawPlayerBoat();
    }
  }
  else if (gameState == GAME_OVER) {
    drawGameOver();
    handleEndScreenInput();
  }
  else if (gameState == WIN) {
    drawWin();
    handleEndScreenInput();
  }

  drawHitEffect();

  display.display();
  delay(20);
} 
Schemat
Youtube
Tagi
BitwaMorska Arduino Gra Statki Okręty