#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
// --- НАСТРОЙКИ СЕТИ ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* scriptID = "AKfycbxKuAZbjhEgFOSKtyUrIJuFyDpsZ5JuEbB4P5WZMRTIVSKzDvzw3UJJHEsefdpvJJKYAA";
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define BUZZER_PIN 25
#define BUTTON_PIN 15
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Preferences preferences;
// --- СОСТОЯНИЕ ИГРЫ ---
String playerName = "";
String deviceID = "";
int score = 0, highScore = 0, lives = 3, heartsCollected = 0;
int dinoY = 39, velocity = 0, gravity = 2;
bool isJumping = false, legToggle = false, starActive = false, isBossMode = false;
unsigned long invincibilityUntil = 0, starEndTime = 0;
int obstacleX = 200, obstacleY = 45, obsType = 0, bonusX = -20, bonusY = 30, bonusType = 0;
int bossTimer = 0, bulletX = -20, bulletY = 45;
// --- БИТМАПЫ ---
const unsigned char dino_f1[] PROGMEM = { 0x00, 0x00, 0x07, 0xf0, 0x07, 0xf8, 0x07, 0xfc, 0x07, 0xfc, 0x07, 0xf0, 0x07, 0xf0, 0x47, 0xe0, 0x47, 0xe0, 0xc7, 0xe0, 0xff, 0xe0, 0xff, 0xe0, 0x7f, 0xe0, 0x3f, 0xe0, 0x1c, 0x20, 0x18, 0x00 };
const unsigned char dino_f2[] PROGMEM = { 0x00, 0x00, 0x07, 0xf0, 0x07, 0xf8, 0x07, 0xfc, 0x07, 0xfc, 0x07, 0xf0, 0x07, 0xf0, 0x47, 0xe0, 0x47, 0xe0, 0xc7, 0xe0, 0xff, 0xe0, 0xff, 0xe0, 0x7f, 0xe0, 0x3f, 0xe0, 0x08, 0x70, 0x00, 0x30 };
const unsigned char cactus_mini[] PROGMEM = { 0x18, 0x18, 0x18, 0x98, 0xdb, 0xff, 0xdb, 0x18, 0x18, 0x18 };
const unsigned char cactus_big[] PROGMEM = { 0x06, 0x00, 0x06, 0x00, 0x06, 0x18, 0x66, 0x18, 0x66, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x06, 0x1f, 0x06, 0x1f, 0x06, 0x18, 0x06, 0x18, 0x06, 0x18 };
const unsigned char ptero_bmp[] PROGMEM = { 0x20, 0x00, 0x70, 0x00, 0xf8, 0x00, 0xff, 0x80, 0xff, 0xe0, 0x1f, 0x00, 0x0e, 0x00, 0x04, 0x00 };
const unsigned char heart_bmp[] PROGMEM = { 0x66, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x18 };
const unsigned char star_bmp[] PROGMEM = { 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18 };
void printCenter(String text, int y, int size = 1) {
display.setTextSize(size);
display.setTextColor(WHITE);
int16_t x1, y1; uint16_t w, h;
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, y);
display.print(text);
}
void sendDataToGoogle(int finalScore) {
if (WiFi.status() == WL_CONNECTED) {
display.clearDisplay();
printCenter("SYNCING DATA...", 20, 1);
display.drawRect(14, 40, 100, 10, WHITE);
display.display();
HTTPClient http;
String url = "https://script.google.com/macros/s/" + String(scriptID) + "/exec";
url += "?nick=" + playerName + "&id=" + deviceID + "&score=" + String(finalScore);
http.begin(url.c_str());
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
for(int i=0; i<80; i+=10) {
display.fillRect(16, 42, i, 6, WHITE);
display.display();
delay(20);
}
int httpCode = http.GET();
display.fillRect(16, 42, 96, 6, WHITE);
if (httpCode > 0) printCenter("DONE!", 55, 1);
else printCenter("ERROR", 55, 1);
display.display();
delay(500);
http.end();
} else {
display.clearDisplay();
printCenter("NO WIFI - SAVED LOCALLY", 30, 1);
display.display();
delay(1000);
}
}
void inputName() {
String chars = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String entry = " ";
int currentPos = 0, charIdx = 0;
bool forceExit = false;
while (currentPos < 6 && !forceExit) {
display.clearDisplay();
printCenter("NEW IDENTITY", 5, 1);
display.setTextSize(2);
for (int i = 0; i < 6; i++) {
display.setCursor(10 + (i * 18), 25);
if (i == currentPos) {
display.print(chars[charIdx]);
} else {
char c = (entry[i] == ' ') ? '_' : entry[i];
display.print(c);
}
}
display.setTextSize(1);
printCenter("3s HOLD: START", 55);
display.display();
while (digitalRead(BUTTON_PIN) == HIGH);
unsigned long startPress = millis();
while (digitalRead(BUTTON_PIN) == LOW);
unsigned long duration = millis() - startPress;
if (duration < 500) {
charIdx = (charIdx + 1) % chars.length();
tone(BUZZER_PIN, 800, 20);
}
else if (duration >= 500 && duration < 2500) {
entry.setCharAt(currentPos, chars[charIdx]);
currentPos++;
charIdx = 0;
tone(BUZZER_PIN, 1200, 50);
}
else if (duration >= 3000) {
if (currentPos < 6) entry.setCharAt(currentPos, chars[charIdx]);
forceExit = true;
tone(BUZZER_PIN, 1500, 100);
}
delay(100);
}
playerName = entry;
playerName.trim();
if (playerName == "") playerName = "DINO";
preferences.putString("name", playerName);
}
void startMenu() {
bool selectContinue = true;
while (true) {
display.clearDisplay();
printCenter("IDENTITY FOUND:", 5);
display.setTextSize(2); printCenter(playerName, 18);
display.setTextSize(1);
display.setCursor(20, 42); display.print(selectContinue ? "> CONTINUE" : " CONTINUE");
display.setCursor(20, 52); display.print(!selectContinue ? "> NEW ID" : " NEW ID");
display.display();
while (digitalRead(BUTTON_PIN) == HIGH);
unsigned long start = millis();
while (digitalRead(BUTTON_PIN) == LOW);
if (millis() - start < 500) { selectContinue = !selectContinue; tone(BUZZER_PIN, 800, 20); }
else { tone(BUZZER_PIN, 1200, 100); if (!selectContinue) inputName(); return; }
delay(100);
}
}
void resetGame() {
if (score > highScore) {
highScore = score;
preferences.putInt("highscore", highScore);
}
sendDataToGoogle(score);
display.clearDisplay();
printCenter("GAME OVER", 10, 2);
printCenter("SCORE: " + String(score), 30, 1);
printCenter("PRESS TO RESTART", 42, 1);
// Добавляем версию и автора в конце
display.setTextSize(1);
printCenter("Andibond ver. 3.2.1", 55);
display.display();
tone(BUZZER_PIN, 100, 500);
delay(500);
while(digitalRead(BUTTON_PIN) == HIGH);
while(digitalRead(BUTTON_PIN) == LOW);
score = 0; lives = 3; heartsCollected = 0; isBossMode = false;
obstacleX = 200; bonusX = -30; invincibilityUntil = millis() + 1000;
}
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
// Приветственный экран с автором и версией
printCenter("Andibond", 15, 2);
printCenter("ver. 3.2.1", 35, 1);
printCenter("WiFi Connecting...", 50, 1);
display.display();
WiFi.begin(ssid, password);
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) { delay(500); retry++; }
String mac = WiFi.macAddress();
mac.replace(":", "");
deviceID = mac.substring(mac.length() - 6);
preferences.begin("dino-game", false);
highScore = preferences.getInt("highscore", 0);
playerName = preferences.getString("name", "");
if (playerName == "") inputName();
else startMenu();
display.clearDisplay();
printCenter("READY?", 25, 2);
display.setTextSize(1);
display.setCursor(0, 55); display.print("ID: " + deviceID);
display.display();
while(digitalRead(BUTTON_PIN) == HIGH);
while(digitalRead(BUTTON_PIN) == LOW);
}
void loop() {
display.setTextSize(1);
if (digitalRead(BUTTON_PIN) == LOW && !isJumping) {
velocity = -12; isJumping = true; tone(BUZZER_PIN, 600, 50);
}
if (isJumping) {
dinoY += velocity; velocity += gravity;
if (dinoY >= 39) { dinoY = 39; isJumping = false; }
} else { if(millis() % 200 < 100) legToggle = !legToggle; }
if (!isBossMode) {
int speed = constrain(7 + (score/15), 7, 14);
obstacleX -= speed;
if (obstacleX < -20) {
score++; obstacleX = 128 + random(20, 80);
obsType = random(0, 2);
if (obsType == 1) obstacleY = random(15, 40); else obstacleY = 45;
if (score % 5 == 0) { bonusX = 128; bonusY = random(20, 40); bonusType = random(0, 3); }
}
if (score > 0 && score % 100 == 0) { isBossMode = true; bossTimer = 400; bulletX = 90; bulletY = 45; }
} else {
bossTimer--; bulletX -= 9;
if (bulletX < -10) { bulletX = 100; bulletY = random(30, 50); }
if (bossTimer <= 0) { isBossMode = false; score += 10; obstacleX = 160; }
}
bonusX -= 7;
if (bonusX > 15 && bonusX < 35 && dinoY < bonusY + 8 && dinoY + 16 > bonusY) {
if (bonusType == 0) { heartsCollected++; if (heartsCollected >= 5) { heartsCollected = 0; if(lives < 3) lives++; } }
else if (bonusType == 1) { starActive = true; starEndTime = millis() + 7000; }
else if (bonusType == 2) { if (lives < 3) lives++; }
bonusX = -30;
}
if (starActive && millis() > starEndTime) starActive = false;
if (lives > 0 && !starActive && millis() > invincibilityUntil) {
bool hit = false;
if (!isBossMode && obstacleX < 30 && obstacleX > 15 && dinoY > obstacleY - 12 && dinoY < obstacleY + 8) hit = true;
if (isBossMode && bulletX < 30 && bulletX > 15 && abs(dinoY - bulletY) < 10) hit = true;
if (hit) { lives--; invincibilityUntil = millis() + 1000; tone(BUZZER_PIN, 150, 400); }
}
if (lives <= 0) resetGame();
display.clearDisplay();
display.drawLine(0, 55, 128, 55, WHITE);
display.setCursor(0, 1); display.print("HP:" + String(lives));
display.setCursor(33, 1); display.print("H:" + String(heartsCollected));
display.setCursor(60, 1); display.print("S:" + String(score));
display.setCursor(95, 1); display.print("HI:" + String(highScore));
if (millis() > invincibilityUntil || (millis() / 100) % 2 == 0) {
display.drawBitmap(20, dinoY, (legToggle ? dino_f1 : dino_f2), 16, 16, WHITE);
if (starActive) display.drawCircle(27, dinoY + 8, 12, WHITE);
}
if (!isBossMode) {
if (obstacleX < 128) {
if (obsType == 0) display.drawBitmap(obstacleX, obstacleY, cactus_mini, 8, 10, WHITE);
else display.drawBitmap(obstacleX, obstacleY, ptero_bmp, 12, 8, WHITE);
}
} else {
display.drawBitmap(110, 43, cactus_big, 12, 12, WHITE);
display.drawBitmap(bulletX, bulletY, cactus_mini, 8, 10, WHITE);
display.setCursor(40, 15); display.print("BOSS!");
}
if (bonusX > 0 && bonusX < 128) {
if (bonusType == 0) display.drawBitmap(bonusX, bonusY, heart_bmp, 8, 7, WHITE);
else if (bonusType == 1) display.drawBitmap(bonusX, bonusY, star_bmp, 8, 8, WHITE);
else { display.drawBitmap(bonusX, bonusY, heart_bmp, 8, 7, WHITE); display.drawRect(bonusX-1, bonusY-1, 10, 9, WHITE); }
}
display.display();
delay(30);
}