#include #include #include #define I2C_ADDR 0x3c #define SCR_W 128 #define SCR_H 64 #define FRAME_MS 40 #define N_OBJ 3 #define N_BUBBLES 4 #define MAX_LVL 5 #define MAX_GAME_LVL 5 #define BTN_UP 4 #define BTN_DN 5 #define EAT_PER_GROW 2 static const uint8_t BW[] PROGMEM = { 6, 9, 13, 16, 20 }; static const uint8_t BH[] PROGMEM = { 4, 5, 7, 9, 11 }; static const uint8_t BR[] PROGMEM = { 2, 2, 3, 4, 5 }; static const uint8_t TL[] PROGMEM = { 3, 4, 5, 6, 8 }; static const uint8_t CW[] PROGMEM = { 9, 13, 18, 22, 28 }; static const uint8_t CH[] PROGMEM = { 4, 5, 7, 9, 11 }; #define PGM(arr, i) pgm_read_byte(&arr[i]) struct Obj { int16_t x; int8_t y; int8_t spd; uint8_t type; bool active; }; struct Bubble { int16_t x; int16_t y; uint8_t r; }; Adafruit_SH1106G display(SCR_W, SCR_H, &Wire, -1); static Obj objs[N_OBJ]; static Bubble bubbles[N_BUBBLES]; static int8_t playerY = 26; static uint8_t playerLvl = 1; static uint8_t score = 0; static uint8_t eatCount = 0; static uint8_t gameLevel = 1; static bool dead = false; static bool won = false; static uint32_t lastFrame = 0; static uint16_t lfsr = 0xACE1u; static uint8_t fastRand() { lfsr ^= lfsr >> 7; lfsr ^= lfsr << 9; lfsr ^= lfsr >> 13; return (uint8_t)lfsr; } static uint8_t rndRange(uint8_t lo, uint8_t hi) { if (lo >= hi) return lo; return lo + fastRand() % (hi - lo + 1); } void drawFish(int16_t x, int8_t y, uint8_t t, bool goRight) { if (t >= 5) t = 4; uint8_t ch = PGM(CH, t); uint8_t cw = PGM(CW, t); if (t == 0) { display.fillCircle(x + cw / 2, y + ch / 2, ch / 2, SH110X_WHITE); return; } uint8_t bw = PGM(BW, t); uint8_t bh = PGM(BH, t); uint8_t br = PGM(BR, t); uint8_t tl = PGM(TL, t); int8_t hy = y + bh / 2; if (goRight) { int16_t bodyX = x + tl; display.fillTriangle(bodyX, hy, x, y, x, y + bh - 1, SH110X_WHITE); display.fillRoundRect(bodyX, y, bw, bh, br, SH110X_WHITE); display.drawPixel(bodyX + bw - 2 - (br / 4), y + 1 + (bh / 6), SH110X_BLACK); } else { int16_t tailX = x + bw; display.fillTriangle(tailX, hy, tailX + tl, y, tailX + tl, y + bh - 1, SH110X_WHITE); display.fillRoundRect(x, y, bw, bh, br, SH110X_WHITE); display.drawPixel(x + 1 + (br / 4), y + 1 + (bh / 6), SH110X_BLACK); } } void updateBubbles() { for (uint8_t i = 0; i < N_BUBBLES; i++) { bubbles[i].y -= 1; if (bubbles[i].y < -10) { bubbles[i].y = SCR_H + rndRange(5, 20); bubbles[i].x = rndRange(5, SCR_W - 5); bubbles[i].r = rndRange(1, 3); } display.drawCircle(bubbles[i].x, bubbles[i].y, bubbles[i].r, SH110X_WHITE); } } void spawnObj(uint8_t i) { uint8_t maxSpawn = (playerLvl < 4) ? playerLvl + 1 : 4; uint8_t t = rndRange(0, maxSpawn); objs[i].type = t; objs[i].active = true; objs[i].y = rndRange(2, SCR_H - PGM(CH, t) - 2); int8_t sp = 1 + (gameLevel / 2); if (fastRand() & 1) { objs[i].x = -(int16_t)PGM(CW, t) - rndRange(10, 50); objs[i].spd = sp; } else { objs[i].x = SCR_W + rndRange(10, 50); objs[i].spd = -sp; } } int8_t checkHit(const Obj& o) { if (!o.active) return 0; uint8_t pw = PGM(CW, playerLvl), ph = PGM(CH, playerLvl); uint8_t ow = PGM(CW, o.type), oh = PGM(CH, o.type); if (50 < o.x + ow && 50 + pw > o.x && playerY < o.y + oh && playerY + ph > o.y) { if (o.type < playerLvl) return 1; if (o.type > playerLvl) return -1; } return 0; } void resetGame() { playerLvl = 1; playerY = 26; score = 0; eatCount = 0; gameLevel = 1; dead = false; won = false; for (uint8_t i = 0; i < N_OBJ; i++) spawnObj(i); for (uint8_t i = 0; i < N_BUBBLES; i++) { bubbles[i].x = rndRange(0, SCR_W); bubbles[i].y = rndRange(0, SCR_H); bubbles[i].r = rndRange(1, 3); } lastFrame = millis(); } void waitForButton() { delay(400); while (digitalRead(BTN_UP) && digitalRead(BTN_DN)) { yield(); delay(10); } delay(200); } void showStart() { display.clearDisplay(); drawFish(45, 8, 4, true); display.setTextSize(2); display.setTextColor(SH110X_WHITE); display.setCursor(35, 34); display.print(F("START")); display.setTextSize(1); display.setCursor(15, 54); display.print(F("[dowolny przycisk]")); display.display(); waitForButton(); } void showWin() { display.clearDisplay(); display.setTextSize(2); display.setCursor(30, 15); display.print(F("BRAWO!")); drawFish(15, 20, 1, true); drawFish(100, 20, 1, false); display.setTextSize(1); display.setCursor(10, 45); display.print(F("Wynik koncowy: ")); display.print(score); display.setCursor(5, 56); display.print(F("[nacisnij aby zagrac]")); display.display(); waitForButton(); resetGame(); } void showDead() { display.clearDisplay(); display.setTextSize(2); display.setCursor(10, 15); display.print(F("KONIEC!")); display.setTextSize(1); display.setCursor(10, 40); display.print(F("Wynik: ")); display.print(score); display.setCursor(10, 52); display.print(F("[dowolny przycisk]")); display.display(); waitForButton(); resetGame(); } void showLevelUp() { display.clearDisplay(); display.setTextSize(2); display.setCursor(20, 25); display.print(F("LEVEL ")); display.print(gameLevel); display.display(); delay(1500); } void setup() { pinMode(BTN_UP, INPUT_PULLUP); pinMode(BTN_DN, INPUT_PULLUP); delay(100); Wire.begin(); Wire.setClock(400000); Wire.setWireTimeout(5000, true); display.begin(I2C_ADDR, true); display.setRotation(2); resetGame(); showStart(); } void loop() { yield(); uint32_t now = millis(); if (now - lastFrame < FRAME_MS) return; lastFrame = now; if (won) { showWin(); return; } if (dead) { showDead(); return; } if (!digitalRead(BTN_UP)) playerY -= 2; if (!digitalRead(BTN_DN)) playerY += 2; playerY = constrain(playerY, 1, SCR_H - PGM(CH, playerLvl) - 1); display.clearDisplay(); updateBubbles(); for (uint8_t i = 0; i < N_OBJ; i++) { objs[i].x += objs[i].spd; if ((objs[i].spd > 0 && objs[i].x > SCR_W + 10) || (objs[i].spd < 0 && objs[i].x < -35)) { spawnObj(i); continue; } int8_t hit = checkHit(objs[i]); if (hit == 1) { score++; eatCount++; if (playerLvl < 4) { if (eatCount >= EAT_PER_GROW) { eatCount = 0; playerLvl++; } } else { if (eatCount >= EAT_PER_GROW) { eatCount = 0; if (gameLevel >= MAX_GAME_LVL) { won = true; return; } gameLevel++; playerLvl = 1; showLevelUp(); for (uint8_t j = 0; j < N_OBJ; j++) spawnObj(j); lastFrame = millis(); return; } } spawnObj(i); continue; } if (hit == -1) { dead = true; return; } if (objs[i].active) drawFish(objs[i].x, objs[i].y, objs[i].type, objs[i].spd > 0); } drawFish(50, playerY, playerLvl, true); display.setTextSize(1); display.setCursor(0, 0); display.print(score); display.setCursor(80, 0); display.print(F("LVL ")); display.print(gameLevel); display.display(); }