#include #include #include #include // OLED U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2( U8G2_R0, U8X8_PIN_NONE ); // Touch / Input Pins #define PIN_LEFT D1 #define PIN_RIGHT D7 #define PIN_DOWN D8 #define PIN_UP D0 #define PIN_Q1 D9 #define PIN_Q2 D10 #define N_TOUCH 6 #define THRESHOLD 6 enum TouchIndex { T_LEFT = 0, T_RIGHT, T_DOWN, T_UP, T_Q1, T_Q2 }; const uint8_t touchPins[N_TOUCH] = { PIN_LEFT, PIN_RIGHT, PIN_DOWN, PIN_UP, PIN_Q1, PIN_Q2 }; int touchValue[N_TOUCH]; bool touchedNow[N_TOUCH] = { false, false, false, false, false, false }; bool touchedBefore[N_TOUCH] = { false, false, false, false, false, false }; // Screen const int SCREEN_W = 128; const int SCREEN_H = 64; const int PLAY_TOP = 11; const int PLAY_BOTTOM = 61; // Sprite Sizes const int PLAYER_W = 14; const int PLAYER_H = 9; const int ENEMY_W = 10; const int ENEMY_H = 7; const int BOSS_W = 22; const int BOSS_H = 19; // Game State enum GameState { STATE_TITLE, STATE_PLAYING, STATE_BOSS, STATE_PAUSED, STATE_LEVEL_CLEAR, STATE_GAME_OVER }; GameState gameState = STATE_TITLE; GameState stateBeforePause = STATE_PLAYING; // Game Data unsigned long score = 0; int lives = 4; const unsigned long BOSS_SCORE = 800; // SPACE IMPACT LOGO 128x64 const unsigned char epd_bitmap_co7orh[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xe3, 0xff, 0xfe, 0x07, 0xff, 0x80, 0x7f, 0xff, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe3, 0xff, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xfc, 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0x83, 0xff, 0xff, 0x1f, 0xff, 0xc3, 0xff, 0xfc, 0x3f, 0xff, 0xf0, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x03, 0x80, 0x3e, 0x3c, 0x07, 0xc7, 0xf0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x07, 0x80, 0x1c, 0x3c, 0x07, 0xc7, 0xe0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x07, 0x80, 0x1c, 0x3c, 0x07, 0x87, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x07, 0xff, 0xf8, 0x7f, 0xff, 0x87, 0xc0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x07, 0xff, 0xf8, 0x7f, 0xff, 0x87, 0xc0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x07, 0xff, 0xff, 0x07, 0xff, 0xf8, 0x7f, 0xff, 0x8f, 0xc0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0xff, 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0x9f, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0x80, 0x00, 0xf8, 0x3f, 0x1f, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x1f, 0x80, 0x01, 0xf8, 0x3e, 0x1f, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0x80, 0x01, 0xf8, 0x3e, 0x1f, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x1f, 0x80, 0x01, 0xf8, 0x3e, 0x1f, 0xf8, 0x80, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x1f, 0x80, 0x01, 0xe0, 0x7c, 0x1e, 0x00, 0xc0, 0x07, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x3f, 0x00, 0x03, 0xe0, 0xfc, 0x00, 0x00, 0x03, 0xc1, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xe0, 0x3e, 0x00, 0x03, 0xe0, 0xfc, 0x00, 0x48, 0x0f, 0xe0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x27, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0f, 0xf8, 0x1f, 0xe3, 0xff, 0xfc, 0x07, 0xf0, 0x00, 0x61, 0x86, 0x3f, 0xff, 0xfc, 0x00, 0x7f, 0x0f, 0xfc, 0x3f, 0xe3, 0xff, 0xff, 0x0f, 0xff, 0xc0, 0xc0, 0x0c, 0x3f, 0xff, 0xe0, 0x00, 0x7f, 0x0f, 0xfe, 0x7f, 0xc3, 0xff, 0xff, 0x1f, 0xff, 0xc1, 0xd8, 0x1c, 0x7f, 0xff, 0xe0, 0x00, 0x7e, 0x0f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0x3f, 0xff, 0xc3, 0xff, 0xfc, 0xff, 0xff, 0xe0, 0x00, 0xfe, 0x0f, 0xff, 0xff, 0xc7, 0x00, 0x3c, 0x3c, 0x07, 0xcf, 0xe0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xfe, 0x1f, 0xff, 0xff, 0x87, 0x00, 0x3c, 0x3c, 0x07, 0x8f, 0xc0, 0x00, 0x01, 0xf8, 0x00, 0x00, 0xfe, 0x1f, 0xff, 0xff, 0x87, 0x80, 0x38, 0x3c, 0x0f, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x00, 0x10, 0xfe, 0x1f, 0xff, 0xff, 0x87, 0xff, 0xf8, 0xff, 0xff, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x00, 0x00, 0xf8, 0x1f, 0x0f, 0x3f, 0x87, 0xff, 0xf8, 0xff, 0xff, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x00, 0x00, 0xf8, 0x1f, 0x0c, 0x3f, 0x8f, 0xff, 0xe0, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x3f, 0x1f, 0xc1, 0x20, 0xf9, 0xff, 0x1f, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x03, 0xf8, 0x7f, 0x00, 0x3e, 0x1f, 0x00, 0x01, 0xf0, 0x3e, 0x1f, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x03, 0xf8, 0x7e, 0x00, 0xfe, 0x1f, 0x00, 0x01, 0xf0, 0x3e, 0x1f, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x03, 0xf0, 0x7c, 0x00, 0xfe, 0x1f, 0x00, 0x01, 0xf0, 0x3e, 0x1f, 0xff, 0xc0, 0x07, 0xc0, 0x00, 0x03, 0xf0, 0x7c, 0x00, 0xfe, 0x1f, 0x00, 0x01, 0xf0, 0x3e, 0x1f, 0xff, 0xe0, 0x07, 0xc0, 0x00, 0x07, 0xf0, 0xfc, 0x00, 0xfc, 0x7f, 0x00, 0x07, 0xe0, 0xf8, 0x1f, 0xff, 0xe0, 0x1f, 0xc0, 0x00, 0x07, 0xf0, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x07, 0xe0, 0xf8, 0x03, 0xff, 0xc0, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // PLAYER SHIP - custom like reference image // 14x9 const char playerShip[PLAYER_H][PLAYER_W + 1] = { "..##..........", ".####.........", "######........", "##.#####......", "###########...", "##.#####......", "######........", ".####.........", "..##.........." }; // DEFAULT NORMAL ENEMY 10x7 const unsigned char enemySprite[] PROGMEM = { 0x20, 0x00, 0x70, 0x00, 0xFC, 0x00, 0xFF, 0x03, 0xFC, 0x00, 0x70, 0x00, 0x20, 0x00 }; // BOSS / KING - custom monster 22x19 const char bossMonster[BOSS_H][BOSS_W + 1] = { "........#####..###....", ".......##.##.######...", "###...#####..#####....", "##############.##.....", "##.##############.....", ".#..###.#.#..#####....", ".#####..####..####..##", "..##...######....#####", ".##...###.###.##.#####", ".#....###.###..#######", ".#....######....#####.", ".#.#.........##.####..", ".#######......#####...", ".##.#.#.#...##.##.....", ".##.####..##.####.....", ".#.#.#.....####.##....", ".#.......######.##....", "..##########.######...", ".....###.##...#####..." }; // Structs struct Player { int x; int y; bool invincible; unsigned long invincibleStart; }; struct Bullet { int x; int y; bool active; }; struct EnemyBullet { int x; int y; int vx; int vy; bool active; }; struct Enemy { int x; int y; int baseY; int hp; int speed; int movement; float phase; bool shoots; bool active; unsigned long lastShot; }; struct Star { int x; int y; int speed; }; struct Explosion { int x; int y; int frame; bool active; unsigned long lastFrame; }; struct Boss { int x; int y; int hp; int maxHP; int verticalDirection; bool active; bool charging; bool returning; unsigned long lastShot; unsigned long lastCharge; }; // Objects Player player; const int MAX_BULLETS = 8; Bullet bullets[MAX_BULLETS]; const int MAX_ENEMY_BULLETS = 8; EnemyBullet enemyBullets[MAX_ENEMY_BULLETS]; const int MAX_ENEMIES = 6; Enemy enemies[MAX_ENEMIES]; const int MAX_STARS = 16; Star stars[MAX_STARS]; const int MAX_EXPLOSIONS = 6; Explosion explosions[MAX_EXPLOSIONS]; Boss boss; // Timers unsigned long lastPlayerMove = 0; unsigned long lastBulletUpdate = 0; unsigned long lastEnemyUpdate = 0; unsigned long lastEnemySpawn = 0; unsigned long lastEnemyBulletUpdate = 0; unsigned long lastStarUpdate = 0; const unsigned long PLAYER_INTERVAL = 25; const unsigned long BULLET_INTERVAL = 25; const unsigned long ENEMY_INTERVAL = 45; const unsigned long ENEMY_BULLET_INTERVAL = 35; const unsigned long STAR_INTERVAL = 50; // Touch Helpers void updateTouch() { const int maxCount = 200; for (int i = 0; i < N_TOUCH; i++) { touchedBefore[i] = touchedNow[i]; pinMode(touchPins[i], OUTPUT); digitalWrite(touchPins[i], LOW); delayMicroseconds(25); pinMode(touchPins[i], INPUT_PULLUP); int count = 0; while (digitalRead(touchPins[i]) == LOW && count < maxCount) { count++; } touchValue[i] = count; touchedNow[i] = count > THRESHOLD; } } bool touchHeld(int index) { return touchedNow[index]; } bool touchPressed(int index) { return touchedNow[index] && !touchedBefore[index]; } // Draw Logo // bitmap is MSB first void drawLogo() { for (int y = 0; y < 64; y++) { for (int x = 0; x < 128; x++) { int byteIndex = y * 16 + x / 8; uint8_t data = pgm_read_byte(&epd_bitmap_co7orh[byteIndex]); if (data & (0x80 >> (x & 7))) { u8g2.drawPixel(x, y); } } } } // Reset Game void resetGame() { score = 0; lives = 4; player.x = 12; player.y = 32; player.invincible = false; player.invincibleStart = 0; for (int i = 0; i < MAX_BULLETS; i++) { bullets[i].active = false; } for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { enemyBullets[i].active = false; } for (int i = 0; i < MAX_ENEMIES; i++) { enemies[i].active = false; } for (int i = 0; i < MAX_EXPLOSIONS; i++) { explosions[i].active = false; } for (int i = 0; i < MAX_STARS; i++) { stars[i].x = random(0, SCREEN_W); stars[i].y = random(PLAY_TOP + 2, PLAY_BOTTOM); stars[i].speed = random(1, 3); } boss.active = false; boss.hp = 0; lastEnemySpawn = millis(); gameState = STATE_PLAYING; } // Player Update void updatePlayer() { if (millis() - lastPlayerMove < PLAYER_INTERVAL) { return; } lastPlayerMove = millis(); if (touchHeld(T_LEFT)) { player.x -= 2; } if (touchHeld(T_RIGHT)) { player.x += 2; } if (touchHeld(T_UP)) { player.y -= 2; } if (touchHeld(T_DOWN)) { player.y += 2; } player.x = constrain(player.x, 0, SCREEN_W - PLAYER_W - 8); player.y = constrain(player.y, PLAY_TOP + 1, PLAY_BOTTOM - PLAYER_H); if (player.invincible && millis() - player.invincibleStart > 1200) { player.invincible = false; } } // Player Fire void fireBullet() { for (int i = 0; i < MAX_BULLETS; i++) { if (!bullets[i].active) { bullets[i].active = true; bullets[i].x = player.x + PLAYER_W - 1; bullets[i].y = player.y + PLAYER_H / 2; return; } } } // Bullets Update void updateBullets() { if (millis() - lastBulletUpdate < BULLET_INTERVAL) { return; } lastBulletUpdate = millis(); for (int i = 0; i < MAX_BULLETS; i++) { if (!bullets[i].active) continue; bullets[i].x += 4; if (bullets[i].x > 127) { bullets[i].active = false; } } } // Explosions void createExplosion(int x, int y) { for (int i = 0; i < MAX_EXPLOSIONS; i++) { if (!explosions[i].active) { explosions[i].active = true; explosions[i].x = x; explosions[i].y = y; explosions[i].frame = 0; explosions[i].lastFrame = millis(); return; } } } void updateExplosions() { for (int i = 0; i < MAX_EXPLOSIONS; i++) { if (!explosions[i].active) continue; if (millis() - explosions[i].lastFrame > 70) { explosions[i].lastFrame = millis(); explosions[i].frame++; if (explosions[i].frame >= 4) { explosions[i].active = false; } } } } // Enemy Bullets void createEnemyBullet(int x, int y, int vx, int vy) { for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { if (!enemyBullets[i].active) { enemyBullets[i].active = true; enemyBullets[i].x = x; enemyBullets[i].y = y; enemyBullets[i].vx = vx; enemyBullets[i].vy = vy; return; } } } void updateEnemyBullets() { if (millis() - lastEnemyBulletUpdate < ENEMY_BULLET_INTERVAL) { return; } lastEnemyBulletUpdate = millis(); for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { if (!enemyBullets[i].active) continue; enemyBullets[i].x += enemyBullets[i].vx; enemyBullets[i].y += enemyBullets[i].vy; if (enemyBullets[i].x < 0 || enemyBullets[i].x >= 128 || enemyBullets[i].y < PLAY_TOP || enemyBullets[i].y >= 64) { enemyBullets[i].active = false; } } } // Normal Enemy Spawn void spawnEnemy() { for (int i = 0; i < MAX_ENEMIES; i++) { if (!enemies[i].active) { enemies[i].active = true; enemies[i].x = 127; enemies[i].y = random(16, 55); enemies[i].baseY = enemies[i].y; enemies[i].hp = 1; enemies[i].speed = random(1, 3); enemies[i].movement = random(0, 2); enemies[i].phase = 0; enemies[i].shoots = random(0, 100) < 40; enemies[i].lastShot = millis(); return; } } } // Normal Enemies Update void updateEnemies() { if (gameState != STATE_PLAYING) return; if (millis() - lastEnemySpawn > 900) { spawnEnemy(); lastEnemySpawn = millis(); } if (millis() - lastEnemyUpdate < ENEMY_INTERVAL) { return; } lastEnemyUpdate = millis(); for (int i = 0; i < MAX_ENEMIES; i++) { if (!enemies[i].active) continue; enemies[i].x -= enemies[i].speed; if (enemies[i].movement == 1) { enemies[i].phase += 0.25; enemies[i].y = enemies[i].baseY + (int)(sin(enemies[i].phase) * 7); enemies[i].y = constrain(enemies[i].y, PLAY_TOP + 2, PLAY_BOTTOM - ENEMY_H); } if (enemies[i].shoots && millis() - enemies[i].lastShot > 1500) { createEnemyBullet(enemies[i].x, enemies[i].y + 3, -2, 0); enemies[i].lastShot = millis(); } if (enemies[i].x < -ENEMY_W - 2) { enemies[i].active = false; } } } // Boss void startBoss() { gameState = STATE_BOSS; for (int i = 0; i < MAX_ENEMIES; i++) { enemies[i].active = false; } for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { enemyBullets[i].active = false; } boss.active = true; boss.x = 128; boss.y = 25; boss.maxHP = 25; boss.hp = boss.maxHP; boss.verticalDirection = 1; boss.charging = false; boss.returning = false; boss.lastShot = millis(); boss.lastCharge = millis(); } void updateBoss() { if (gameState != STATE_BOSS || !boss.active) { return; } // enter if (boss.x > 100 && !boss.charging && !boss.returning) { boss.x--; return; } // start charge if (!boss.charging && !boss.returning && millis() - boss.lastCharge > 6000) { boss.charging = true; boss.lastCharge = millis(); } // charge forward if (boss.charging) { boss.x -= 3; if (boss.x <= 55) { boss.charging = false; boss.returning = true; } return; } // return if (boss.returning) { boss.x += 3; if (boss.x >= 100) { boss.x = 100; boss.returning = false; } return; } // vertical move boss.y += boss.verticalDirection; if (boss.y <= 17) { boss.y = 17; boss.verticalDirection = 1; } if (boss.y >= 42) { boss.y = 42; boss.verticalDirection = -1; } // shoot if (millis() - boss.lastShot > 1100) { int dy = 0; if (player.y + PLAYER_H / 2 < boss.y + BOSS_H / 2) dy = -1; if (player.y + PLAYER_H / 2 > boss.y + BOSS_H / 2) dy = 1; createEnemyBullet(boss.x, boss.y + BOSS_H / 2, -3, dy); boss.lastShot = millis(); } // dead if (boss.hp <= 0) { boss.hp = 0; boss.active = false; createExplosion(boss.x + BOSS_W / 2, boss.y + BOSS_H / 2); score += 1000; gameState = STATE_LEVEL_CLEAR; } } // Player Damage void damagePlayer() { if (player.invincible) return; lives--; createExplosion(player.x + PLAYER_W / 2, player.y + PLAYER_H / 2); player.invincible = true; player.invincibleStart = millis(); player.x = 12; player.y = 32; for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { enemyBullets[i].active = false; } if (lives <= 0) { lives = 0; gameState = STATE_GAME_OVER; } } // Collision bool rectHit( int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh ) { return ( ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by ); } void checkCollisions() { // bullet vs normal enemy for (int b = 0; b < MAX_BULLETS; b++) { if (!bullets[b].active) continue; for (int e = 0; e < MAX_ENEMIES; e++) { if (!enemies[e].active) continue; if (rectHit( bullets[b].x, bullets[b].y, 4, 1, enemies[e].x, enemies[e].y, ENEMY_W, ENEMY_H )) { bullets[b].active = false; enemies[e].hp--; if (enemies[e].hp <= 0) { createExplosion(enemies[e].x + ENEMY_W / 2, enemies[e].y + ENEMY_H / 2); enemies[e].active = false; score += 100; } break; } } } // bullet vs boss if (boss.active) { for (int b = 0; b < MAX_BULLETS; b++) { if (!bullets[b].active) continue; if (rectHit( bullets[b].x, bullets[b].y, 4, 1, boss.x, boss.y, BOSS_W, BOSS_H )) { bullets[b].active = false; boss.hp--; createExplosion(bullets[b].x, bullets[b].y); } } } // enemy bullet vs player for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { if (!enemyBullets[i].active) continue; if (rectHit( enemyBullets[i].x, enemyBullets[i].y, 2, 2, player.x + 1, player.y + 1, PLAYER_W - 2, PLAYER_H - 2 )) { enemyBullets[i].active = false; damagePlayer(); return; } } // enemy body vs player for (int i = 0; i < MAX_ENEMIES; i++) { if (!enemies[i].active) continue; if (rectHit( player.x + 1, player.y + 1, PLAYER_W - 2, PLAYER_H - 2, enemies[i].x, enemies[i].y, ENEMY_W, ENEMY_H )) { enemies[i].active = false; damagePlayer(); return; } } // boss body vs player if (boss.active) { if (rectHit( player.x + 1, player.y + 1, PLAYER_W - 2, PLAYER_H - 2, boss.x, boss.y, BOSS_W, BOSS_H )) { damagePlayer(); return; } } } // Starfield void updateStars() { if (millis() - lastStarUpdate < STAR_INTERVAL) { return; } lastStarUpdate = millis(); for (int i = 0; i < MAX_STARS; i++) { stars[i].x -= stars[i].speed; if (stars[i].x < 0) { stars[i].x = 127; stars[i].y = random(PLAY_TOP + 2, PLAY_BOTTOM); stars[i].speed = random(1, 3); } } } // Draw Background void drawBackground() { for (int i = 0; i < MAX_STARS; i++) { u8g2.drawPixel(stars[i].x, stars[i].y); } static int terrainOffset = 0; terrainOffset--; if (terrainOffset <= -16) { terrainOffset = 0; } for (int x = terrainOffset; x < 128; x += 16) { u8g2.drawLine(x, 63, x + 4, 59); u8g2.drawLine(x + 4, 59, x + 8, 61); u8g2.drawLine(x + 8, 61, x + 12, 57); u8g2.drawLine(x + 12, 57, x + 16, 63); } } // Draw Player void drawPlayerShip(int xPos, int yPos) { for (int y = 0; y < PLAYER_H; y++) { for (int x = 0; x < PLAYER_W; x++) { if (playerShip[y][x] == '#') { u8g2.drawPixel(xPos + x, yPos + y); } } } } void drawPlayer() { if (player.invincible) { if ((millis() / 100) % 2 == 0) { return; } } drawPlayerShip(player.x, player.y); // engine blink if ((millis() / 80) % 2 == 0) { u8g2.drawPixel(player.x - 1, player.y + PLAYER_H / 2); } } // Draw Bullets void drawBullets() { for (int i = 0; i < MAX_BULLETS; i++) { if (!bullets[i].active) continue; u8g2.drawLine( bullets[i].x, bullets[i].y, bullets[i].x + 3, bullets[i].y ); } } // Draw Normal Enemies void drawEnemies() { for (int i = 0; i < MAX_ENEMIES; i++) { if (!enemies[i].active) continue; u8g2.drawXBMP( enemies[i].x, enemies[i].y, ENEMY_W, ENEMY_H, enemySprite ); } } // Draw Boss void drawBossMonster(int xPos, int yPos) { for (int y = 0; y < BOSS_H; y++) { for (int x = 0; x < BOSS_W; x++) { if (bossMonster[y][x] == '#') { u8g2.drawPixel(xPos + x, yPos + y); } } } } void drawBoss() { if (!boss.active) return; drawBossMonster(boss.x, boss.y); } // Draw Enemy Bullets void drawEnemyBullets() { for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { if (!enemyBullets[i].active) continue; u8g2.drawBox( enemyBullets[i].x, enemyBullets[i].y, 2, 2 ); } } // Draw Explosions void drawExplosions() { for (int i = 0; i < MAX_EXPLOSIONS; i++) { if (!explosions[i].active) continue; int r = explosions[i].frame + 1; u8g2.drawCircle(explosions[i].x, explosions[i].y, r); u8g2.drawPixel(explosions[i].x - r - 1, explosions[i].y); u8g2.drawPixel(explosions[i].x + r + 1, explosions[i].y); u8g2.drawPixel(explosions[i].x, explosions[i].y - r - 1); u8g2.drawPixel(explosions[i].x, explosions[i].y + r + 1); } } // HUD void drawHUD() { char text[20]; u8g2.setFont(u8g2_font_5x8_tf); snprintf(text, sizeof(text), "L%d", lives); u8g2.drawStr(1, 8, text); snprintf(text, sizeof(text), "%05lu", score); u8g2.drawStr(98, 8, text); u8g2.drawLine(0, 10, 127, 10); } // Boss Health void drawBossHealth() { if (!boss.active) return; int hpWidth = map(boss.hp, 0, boss.maxHP, 0, 48); hpWidth = constrain(hpWidth, 0, 48); u8g2.drawFrame(39, 12, 50, 5); if (hpWidth > 0) { u8g2.drawBox(40, 13, hpWidth, 3); } } // Pause void drawPause() { u8g2.setDrawColor(0); u8g2.drawBox(34, 23, 60, 18); u8g2.setDrawColor(1); u8g2.drawFrame(34, 23, 60, 18); u8g2.setFont(u8g2_font_6x12_tf); u8g2.drawStr(46, 36, "PAUSE"); } // Game Over void drawGameOver() { u8g2.setFont(u8g2_font_6x12_tf); u8g2.drawStr(34, 29, "GAME OVER"); u8g2.setFont(u8g2_font_5x8_tf); u8g2.drawStr(28, 43, "Q1 RESTART"); } // Level Clear void drawLevelClear() { u8g2.setFont(u8g2_font_6x12_tf); u8g2.drawStr(27, 29, "LEVEL CLEAR"); u8g2.setFont(u8g2_font_5x8_tf); u8g2.drawStr(28, 43, "Q1 RESTART"); } // Render Helpers void renderPlayfield() { drawBackground(); drawPlayer(); drawBullets(); drawEnemies(); drawEnemyBullets(); drawBoss(); drawExplosions(); drawHUD(); if (boss.active) { drawBossHealth(); } } void renderGame() { u8g2.clearBuffer(); if (gameState == STATE_TITLE) { drawLogo(); } else if (gameState == STATE_PLAYING || gameState == STATE_BOSS) { renderPlayfield(); } else if (gameState == STATE_PAUSED) { renderPlayfield(); drawPause(); } else if (gameState == STATE_GAME_OVER) { drawExplosions(); drawGameOver(); } else if (gameState == STATE_LEVEL_CLEAR) { drawExplosions(); drawLevelClear(); } u8g2.sendBuffer(); } // Setup void setup() { Serial.begin(115200); Wire.begin(); u8g2.begin(); randomSeed(micros()); gameState = STATE_TITLE; renderGame(); } // Loop void loop() { updateTouch(); // TITLE if (gameState == STATE_TITLE) { if (touchPressed(T_Q1)) { resetGame(); } renderGame(); delay(10); return; } // PAUSED if (gameState == STATE_PAUSED) { if (touchPressed(T_Q2)) { gameState = stateBeforePause; } renderGame(); delay(10); return; } // GAME OVER / LEVEL CLEAR if (gameState == STATE_GAME_OVER || gameState == STATE_LEVEL_CLEAR) { updateExplosions(); if (touchPressed(T_Q1)) { gameState = STATE_TITLE; // optional: clear active objects for (int i = 0; i < MAX_BULLETS; i++) { bullets[i].active = false; } for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { enemyBullets[i].active = false; } for (int i = 0; i < MAX_ENEMIES; i++) { enemies[i].active = false; } for (int i = 0; i < MAX_EXPLOSIONS; i++) { explosions[i].active = false; } boss.active = false; } renderGame(); delay(10); return; } // Q2 pause if (touchPressed(T_Q2)) { stateBeforePause = gameState; gameState = STATE_PAUSED; renderGame(); delay(10); return; } // Q1 fire if (touchPressed(T_Q1)) { fireBullet(); } // updates updatePlayer(); updateBullets(); updateEnemyBullets(); updateStars(); updateExplosions(); // normal level if (gameState == STATE_PLAYING) { updateEnemies(); if (score >= BOSS_SCORE) { startBoss(); } } // boss if (gameState == STATE_BOSS) { updateBoss(); } // collisions checkCollisions(); // render renderGame(); delay(10); }