Gra typu "Space Invaders". Sterując okrętem pokonaj wrogą flotę zanim ona pokona Ciebie.
1. Płytka Arduino UNO lub kompatybilna
2. Shield SIC Game Console
3. Ekran
4. Zasilanie
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.
#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);
}