#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const char* ssid = "ESP_NET";
const char* password = "12345678";
const char* REMOTE_IP = "192.168.4.1"; // <-- ustaw na IP drugiego ESP
const unsigned int UDP_PORT = 4210;
const int pinGora = 14; // D1
const int pinDol = 2; // D2
const int pinLewo = 12; // D3
const int pinPrawo = 13; // D4
const int pinJedzenie = 0; // D3 na Wemosie, przycisk dla ziarenka
// Pozycje i stany
int fishX = SCREEN_WIDTH / 2;
int fishY = SCREEN_HEIGHT / 2;
int foodX = -1;
int foodY = -1;
int foodCaught = 0;
unsigned long lastMoveTime = 0;
const long moveInterval = 200;
bool manualControl = false;
int octopusX = SCREEN_WIDTH;
bool octopusVisible = false;
Serial.begin(115200);
pinMode(pinGora, INPUT_PULLUP);
pinMode(pinDol, INPUT_PULLUP);
pinMode(pinLewo, INPUT_PULLUP);
pinMode(pinPrawo, INPUT_PULLUP);
pinMode(pinJedzenie, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Błąd OLED");
for (;;);
}
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
Serial.println("\nPołączono z WiFi");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
udp.begin(UDP_PORT);
display.clearDisplay();
drawFish(fishX, fishY);
manualControl = false;
if (digitalRead(pinGora) == LOW) {
moveFish(0, -4);
manualControl = true;
if (digitalRead(pinDol) == LOW) {
moveFish(0, 4);
manualControl = true;
}
if (digitalRead(pinLewo) == LOW) {
moveFish(-4, 0);
manualControl = true;
if (digitalRead(pinPrawo) == LOW) {
moveFish(4, 0);
manualControl = true;
if (digitalRead(pinJedzenie) == LOW) {
// Odbieranie UDP i tworzenie jedzenia po stronie odbiorcy
int packetSize = udp.parsePacket();
Serial.print("Odebrano pakiet UDP: ");
char incoming[20];
int len = udp.read(incoming, sizeof(incoming));
incoming[len] = 0;
if (String(incoming) == "FOOD") {
Serial.println("Odebrano FOOD");
spawnFood();
foodX = -1;
foodY = -1;
foodCaught++;
if (foodCaught >= 10) {
foodCaught = 0;
octopusX = SCREEN_WIDTH + 10; // reset pozycji poza ekranem po prawej
octopusVisible = true;
if (!manualControl && millis() - lastMoveTime > moveInterval) {
moveFishRandomly();
lastMoveTime = millis();
drawFish(fishX, fishY); // zawsze rysuj całość na końcu
// ------------------- UDP ----------------------
Serial.println("Wysyłam FOOD przez UDP");
udp.beginPacket(REMOTE_IP, UDP_PORT);
udp.print("FOOD");
udp.endPacket();
// ------------------ RYBKA I GRAFIKA -------------------
void moveFish(int dx, int dy) {
fishX += dx;
fishY += dy;
fishX = constrain(fishX, 0, SCREEN_WIDTH - 1);
fishY = constrain(fishY, 0, SCREEN_HEIGHT - 1);
}
void moveFishRandomly() {
int direction = random(4);
case 0: moveFish(0, -2); break;
case 1: moveFish(0, 2); break;
case 2: moveFish(-2, 0); break;
case 3: moveFish(2, 0); break;
}
foodX = random(0, SCREEN_WIDTH);
foodY = 0;
foodY += 2;
if (foodY >= SCREEN_HEIGHT) {
}
display.fillCircle(foodX, foodY, 2, SSD1306_WHITE);
display.display();
return abs(fishX - foodX) < 5 && abs(fishY - foodY) < 5;
void drawFish(int x, int y) {
display.clearDisplay();
rysujZamek(95, 64);
rysujBabelki(108, 54);
drawOctopus(octopusX, 20);
for (int x = 0; x <= 30; x += 8) drawAnimatedSeaweed(x, 2 + (x % 2), x * 0.1);
for (int x = 45; x <= 65; x += 8) drawAnimatedSeaweed(x, 2 + (x % 3), x * 0.1);
for (int x = 75; x <= 90; x += 8) drawAnimatedSeaweed(x, 2 + (x % 2), x * 0.1);
display.fillRoundRect(x - 5, y - 3, 10, 6, 3, SSD1306_WHITE);
display.fillTriangle(x - 5, y, x - 9, y - 4, x - 9, y + 4, SSD1306_WHITE);
display.drawPixel(x + 4, y - 2, SSD1306_BLACK);
display.drawPixel(x + 5, y + 2, SSD1306_BLACK);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Jedzenie: ");
display.print(foodCaught);
display.display();
void drawAnimatedSeaweed(int x, int baseHeight, float timeOffset) {
int y = SCREEN_HEIGHT - 1;
float t = millis() / 600.0 + timeOffset;
for (int i = 0; i < baseHeight; i++) {
float sway = sin(t + i * 0.5) * 1.5;
int nextX = x + sway;
int nextY = y - 5;
display.drawLine(x, y, nextX, nextY, SSD1306_WHITE);
x = nextX;
y = nextY;
}
void rysujZamek(int x, int y) {
display.fillRect(x + 8, y - 24, 16, 24, SSD1306_WHITE);
display.fillTriangle(x + 8, y - 24, x + 16, y - 36, x + 24, y - 24, SSD1306_WHITE);
display.fillRect(x, y - 16, 8, 16, SSD1306_WHITE);
display.fillRect(x + 24, y - 16, 8, 16, SSD1306_WHITE);
display.fillRect(x + 14, y - 8, 6, 8, SSD1306_BLACK);
display.drawPixel(x + 14, y - 20, SSD1306_BLACK);
display.drawPixel(x + 18, y - 20, SSD1306_BLACK);
}
void rysujBabelki(int baseX, int baseY) {
int czas = millis() / 300;
int maxBubbleY = 20;
for (int i = 0; i < 3; i++) {
int bx = baseX + (i % 2 == 0 ? 0 : (i == 1 ? -2 : 2));
int by = baseY - (czas + i * 8) % (baseY - maxBubbleY);
display.drawCircle(bx, by, 1, SSD1306_WHITE);
void drawOctopus(int x, int y) {
display.fillCircle(x, y, 6, SSD1306_WHITE); // większa głowa
display.fillCircle(x - 2, y - 2, 1, SSD1306_BLACK); // lewe oko
display.fillCircle(x + 2, y - 2, 1, SSD1306_BLACK); // prawe oko
display.drawPixel(x - 2, y + 2, SSD1306_BLACK);
display.drawPixel(x - 1, y + 3, SSD1306_BLACK);
display.drawPixel(x, y + 3, SSD1306_BLACK);
display.drawPixel(x + 1, y + 3, SSD1306_BLACK);
display.drawPixel(x + 2, y + 2, SSD1306_BLACK);
// Macki (symetryczne, zakrzywione)
display.drawLine(x - 6, y + 5, x - 10, y + 10, SSD1306_WHITE);
display.drawLine(x - 4, y + 6, x - 6, y + 12, SSD1306_WHITE);
display.drawLine(x - 2, y + 6, x - 2, y + 13, SSD1306_WHITE);
display.drawLine(x + 2, y + 6, x + 2, y + 13, SSD1306_WHITE);
display.drawLine(x + 4, y + 6, x + 6, y + 12, SSD1306_WHITE);
display.drawLine(x + 6, y + 5, x + 10, y + 10, SSD1306_WHITE);
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// WiFi jako Access Point
const char* ssid = "ESP_NET";
const char* password = "12345678";
const char* REMOTE_IP = "192.168.4.2";
const unsigned int UDP_PORT = 4210;
const int pinGora = 14; // D1
const int pinDol = 2; // D2
const int pinLewo = 12; // D3
const int pinPrawo = 13; // D4
const int pinJedzenie = 0; // D3 na Wemosie, przycisk dla ziarenka
int fishX = SCREEN_WIDTH / 2;
int fishY = SCREEN_HEIGHT / 2;
int foodX = -1;
int foodY = -1;
int foodCaught = 0;
unsigned long lastMoveTime = 0;
const long moveInterval = 200;
bool manualControl = false;
int octopusX = SCREEN_WIDTH;
bool octopusVisible = false;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Access Point uruchomiony");
pinMode(pinGora, INPUT_PULLUP);
pinMode(pinDol, INPUT_PULLUP);
pinMode(pinLewo, INPUT_PULLUP);
pinMode(pinPrawo, INPUT_PULLUP);
pinMode(pinJedzenie, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Błąd OLED");
for (;;);
}
WiFi.softAP(ssid, password);
Serial.println("Uruchomiono AP");
Serial.print("IP: ");
Serial.println(WiFi.softAPIP());
udp.begin(UDP_PORT);
display.clearDisplay();
drawFish(fishX, fishY);
manualControl = false;
if (digitalRead(pinGora) == LOW) {
moveFish(0, -4);
manualControl = true;
if (digitalRead(pinDol) == LOW) {
moveFish(0, 4);
manualControl = true;
if (digitalRead(pinLewo) == LOW) {
moveFish(-4, 0);
manualControl = true;
if (digitalRead(pinPrawo) == LOW) {
moveFish(4, 0);
manualControl = true;
// Tylko wysyłanie — bez lokalnego jedzenia!
if (digitalRead(pinJedzenie) == LOW) {
// Odbieranie UDP i tworzenie jedzenia po stronie odbiorcy
int packetSize = udp.parsePacket();
if (packetSize) {
Serial.print("Odebrano pakiet UDP: ");
char incoming[20];
int len = udp.read(incoming, sizeof(incoming));
incoming[len] = 0;
if (String(incoming) == "FOOD") {
Serial.println("Odebrano FOOD");
spawnFood(); // ziarenko pojawia się tylko na odbiorniku
foodX = -1;
foodY = -1;
foodCaught++;
if (foodCaught >= 10) {
foodCaught = 0;
octopusX = SCREEN_WIDTH + 10; // reset pozycji poza ekranem po prawej
octopusVisible = true;
}
if (!manualControl && millis() - lastMoveTime > moveInterval) {
moveFishRandomly();
lastMoveTime = millis();
drawFish(fishX, fishY); // zawsze rysuj całość na końcu
delay(100);
// ------------------- UDP ----------------------
Serial.println("Wysyłam FOOD przez UDP");
udp.beginPacket(REMOTE_IP, UDP_PORT);
udp.print("FOOD");
udp.endPacket();
// ------------------ RYBKA I GRAFIKA -------------------
void moveFish(int dx, int dy) {
fishX += dx;
fishY += dy;
fishX = constrain(fishX, 0, SCREEN_WIDTH - 1);
fishY = constrain(fishY, 0, SCREEN_HEIGHT - 1);
void moveFishRandomly() {
int direction = random(4);
switch (direction) {
case 0: moveFish(0, -2); break;
case 1: moveFish(0, 2); break;
case 2: moveFish(-2, 0); break;
case 3: moveFish(2, 0); break;
foodX = random(0, SCREEN_WIDTH);
foodY = 0;
if (foodY >= SCREEN_HEIGHT) {
display.fillCircle(foodX, foodY, 2, SSD1306_WHITE);
return abs(fishX - foodX) < 5 && abs(fishY - foodY) < 5;
void drawFish(int x, int y) {
display.clearDisplay();
rysujZamek(95, 64);
rysujBabelki(108, 54);
if (octopusVisible) {
drawOctopus(octopusX, 20);
}
for (int x = 0; x <= 30; x += 8) drawAnimatedSeaweed(x, 2 + (x % 2), x * 0.1);
for (int x = 45; x <= 65; x += 8) drawAnimatedSeaweed(x, 2 + (x % 3), x * 0.1);
for (int x = 75; x <= 90; x += 8) drawAnimatedSeaweed(x, 2 + (x % 2), x * 0.1);
display.fillRoundRect(x - 5, y - 3, 10, 6, 3, SSD1306_WHITE);
display.fillTriangle(x - 5, y, x - 9, y - 4, x - 9, y + 4, SSD1306_WHITE);
display.drawPixel(x + 4, y - 2, SSD1306_BLACK);
display.drawPixel(x + 5, y + 2, SSD1306_BLACK);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Jedzenie: ");
display.print(foodCaught);
display.display();
}
void drawAnimatedSeaweed(int x, int baseHeight, float timeOffset) {
int y = SCREEN_HEIGHT - 1;
float t = millis() / 600.0 + timeOffset;
for (int i = 0; i < baseHeight; i++) {
float sway = sin(t + i * 0.5) * 1.5;
int nextX = x + sway;
int nextY = y - 5;
display.drawLine(x, y, nextX, nextY, SSD1306_WHITE);
x = nextX;
y = nextY;
}
void rysujZamek(int x, int y) {
display.fillRect(x + 8, y - 24, 16, 24, SSD1306_WHITE);
display.fillTriangle(x + 8, y - 24, x + 16, y - 36, x + 24, y - 24, SSD1306_WHITE);
display.fillRect(x, y - 16, 8, 16, SSD1306_WHITE);
display.fillRect(x + 24, y - 16, 8, 16, SSD1306_WHITE);
display.fillRect(x + 14, y - 8, 6, 8, SSD1306_BLACK);
display.drawPixel(x + 14, y - 20, SSD1306_BLACK);
display.drawPixel(x + 18, y - 20, SSD1306_BLACK);
void rysujBabelki(int baseX, int baseY) {
int czas = millis() / 300;
int maxBubbleY = 20;
for (int i = 0; i < 3; i++) {
int bx = baseX + (i % 2 == 0 ? 0 : (i == 1 ? -2 : 2));
int by = baseY - (czas + i * 8) % (baseY - maxBubbleY);
display.drawCircle(bx, by, 1, SSD1306_WHITE);
void drawOctopus(int x, int y) {
display.fillCircle(x, y, 6, SSD1306_WHITE); // większa głowa
// Oczka
display.fillCircle(x - 2, y - 2, 1, SSD1306_BLACK); // lewe oko
display.fillCircle(x + 2, y - 2, 1, SSD1306_BLACK); // prawe oko
// Uśmiech
display.drawPixel(x - 2, y + 2, SSD1306_BLACK);
display.drawPixel(x - 1, y + 3, SSD1306_BLACK);
display.drawPixel(x, y + 3, SSD1306_BLACK);
display.drawPixel(x + 1, y + 3, SSD1306_BLACK);
display.drawPixel(x + 2, y + 2, SSD1306_BLACK);
// Macki (symetryczne, zakrzywione)
display.drawLine(x - 6, y + 5, x - 10, y + 10, SSD1306_WHITE);
display.drawLine(x - 4, y + 6, x - 6, y + 12, SSD1306_WHITE);
display.drawLine(x - 2, y + 6, x - 2, y + 13, SSD1306_WHITE);
display.drawLine(x + 2, y + 6, x + 2, y + 13, SSD1306_WHITE);
display.drawLine(x + 4, y + 6, x + 6, y + 12, SSD1306_WHITE);
display.drawLine(x + 6, y + 5, x + 10, y + 10, SSD1306_WHITE);
} else {
octopusVisible = false;
}