¡Claro que sí\! Aquí tienes el código completo y final para la versión **profesional** del juego **Invasor Espacial**.
Este código incluye todas las mejoras que discutimos: la corrección del bug de nivel, la dificultad progresiva con enemigos de élite, el potenciador de **Disparo Triple**, y los efectos visuales mejorados, todo dentro de una estructura de Clases (OOP) limpia y organizada.
-----
### Código Profesional Completo: Invasor Espacial v2.0
```cpp
// =========================================================================
// == JUEGO INVASOR ESPACIAL (VERSIÓN PROFESIONAL) ==
// =========================================================================
// Autor: Josue Cardenas Aguirre (Adaptado por Gemini)
// Mejoras:
// - Arquitectura Orientada a Objetos (OOP).
// - Solucionado el bug de transición de nivel.
// - Niveles con dificultad progresiva y enemigos de élite.
// - Potenciador de Disparo Triple.
// - Efectos visuales mejorados y controles invertidos.
// =========================================================================
// --- LIBRERÍAS ---
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_SSD1306.h>
#include <vector>
// --- PINES ---
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define JOY_HORIZ 35
#define JOY_VERT 32
#define JOY_SEL 25
// --- CONFIGURACIÓN OLED ---
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
// --- CONFIGURACIÓN DEL JUEGO ---
#define SCREEN_W 320
#define SCREEN_H 240
#define PLAYER_WIDTH 16
#define PLAYER_HEIGHT 10
#define PLAYER_SPEED 4
#define PLAYER_LIVES 3
#define MAX_BULLETS 15 // Aumentado para el disparo triple
#define BULLET_SPEED 6
#define FIRE_COOLDOWN 300
#define POWERUP_DURATION 7000 // 7 segundos
// --- PALETA DE COLORES ---
#define COLOR_BACKGROUND 0x00A5 // Azul oscuro
#define COLOR_PLAYER ILI9341_CYAN
#define COLOR_BULLET ILI9341_YELLOW
#define COLOR_ENEMY ILI9341_MAGENTA
#define COLOR_ENEMY_ELITE ILI9341_RED
#define COLOR_POWERUP ILI9341_GREEN
// --- Estructuras y Clases ---
struct Point { int x, y; };
class Bullet {
public:
Point pos;
Point vel; // Velocidad con componentes x, y
bool active = true;
Bullet(int x, int y, int vx = 0, int vy = -BULLET_SPEED) : pos{x, y}, vel{vx, vy} {}
void update(Adafruit_ILI9341 &tft) {
tft.fillRect(pos.x, pos.y, 3, 6, COLOR_BACKGROUND); // Borrar
pos.x += vel.x;
pos.y += vel.y;
if (pos.y < 0 || pos.y > SCREEN_H || pos.x < 0 || pos.x > SCREEN_W) {
active = false;
} else {
tft.fillRect(pos.x, pos.y, 3, 6, COLOR_BULLET); // Dibujar
}
}
};
class Enemy {
public:
Point pos;
bool active = true;
int health = 1;
bool isElite = false;
Enemy(int x, int y, bool elite = false) : pos{x, y}, isElite(elite) {
if (isElite) health = 3;
}
void draw(Adafruit_ILI9341 &tft) {
if (!active) return;
uint16_t color = isElite ? COLOR_ENEMY_ELITE : COLOR_ENEMY;
tft.fillRect(pos.x, pos.y, 12, 12, color);
if(isElite) tft.drawRect(pos.x, pos.y, 12, 12, ILI9341_WHITE);
}
void undraw(Adafruit_ILI9341 &tft) {
tft.fillRect(pos.x, pos.y, 12, 12, COLOR_BACKGROUND);
}
};
class PowerUp {
public:
Point pos;
bool active = true;
PowerUp(int x, int y) : pos{x, y} {}
void update(Adafruit_ILI9341 &tft) {
undraw(tft);
pos.y += 2; // Cae lentamente
if (pos.y > SCREEN_H) active = false;
else draw(tft);
}
void draw(Adafruit_ILI9341 &tft) {
if (!active) return;
tft.fillCircle(pos.x, pos.y, 6, COLOR_POWERUP);
tft.setCursor(pos.x - 3, pos.y - 4);
tft.setTextColor(ILI9341_BLACK);
tft.print("S");
}
void undraw(Adafruit_ILI9341 &tft) {
tft.fillCircle(pos.x, pos.y, 6, COLOR_BACKGROUND);
}
};
// --- Instancias Globales ---
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// --- Variables de Estado del Juego ---
enum GameState { START_SCREEN, PLAYING, LEVEL_TRANSITION, GAME_OVER };
GameState currentState = START_SCREEN;
int lives, score, highScore = 0, level;
Point player_pos;
int lastPlayerX;
unsigned long lastFireTime = 0;
unsigned long lastEnemyMoveTime = 0;
long enemyMoveInterval;
int enemyDirection = 1;
bool spreadShotActive = false;
unsigned long powerUpEndTime = 0;
unsigned long transitionEndTime = 0;
std::vector<Bullet> bullets;
std::vector<Enemy> enemies;
std::vector<PowerUp> powerups;
// =========================================================================
// == FUNCIONES PRINCIPALES ==
// =========================================================================
void drawPlayer() {
tft.fillTriangle(player_pos.x, player_pos.y + PLAYER_HEIGHT, player_pos.x + PLAYER_WIDTH, player_pos.y + PLAYER_HEIGHT, player_pos.x + (PLAYER_WIDTH / 2), player_pos.y, COLOR_PLAYER);
}
void undrawPlayer() {
tft.fillTriangle(lastPlayerX, player_pos.y + PLAYER_HEIGHT, lastPlayerX + PLAYER_WIDTH, player_pos.y + PLAYER_HEIGHT, lastPlayerX + (PLAYER_WIDTH / 2), player_pos.y, COLOR_BACKGROUND);
}
void fireBullet() {
if (millis() - lastFireTime > FIRE_COOLDOWN) {
lastFireTime = millis();
if (spreadShotActive) {
if (bullets.size() < MAX_BULLETS - 2) {
bullets.emplace_back(player_pos.x + PLAYER_WIDTH / 2 - 1, player_pos.y, -2); // Diagonal izquierda
bullets.emplace_back(player_pos.x + PLAYER_WIDTH / 2 - 1, player_pos.y, 0); // Centro
bullets.emplace_back(player_pos.x + PLAYER_WIDTH / 2 - 1, player_pos.y, 2); // Diagonal derecha
}
} else {
if (bullets.size() < MAX_BULLETS) {
bullets.emplace_back(player_pos.x + PLAYER_WIDTH / 2 - 1, player_pos.y);
}
}
}
}
void createExplosion(int x, int y) {
for (int i = 0; i < 15; i++) {
tft.drawPixel(x + random(-8, 8), y + random(-8, 8), ILI9341_ORANGE);
}
delay(20); // Pequeño delay para el efecto
for (int i = 0; i < 15; i++) {
tft.drawPixel(x + random(-8, 8), y + random(-8, 8), COLOR_BACKGROUND);
}
}
void setupLevel(int lvl) {
enemies.clear();
bullets.clear();
powerups.clear();
spreadShotActive = false;
level = lvl;
enemyMoveInterval = 500 - (level * 40);
if (enemyMoveInterval < 100) enemyMoveInterval = 100;
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 8; c++) {
bool isElite = (level >= 2 && r == 0 && (c == 2 || c == 5));
enemies.emplace_back(c * 20 + 20, r * 20 + 20, isElite);
}
}
tft.fillScreen(COLOR_BACKGROUND);
}
void initGame() {
lives = PLAYER_LIVES;
score = 0;
player_pos = {(SCREEN_W - PLAYER_WIDTH) / 2, SCREEN_H - PLAYER_HEIGHT - 10};
lastPlayerX = player_pos.x;
setupLevel(1);
}
void updateOLED() {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0); display.print("Score:"); display.setCursor(80, 0); display.print(score);
display.setCursor(0, 20); display.print("Vidas:"); display.setCursor(80, 20); display.print(lives);
display.setTextSize(1);
display.setCursor(0, 45); display.print("Nivel: "); display.print(level);
if (spreadShotActive) {
display.setCursor(64, 45);
display.print("TRIPLE!");
}
display.display();
}
void drawStartScreen() {
tft.fillScreen(COLOR_BACKGROUND);
tft.setTextSize(4);
tft.setCursor(70, 70);
tft.setTextColor(ILI9341_CYAN);
tft.print("INVASOR");
tft.setCursor(70, 110);
tft.print("ESPACIAL");
tft.setTextSize(2);
tft.setCursor(40, 180);
tft.print("Presiona para Jugar");
}
void drawGameOverScreen() {
if (score > highScore) highScore = score;
tft.fillScreen(ILI9341_RED);
tft.setTextSize(4);
tft.setCursor(40, 80);
tft.print("GAME OVER");
tft.setTextSize(2);
tft.setCursor(80, 140);
tft.print("Record: "); tft.print(highScore);
tft.setCursor(40, 180);
tft.print("Presiona para reiniciar");
}
void handlePlayingState();
void handleLevelTransitionState();
// =========================================================================
// == PROGRAMA PRINCIPAL ==
// =========================================================================
void setup() {
tft.begin();
tft.setRotation(1);
pinMode(JOY_SEL, INPUT_PULLUP);
Wire.begin();
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { for(;;); }
}
void loop() {
switch (currentState) {
case START_SCREEN:
drawStartScreen();
if (digitalRead(JOY_SEL) == LOW) {
initGame();
currentState = PLAYING;
delay(200); // Pequeño debounce
}
break;
case PLAYING:
handlePlayingState();
break;
case LEVEL_TRANSITION:
handleLevelTransitionState();
break;
case GAME_OVER:
if (digitalRead(JOY_SEL) == LOW) {
currentState = START_SCREEN;
delay(200);
}
break;
}
updateOLED();
}
// --- LÓGICA DE ESTADOS (SEPARADA PARA MAYOR CLARIDAD) ---
void handlePlayingState() {
// --- ACTUALIZAR POSICIONES ---
lastPlayerX = player_pos.x;
int horiz = analogRead(JOY_HORIZ);
if (horiz < 500) player_pos.x += PLAYER_SPEED;
else if (horiz > 3500) player_pos.x -= PLAYER_SPEED;
if (player_pos.x < 0) player_pos.x = 0;
if (player_pos.x > SCREEN_W - PLAYER_WIDTH) player_pos.x = SCREEN_W - PLAYER_WIDTH;
if (digitalRead(JOY_SEL) == LOW) fireBullet();
// Actualizar balas y eliminar las inactivas
for (auto it = bullets.begin(); it != bullets.end(); ) {
it->update(tft);
if (!it->active) it = bullets.erase(it);
else ++it;
}
// Actualizar powerups
for (auto it = powerups.begin(); it != powerups.end(); ) {
it->update(tft);
if (!it->active) it = powerups.erase(it);
else ++it;
}
// Actualizar enemigos
if (millis() - lastEnemyMoveTime > enemyMoveInterval) {
lastEnemyMoveTime = millis();
bool wallHit = false;
for (auto &enemy : enemies) {
enemy.undraw(tft);
enemy.pos.x += 5 * enemyDirection;
if (enemy.pos.x <= 0 || enemy.pos.x >= SCREEN_W - 12) wallHit = true;
}
if (wallHit) {
enemyDirection *= -1;
for (auto &enemy : enemies) enemy.pos.y += 12;
}
}
// --- COMPROBAR COLISIONES ---
for (auto it_b = bullets.begin(); it_b != bullets.end(); ) {
bool bullet_hit = false;
for (auto it_e = enemies.begin(); it_e != enemies.end(); ) {
if (it_b->pos.x > it_e->pos.x && it_b->pos.x < it_e->pos.x + 12 &&
it_b->pos.y > it_e->pos.y && it_b->pos.y < it_e->pos.y + 12) {
it_b->active = false;
bullet_hit = true;
it_e->health--;
if (it_e->health <= 0) {
createExplosion(it_e->pos.x + 6, it_e->pos.y + 6);
if (it_e->isElite && random(0, 2) == 0) { // 50% chance de soltar powerup
powerups.emplace_back(it_e->pos.x + 6, it_e->pos.y + 6);
}
it_e = enemies.erase(it_e);
score += it_e->isElite ? 50 : 10;
} else {
++it_e;
}
break;
} else {
++it_e;
}
}
if (bullet_hit) it_b = bullets.erase(it_b);
else ++it_b;
}
// Colisión de powerup con jugador
for (auto it = powerups.begin(); it != powerups.end(); ) {
if (player_pos.x < it->pos.x && player_pos.x + PLAYER_WIDTH > it->pos.x &&
player_pos.y < it->pos.y && player_pos.y + PLAYER_HEIGHT > it->pos.y) {
it->active = false;
spreadShotActive = true;
powerUpEndTime = millis() + POWERUP_DURATION;
}
if (!it->active) it = powerups.erase(it);
else ++it;
}
// --- COMPROBAR ESTADO DE POWERUP ---
if (spreadShotActive && millis() > powerUpEndTime) {
spreadShotActive = false;
}
// --- COMPROBAR CONDICIÓN DE FIN DE JUEGO ---
for (const auto &enemy : enemies) {
if (enemy.pos.y + 12 >= player_pos.y) {
currentState = GAME_OVER;
drawGameOverScreen();
return;
}
}
if (enemies.empty()) {
currentState = LEVEL_TRANSITION;
transitionEndTime = millis() + 2000; // Duración de 2 segundos
tft.fillScreen(ILI9341_GREEN);
tft.setCursor(80,120);
tft.setTextSize(2);
tft.print("NIVEL SUPERADO!");
}
// --- DIBUJAR TODO ---
if (player_pos.x != lastPlayerX) undrawPlayer();
drawPlayer();
for (auto &enemy : enemies) enemy.draw(tft);
}
void handleLevelTransitionState() {
if (millis() > transitionEndTime) {
setupLevel(level + 1);
currentState = PLAYING;
}
}
```