#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Arduino.h>
// --- Экран ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- Кнопки ---
#define B1 4
#define B2 5
#define B3 18
#define B4 19
#define C 21
// --- Глобальные состояния ---
bool inGame = false;
int gameIndex = -1;
bool gameOver = false;
bool inSettings = false;
bool showFPS = true;
int fpsLimit = 30;
bool invertX = false;
bool invertY = false;
bool wifiOn = true;
// --- FPS переменные ---
int lastFPS = 0;
unsigned long lastMillis = 0;
int currentFPS = 0;
unsigned long frameStart = 0;
// --- Меню ---
int selectedIndex = 0;
float scrollOffsetY = 0;
const int menuItemsCount = 4;
const char* menuItems[] = {"Settings","Snake","Ping-Pong","Flappy Bird"};
// --- Настройки ---
int settingsIndex = 0;
const int settingsCount = 4;
float scrollOffsetYSettings = 0;
// --- Wi-Fi AP ---
const char* apSSID = "ESP32_OnionOS";
const char* apPassword = "323245";
// --- Графика для сплэш-скрина ---
const uint8_t Onion_map[] PROGMEM = {
0x06, 0x80, 0x09, 0x00, 0x03, 0x80, 0x01, 0x00, 0x02, 0x80,
0x0c, 0x60, 0x10, 0x10, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08,
0x20, 0x08, 0x20, 0x08, 0x1c, 0x70, 0x07, 0xc0, 0x02, 0x80,
0x00, 0x00
};
// --- Snake ---
#define MAX_SNAKE 100
struct Point{ int x,y; };
Point snake[MAX_SNAKE];
int snakeLen = 3;
int dirX = 1, dirY = 0;
Point apple;
int scoreSnake = 0;
// --- Pong ---
int ballX, ballY, ballDX, ballDY;
int paddleX = 40;
int enemyX = 40;
int scorePong = 0;
// --- Flappy Bird ---
float birdY = 32;
float birdDY = 0;
int scoreFlappy = 0;
struct FlappyPipe { float x; int gapY; };
FlappyPipe flappyPipe;
const int gapSize = 20;
// --- Функции Wi-Fi ---
void createAP(bool enable){
if(enable){
IPAddress localIP(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(apSSID, apPassword);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("AP Enabled!");
display.print("SSID: "); display.println(apSSID);
display.print("IP: "); display.println(localIP);
display.display();
delay(2000);
} else WiFi.softAPdisconnect(true);
}
// --- FPS ---
void updateFPS(){
currentFPS++;
if(millis() - lastMillis >= 1000){
lastFPS = currentFPS;
currentFPS = 0;
lastMillis = millis();
}
}
void drawFPS(int x = SCREEN_WIDTH-40, int y = SCREEN_HEIGHT-8){
if(showFPS){
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(x,y);
display.print("FPS:"); display.print(lastFPS);
}
}
// --- Меню и настройки ---
void drawMenu(){
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor((SCREEN_WIDTH-48)/2,0);
display.println("Menu");
const int itemHeight = 14;
float targetOffset = -selectedIndex*itemHeight + (SCREEN_HEIGHT-itemHeight)/2;
scrollOffsetY += (targetOffset - scrollOffsetY) * 0.3;
int yStart = int(scrollOffsetY);
for(int i=0;i<menuItemsCount;i++){
display.setTextColor(SSD1306_WHITE);
if(i==selectedIndex){
int textWidth = strlen(menuItems[i])*6;
display.fillRect(5,yStart-2,textWidth+6,12,SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK,SSD1306_WHITE);
}
display.setCursor(10,yStart);
display.println(menuItems[i]);
yStart += itemHeight;
}
drawFPS();
display.display();
}
void drawSettings(){
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Settings");
const int itemHeight = 14;
float targetOffset = -settingsIndex*itemHeight + SCREEN_HEIGHT/2 - itemHeight/2;
scrollOffsetYSettings += (targetOffset - scrollOffsetYSettings) * 0.3;
int yStart = int(scrollOffsetYSettings);
for(int i=0;i<settingsCount;i++){
display.setTextColor(SSD1306_WHITE);
if(i==settingsIndex){
display.fillRect(5,yStart-2,110,12,SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK,SSD1306_WHITE);
}
display.setCursor(10,yStart);
switch(i){
case 0: display.print("FPS:"); display.println(fpsLimit); break;
case 1: display.print("Show FPS:"); display.println(showFPS?"ON":"OFF"); break;
case 2: display.print("Invert X:"); display.println(invertX?"ON":"OFF"); break;
case 3: display.print("Wi-Fi AP:"); display.println(wifiOn?"ON":"OFF"); break;
}
yStart+=itemHeight;
}
display.display();
}
void settingsUp(){ settingsIndex=(settingsIndex==0?settingsCount-1:settingsIndex-1); drawSettings(); delay(150); }
void settingsDown(){ settingsIndex=(settingsIndex==settingsCount-1?0:settingsIndex+1); drawSettings(); delay(150); }
void settingsSelect(){
switch(settingsIndex){
case 0: fpsLimit=(fpsLimit==10?20:(fpsLimit==20?30:(fpsLimit==30?60:10))); break;
case 1: showFPS=!showFPS; break;
case 2: invertX=!invertX; break;
case 3: wifiOn=!wifiOn; createAP(wifiOn); break;
}
drawSettings(); delay(150);
}
// --- Snake ---
void resetSnake(){
snakeLen = 3;
snake[0] = {5,5}; snake[1] = {4,5}; snake[2] = {3,5};
dirX = 1; dirY = 0;
apple = {random(0,SCREEN_WIDTH/4), random(0,SCREEN_HEIGHT/4)};
scoreSnake = 0; gameOver = false;
}
void updateSnake(){
Point head = snake[0];
head.x += dirX; head.y += dirY;
for(int i=snakeLen;i>0;i--) snake[i]=snake[i-1];
snake[0]=head;
if(head.x==apple.x && head.y==apple.y){
if(snakeLen<MAX_SNAKE-1) snakeLen++;
scoreSnake++;
apple={random(0,SCREEN_WIDTH/4), random(0,SCREEN_HEIGHT/4)};
}
if(head.x<0||head.y<0||head.x>=SCREEN_WIDTH/4||head.y>=SCREEN_HEIGHT/4) gameOver=true;
for(int i=1;i<snakeLen;i++) if(snake[i].x==head.x && snake[i].y==head.y) gameOver=true;
}
void drawSnake(){
display.clearDisplay();
for(int i=0;i<snakeLen;i++) display.fillRect(snake[i].x*4,snake[i].y*4,4,4,SSD1306_WHITE);
display.fillRect(apple.x*4,apple.y*4,4,4,SSD1306_WHITE);
display.setCursor(0,0);
display.setTextSize(1);
display.print("Score:"); display.print(scoreSnake);
drawFPS();
display.display();
}
// --- Pong ---
void resetPong(){
ballX = SCREEN_WIDTH/2; ballY = SCREEN_HEIGHT/2;
ballDX = 1; ballDY = 1;
paddleX = 40; enemyX = 40;
scorePong = 0; gameOver = false;
}
void updatePong(){
ballX += ballDX; ballY += ballDY;
if(ballX <= 0 || ballX >= SCREEN_WIDTH-3) ballDX*=-1;
if(ballY <= 5 && ballX >= enemyX && ballX <= enemyX+30){ ballDY*=-1; scorePong++; }
if(ballY >= SCREEN_HEIGHT-8 && ballX >= paddleX && ballX <= paddleX+30){ ballDY*=-1; scorePong++; }
if(ballY < 0 || ballY > SCREEN_HEIGHT) gameOver=true;
if(ballX > enemyX+15) enemyX++; else if(ballX < enemyX+15) enemyX--;
}
void drawPong(){
display.clearDisplay();
display.fillRect(paddleX, SCREEN_HEIGHT-5, 30, 3, SSD1306_WHITE);
display.fillRect(enemyX, 2, 30, 3, SSD1306_WHITE);
display.fillRect(ballX, ballY, 3, 3, SSD1306_WHITE);
display.setCursor(0,0);
display.setTextSize(1);
display.print("Score:"); display.print(scorePong);
drawFPS();
display.display();
}
// --- Flappy Bird ---
void resetFlappy(){
birdY = 32; birdDY = 0;
flappyPipe.x = SCREEN_WIDTH;
flappyPipe.gapY = random(10, SCREEN_HEIGHT-30);
scoreFlappy = 0; gameOver = false;
}
void updateFlappy(){
birdDY += 0.27; birdY += birdDY;
if(!digitalRead(B1)) birdDY = -1.8;
flappyPipe.x -= 1.008;
if(flappyPipe.x < 0){
flappyPipe.x = SCREEN_WIDTH;
flappyPipe.gapY = random(10, SCREEN_HEIGHT-30);
scoreFlappy++;
}
if(birdY < 0 || birdY > SCREEN_HEIGHT) gameOver = true;
if((birdY < flappyPipe.gapY || birdY > flappyPipe.gapY+gapSize) && flappyPipe.x < 20 && flappyPipe.x > 0) gameOver = true;
}
void drawFlappy(){
display.clearDisplay();
display.fillRect(10, birdY, 5, 5, SSD1306_WHITE);
display.fillRect(flappyPipe.x, 0, 8, flappyPipe.gapY, SSD1306_WHITE);
display.fillRect(flappyPipe.x, flappyPipe.gapY+gapSize, 8, SCREEN_HEIGHT-(flappyPipe.gapY+gapSize), SSD1306_WHITE);
display.setCursor(0,0);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print("Score:"); display.print(scoreFlappy);
drawFPS();
display.display();
}
// --- Boot текст ---
void bootText(){
String bootLines[] = {
"Info : 122 89 server.c:299 add_service(): Listening on port 50001 for tcl connections",
"Info : 123 89 server.c:299 add_service(): Listening on port 50002 for telnet connections",
"Debug: 124 89 command.c:152 script_debug(): command - init",
"Debug: 125 89 command.c:152 script_debug(): command - target init",
"Debug: 126 89 command.c:152 script_debug(): command - target names",
"Debug: 127 89 command.c:152 script_debug(): command - esp32c3 cget -event gdb-flash-erase-start",
"Debug: 128 90 command.c:152 script_debug(): command - esp32c3 configure -event gdb-flash-erase-start reset init",
"Debug: 129 90 command.c:152 script_debug(): command - esp32c3 cget -event gdb-flash-write-end",
"Debug: 130 90 command.c:152 script_debug(): command - esp32c3 configure -event gdb-flash-write-end reset halt",
"Debug: 131 90 command.c:152 script_debug(): command - esp32c3 cget -event gdb-attach",
"Debug: 132 90 target.c:1597 handle_target_init_command(): Initializing targets...",
"Debug: 133 90 riscv.c:449 riscv_init_target(): [esp32c3] riscv_init_target()",
"Debug: 134 91 semihosting_common.c:109 semihosting_common_init():",
"Error: 135 118 esp_usb_jtag.c:636 esp_usb_jtag_init(): esp_usb_jtag: could not find or open device!",
"Debug: 136 118 command.c:528 exec_command(): Command 'init' failed with error code -4",
"User : 137 119 command.c:600 command_run_line(): C:/Users/kulik/AppData/Local/Arduino15/packages/esp32/tools/openocd-esp32/v0.12.0-esp32-20250422/bin/../share/openocd/scripts/target/esp_common.cfg:9: Error:",
"Debug: 138 121 breakpoints.c:328 breakpoint_remove_all_internal(): [esp32c3] Delete all breakpoints",
"Debug: 139 121 riscv.c:534 riscv_deinit_target(): [esp32c3] riscv_deinit_target()",
"Error: 140 121 riscv.c:429 get_target_type(): [esp32c3] Unsupported DTM version: -1",
"Error: 141 121 riscv.c:539 riscv_deinit_target(): [esp32c3] Could not identify target type."
};
for(int i = 0; i < 20; i++){
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(bootLines[i]);
display.display();
delay(random(200, 400));
}
}
// --- Splash Screen ---
void splashScreen(){
unsigned long start = millis();
while(millis()-start < 2000){
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor((SCREEN_WIDTH-48)/2, (SCREEN_HEIGHT-8)/2);
display.println("Onion OS");
display.drawBitmap((SCREEN_WIDTH-16)/2, (SCREEN_HEIGHT/2)+2, Onion_map, 16, 16, SSD1306_WHITE);
display.display();
delay(50);
}
}
// --- Setup ---
void setup(){
Serial.begin(115200);
pinMode(B1,INPUT_PULLUP); pinMode(B2,INPUT_PULLUP);
pinMode(B3,INPUT_PULLUP); pinMode(B4,INPUT_PULLUP);
pinMode(C,INPUT_PULLUP);
Wire.begin(22,23);
display.begin(SSD1306_SWITCHCAPVCC,0x3C);
randomSeed(analogRead(0));
bootText();
splashScreen();
createAP(wifiOn);
drawMenu();
}
// --- Loop ---
void loop(){
frameStart = millis();
updateFPS();
if(!inGame && !inSettings){
if(!digitalRead(B1)){ selectedIndex=(selectedIndex==0?menuItemsCount-1:selectedIndex-1); drawMenu(); delay(150);}
if(!digitalRead(B2)){ selectedIndex=(selectedIndex==menuItemsCount-1?0:selectedIndex+1); drawMenu(); delay(150);}
if(!digitalRead(C)){
if(selectedIndex==0){ inSettings=true; settingsIndex=0; drawSettings(); delay(150);}
else{
inGame=true; gameIndex=selectedIndex-1; gameOver=false;
if(gameIndex==0) resetSnake();
if(gameIndex==1) resetPong();
if(gameIndex==2) resetFlappy();
delay(150);
}
}
} else if(inSettings){
if(!digitalRead(B1)) settingsUp();
if(!digitalRead(B2)) settingsDown();
if(!digitalRead(B3)||!digitalRead(B4)) settingsSelect();
if(!digitalRead(C)) { inSettings=false; drawMenu(); delay(150);}
} else{
if(gameOver){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(15,15);
display.println("GAME OVER");
display.setTextSize(1);
display.setCursor(30,40);
display.print("Score:");
if(gameIndex==0) display.print(scoreSnake);
else if(gameIndex==1) display.print(scorePong);
else if(gameIndex==2) display.print(scoreFlappy);
display.display();
if(!digitalRead(C)){ inGame=false; gameIndex=-1; drawMenu(); delay(150);}
return;
}
if(!digitalRead(C)){ inGame=false; gameIndex=-1; drawMenu(); delay(150); return;}
if(gameIndex==0){
if(!digitalRead(B1)) { dirX=0; dirY=-1; }
if(!digitalRead(B2)) { dirX=0; dirY=1; }
if(!digitalRead(B3)) { dirX=-1; dirY=0; }
if(!digitalRead(B4)) { dirX=1; dirY=0; }
updateSnake(); if(!gameOver) drawSnake();
delay(126*(60/fpsLimit));
}
if(gameIndex==1){
if(!digitalRead(B3) && paddleX>0) paddleX-=2;
if(!digitalRead(B4) && paddleX<SCREEN_WIDTH-30) paddleX+=2;
updatePong(); if(!gameOver) drawPong();
}
if(gameIndex==2){ updateFlappy(); drawFlappy(); }
while(millis()-frameStart < 1000/fpsLimit) delay(1);
}
}