Загрузка данных


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// --- ПИНЫ ---
#define JOY_X A0
#define JUMP_BTN 2
#define BUZZER 8

// ============================================================
//   МИР (карта уровня)
// ============================================================
const int WORLD_WIDTH = 512;

// --- ИГРОК ---
struct Player {
  float x, y, vx, vy;
  int w, h;
  bool onGround;
  int hp;
  bool hasSword;
  bool facingRight;
  int attackTimer;
  bool attacking;
};
Player player;

// --- ПЛАТФОРМЫ ---
struct Platform { int x, y, w; int speed; };
const int NUM_PLATFORMS = 20;
Platform platforms[NUM_PLATFORMS];

// --- ШИПЫ ---
struct Spike { int x, y; bool active; };
const int NUM_SPIKES = 12;
Spike spikes[NUM_SPIKES];

// --- МОНЕТЫ ---
struct Coin { int x, y; bool collected; };
const int NUM_COINS = 12;
Coin coins[NUM_COINS];

// --- МЕЧ ---
struct Sword { int x, y; bool taken; };
Sword sword;

// --- ВРАГИ ---
struct Enemy { float x, y, vx; int hp; bool alive; int timer; };
const int NUM_ENEMIES = 4;
Enemy enemies[NUM_ENEMIES];

// --- БОСС ---
struct Boss {
  float x, y;
  int w, h;
  int hp, maxHp;
  bool alive;
  int moveDir;
  unsigned long lastMove;
};
Boss boss;

// --- ФИЗИКА ---
const float GRAVITY = 0.4;
const float JUMP_FORCE = -6.0;
const float MOVE_SPEED = 2.0;
const float FRICTION = 0.85;

// --- КАМЕРА ---
float cameraX = 0;

// --- СОСТОЯНИЯ ---
enum GameState { STATE_PLAYING, STATE_DEAD, STATE_WIN };
GameState currentState = STATE_PLAYING;

// --- ТАЙМЕРЫ ---
unsigned long lastUpdate = 0;
int lastJumpState = HIGH;

// --- КНОПКА АТАКИ ---
#define ATTACK_BTN A1
int lastAttackState = HIGH;

void setup() {
  Serial.begin(9600);
  pinMode(JUMP_BTN, INPUT_PULLUP);
  pinMode(ATTACK_BTN, INPUT_PULLUP);
  pinMode(BUZZER, OUTPUT);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
      for (;;);
    }
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("OPEN WORLD"));
  display.println(F("Find the sword"));
  display.println(F("Defeat the boss!"));
  display.display();
  delay(2000);

  loadWorld();
}

void loop() {
  if (millis() - lastUpdate < 16) return;
  lastUpdate = millis();

  switch (currentState) {
    case STATE_PLAYING:
      handleInput();
      updatePlatforms();
      updateEnemies();
      updateBoss();
      updatePhysics();
      checkCollisions();
      checkSpikes();
      checkCoins();
      checkSwordPickup();
      checkEnemyCollisions();
      checkBossCombat();
      updateCamera();
      checkWinLose();
      render();
      break;
    case STATE_DEAD:
      display.clearDisplay();
      display.setCursor(25, 25);
      display.setTextSize(2);
      display.print(F("YOU DIED"));
      display.display();
      delay(2000);
      loadWorld();
      break;
    case STATE_WIN:
      display.clearDisplay();
      display.setCursor(20, 25);
      display.setTextSize(2);
      display.print(F("BOSS DOWN"));
      display.display();
      delay(2000);
      loadWorld();
      break;
  }
}

// ============================================================
//   СОЗДАНИЕ МИРА
// ============================================================
void loadWorld() {
  currentState = STATE_PLAYING;
  player.x = 10;
  player.y = 40;
  player.vx = 0;
  player.vy = 0;
  player.w = 6;
  player.h = 8;
  player.onGround = false;
  player.hp = 5;
  player.hasSword = false;
  player.facingRight = true;
  player.attacking = false;
  player.attackTimer = 0;
  cameraX = 0;

  for (int i = 0; i < NUM_PLATFORMS; i++) platforms[i] = {0, 0, 0, 0};
  for (int i = 0; i < NUM_SPIKES; i++) spikes[i] = {0, 0, false};
  for (int i = 0; i < NUM_COINS; i++) coins[i] = {0, 0, false};
  for (int i = 0; i < NUM_ENEMIES; i++) enemies[i] = {0, 0, 0, 0, false, 0};

  // --- ПЛАТФОРМЫ (открытый мир 512 пикселей) ---
  platforms[0] = {0, 56, 60, 0};
  platforms[1] = {80, 48, 40, 0};
  platforms[2] = {140, 44, 40, 0};
  platforms[3] = {200, 40, 30, 0};
  platforms[4] = {240, 52, 40, 0};
  platforms[5] = {300, 44, 30, 1}; // движется
  platforms[6] = {350, 36, 40, 0};
  platforms[7] = {410, 48, 60, 0}; // арена босса
  platforms[8] = {480, 40, 32, 0};

  // --- ШИПЫ ---
  spikes[0] = {62, 52, true};
  spikes[1] = {122, 52, true};
  spikes[2] = {182, 52, true};
  spikes[3] = {282, 52, true};
  spikes[4] = {335, 52, true};
  spikes[5] = {395, 44, true};

  // --- МОНЕТЫ ---
  coins[0] = {85, 44, true};
  coins[1] = {145, 40, true};
  coins[2] = {205, 36, true};
  coins[3] = {245, 48, true};
  coins[4] = {305, 40, true};
  coins[5] = {355, 32, true};
  coins[6] = {415, 44, true};
  coins[7] = {485, 36, true};

  // --- МЕЧ ---
  sword = {260, 44, false}; // Меч в середине уровня

  // --- ВРАГИ ---
  enemies[0] = {100, 40, 0.5, 1, true, 0};
  enemies[1] = {220, 32, -0.5, 1, true, 0};
  enemies[2] = {360, 28, 0.5, 2, true, 0};

  // --- БОСС ---
  boss = {440, 32, 16, 16, 10, 10, true, 1, 0};

  Serial.println(F("WORLD LOADED"));
}

// ============================================================
//   УПРАВЛЕНИЕ
// ============================================================
void handleInput() {
  // --- ДВИЖЕНИЕ ---
  int x = analogRead(JOY_X);
  if (x < 300) { player.vx = -MOVE_SPEED; player.facingRight = false; }
  else if (x > 700) { player.vx = MOVE_SPEED; player.facingRight = true; }
  else player.vx *= FRICTION;

  // --- ПРЫЖОК ---
  int jumpReading = digitalRead(JUMP_BTN);
  if (jumpReading == LOW && lastJumpState == HIGH && player.onGround) {
    player.vy = JUMP_FORCE;
    player.onGround = false;
    tone(BUZZER, 800, 30);
  }
  lastJumpState = jumpReading;

  // --- АТАКА (только если есть меч) ---
  int attackReading = digitalRead(ATTACK_BTN);
  if (attackReading == LOW && lastAttackState == HIGH && player.hasSword) {
    player.attacking = true;
    player.attackTimer = 15; // 15 кадров атаки
    tone(BUZZER, 1000, 50);
  }
  lastAttackState = attackReading;

  if (player.attacking) {
    player.attackTimer--;
    if (player.attackTimer <= 0) player.attacking = false;
  }
}

// ============================================================
//   ФИЗИКА
// ============================================================
void updatePhysics() {
  player.vy += GRAVITY;
  player.x += player.vx;
  player.y += player.vy;

  if (player.x < 0) player.x = 0;
  if (player.x + player.w > WORLD_WIDTH) player.x = WORLD_WIDTH - player.w;

  if (player.y > SCREEN_HEIGHT) {
    player.hp = 0;
    currentState = STATE_DEAD;
    tone(BUZZER, 200, 500);
  }
}

void updatePlatforms() {
  for (int i = 0; i < NUM_PLATFORMS; i++) {
    if (platforms[i].speed != 0) {
      platforms[i].x += platforms[i].speed;
      if (platforms[i].x < 280 || platforms[i].x + platforms[i].w > 340) {
        platforms[i].speed = -platforms[i].speed;
      }
    }
  }
}

void updateEnemies() {
  for (int i = 0; i < NUM_ENEMIES; i++) {
    if (!enemies[i].alive) continue;
    enemies[i].x += enemies[i].vx;
    if (enemies[i].x < 0 || enemies[i].x > WORLD_WIDTH) enemies[i].vx = -enemies[i].vx;
    // Простая проверка «от края платформы»
    if (enemies[i].x > 95 && enemies[i].x < 105 && enemies[i].vx > 0) enemies[i].vx = -enemies[i].vx;
    if (enemies[i].x < 95 && enemies[i].vx < 0) enemies[i].vx = -enemies[i].vx;
  }
}

void updateBoss() {
  if (!boss.alive) return;
  if (millis() - boss.lastMove > 40) {
    boss.lastMove = millis();
    boss.x += boss.moveDir * 1.5;
    if (boss.x < 410 || boss.x > 470) boss.moveDir = -boss.moveDir;
  }
}

void updateCamera() {
  cameraX = player.x - SCREEN_WIDTH / 2;
  if (cameraX < 0) cameraX = 0;
  if (cameraX > WORLD_WIDTH - SCREEN_WIDTH) cameraX = WORLD_WIDTH - SCREEN_WIDTH;
}

// ============================================================
//   КОЛЛИЗИИ
// ============================================================
void checkCollisions() {
  player.onGround = false;
  for (int i = 0; i < NUM_PLATFORMS; i++) {
    if (platforms[i].w == 0) continue;
    if (player.x + player.w > platforms[i].x &&
        player.x < platforms[i].x + platforms[i].w) {
      int top = platforms[i].y;
      if (player.vy >= 0 && player.y + player.h >= top && player.y + player.h <= top + 10) {
        player.y = top - player.h;
        player.vy = 0;
        player.onGround = true;
      }
    }
  }
}

void checkSpikes() {
  for (int i = 0; i < NUM_SPIKES; i++) {
    if (!spikes[i].active) continue;
    if (player.x + player.w > spikes[i].x &&
        player.x < spikes[i].x + 5 &&
        player.y + player.h > spikes[i].y &&
        player.y < spikes[i].y + 4) {
      player.hp--;
      if (player.hp <= 0) {
        currentState = STATE_DEAD;
        tone(BUZZER, 200, 500);
      } else {
        // Отбрасываем игрока
        player.vy = -4;
        player.vx = 0;
        tone(BUZZER, 300, 100);
        spikes[i].active = false; // шип ломается
      }
    }
  }
}

void checkCoins() {
  for (int i = 0; i < NUM_COINS; i++) {
    if (!coins[i].collected) continue;
    if (player.x + player.w > coins[i].x &&
        player.x < coins[i].x + 4 &&
        player.y + player.h > coins[i].y &&
        player.y < coins[i].y + 4) {
      coins[i].collected = false;
      tone(BUZZER, 1200, 30);
    }
  }
}

void checkSwordPickup() {
  if (sword.taken) return;
  if (player.x + player.w > sword.x &&
      player.x < sword.x + 6 &&
      player.y + player.h > sword.y &&
      player.y < sword.y + 6) {
    sword.taken = true;
    player.hasSword = true;
    tone(BUZZER, 1500, 100);
    delay(100);
    tone(BUZZER, 2000, 150);
    Serial.println(F("SWORD TAKEN"));
  }
}

void checkEnemyCollisions() {
  for (int i = 0; i < NUM_ENEMIES; i++) {
    if (!enemies[i].alive) continue;

    // Игрок коснулся врага
    if (player.x + player.w > enemies[i].x &&
        player.x < enemies[i].x + 8 &&
        player.y + player.h > enemies[i].y &&
        player.y < enemies[i].y + 8) {
      
      // Если игрок атакует — враг получает урон
      if (player.attacking) {
        enemies[i].hp--;
        if (enemies[i].hp <= 0) {
          enemies[i].alive = false;
          tone(BUZZER, 400, 100);
        }
      } else {
        // Иначе игрок получает урон
        player.hp--;
        player.vx = -player.vx * 2;
        player.vy = -3;
        if (player.hp <= 0) {
          currentState = STATE_DEAD;
          tone(BUZZER, 200, 500);
        }
      }
    }
  }
}

void checkBossCombat() {
  if (!boss.alive) return;
  if (player.x + player.w > boss.x &&
      player.x < boss.x + boss.w &&
      player.y + player.h > boss.y &&
      player.y < boss.y + boss.h) {
    
    if (player.attacking) {
      boss.hp--;
      tone(BUZZER, 500, 100);
      if (boss.hp <= 0) {
        boss.alive = false;
        currentState = STATE_WIN;
        tone(BUZZER, 1500, 300);
      }
    } else {
      player.hp--;
      player.vx = -player.vx * 2;
      player.vy = -4;
      if (player.hp <= 0) {
        currentState = STATE_DEAD;
        tone(BUZZER, 200, 500);
      }
    }
  }
}

void checkWinLose() {
  // Условия победы/поражения проверяются выше
}

// ============================================================
//   ОТРИСОВКА (с учётом камеры)
// ============================================================
void render() {
  display.clearDisplay();

  // Платформы
  for (int i = 0; i < NUM_PLATFORMS; i++) {
    if (platforms[i].w == 0) continue;
    int sx = platforms[i].x - cameraX;
    if (sx + platforms[i].w < 0 || sx > SCREEN_WIDTH) continue;
    display.fillRect(sx, platforms[i].y, platforms[i].w, 2, SSD1306_WHITE);
  }

  // Шипы
  for (int i = 0; i < NUM_SPIKES; i++) {
    if (!spikes[i].active) continue;
    int sx = spikes[i].x - cameraX;
    if (sx < -10 || sx > SCREEN_WIDTH) continue;
    display.fillTriangle(sx, spikes[i].y + 4,
                         sx + 5, spikes[i].y + 4,
                         sx + 2, spikes[i].y, SSD1306_WHITE);
  }

  // Монеты
  for (int i = 0; i < NUM_COINS; i++) {
    if (!coins[i].collected) continue;
    int sx = coins[i].x - cameraX;
    if (sx < -10 || sx > SCREEN_WIDTH) continue;
    display.drawCircle(sx + 2, coins[i].y + 2, 2, SSD1306_WHITE);
  }

  // Меч
  if (!sword.taken) {
    int sx = sword.x - cameraX;
    display.drawLine(sx, sword.y, sx + 4, sword.y + 4, SSD1306_WHITE);
    display.drawLine(sx, sword.y + 4, sx + 4, sword.y, SSD1306_WHITE);
  }

  // Враги
  for (int i = 0; i < NUM_ENEMIES; i++) {
    if (!enemies[i].alive) continue;
    int sx = enemies[i].x - cameraX;
    if (sx < -10 || sx > SCREEN_WIDTH) continue;
    display.fillRect(sx, enemies[i].y, 8, 8, SSD1306_WHITE);
    display.fillRect(sx + 2, enemies[i].y + 2, 2, 2, SSD1306_BLACK);
    display.fillRect(sx + 4, enemies[i].y + 2, 2, 2, SSD1306_BLACK);
  }

  // Босс
  if (boss.alive) {
    int sx = boss.x - cameraX;
    if (sx > -20 && sx < SCREEN_WIDTH) {
      display.fillRect(sx, boss.y, boss.w, boss.h, SSD1306_WHITE);
      display.fillRect(sx + 3, boss.y + 3, 3, 3, SSD1306_BLACK);
      display.fillRect(sx + 10, boss.y + 3, 3, 3, SSD1306_BLACK);
      // Полоска HP босса
      display.fillRect(sx, boss.y - 4, boss.w, 2, SSD1306_WHITE);
      display.fillRect(sx, boss.y - 4, boss.w * boss.hp / boss.maxHp, 2, SSD1306_BLACK);
    }
  }

  // Игрок
  int sx = player.x - cameraX;
  display.fillRect(sx, player.y, player.w, player.h, SSD1306_WHITE);
  
  // Меч в руках игрока (если атакует)
  if (player.attacking && player.hasSword) {
    if (player.facingRight) {
      display.drawLine(sx + player.w, player.y + 2, sx + player.w + 6, player.y + 2, SSD1306_WHITE);
    } else {
      display.drawLine(sx - 6, player.y + 2, sx, player.y + 2, SSD1306_WHITE);
    }
  }

  // --- HUD ---
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print(F("HP:"));
  for (int i = 0; i < player.hp; i++) display.print(F("#"));
  if (player.hasSword) display.print(F(" SWD"));
  display.setCursor(SCREEN_WIDTH - 20, 0);
  display.print(F("L1"));

  display.display();
}