Rybka w akwarium

Typ_projektu
Arduino
Zdjecie główne
OLED 128x64, napis "Jedzenie: 0", rybka na środku, glony na dole, zamek po prawej, płytka z ESP8266, przyciski do sterowania.
Krótki opis projektu

Jest to dwuosobowa gra oparta na mikrokontrolerach ESP8266 z wyświetlaczami OLED, symulująca karmienie rybki. Gracz steruje rybką, której zadaniem jest zbieranie spadającego pożywienia. Po zebraniu 10 porcji jako nagroda wypływa animowana ośmiornica. Dwie niezależne konsolki komunikują się ze sobą za pomocą sieci Wi-Fi - naciśnięcie przycisku na jednej z nich powoduje pojawienie się jedzenia na drugiej, co umożliwia rozgrywkę w trybie dwuosobowym.

Niezbędne elementy

1. Płytka Arduino UNO lub kompatybilna x2

2. Shield SIC Game Console x2

Sprzęt

Komputer PC z Arduino IDE

Opis projektu
  1. WiFi i komunikacja
    • Jeden ESP tworzy Access Point o nazwie „ESP_NET”
    • Drugi ESP łączy się jako klient i komunikują się przez UDP na porcie 4210
    • Komenda "FOOD" przesyłana jest przez UDP i powoduje pojawienie się jedzenia u odbiorcy
  2. Sterowanie
    • 4 przyciski kierunkowe poruszają rybką
    • 1 przycisk wysyła sygnał o jedzeniu
  3. Logika gry
    • Rybka porusza się automatycznie, gdy gracz nią nie steruje, można nią sterować za pomocą przycisków
    • Po złapaniu pokarmu przez dotknięcie go rybką zwiększa się licznik
    • Co 10 złapanych pokarmów pojawia się animowana ośmiornica i licznik jest resetowany
  4. Wyświetlanie
    • Całość wyświetlana jest na OLED przy użyciu biblioteki Adafruit SSD1306
    • Na ekranie pojawiają się: rybka, spadające jedzenie, ośmiornica, bąbelki, zamek i animowane wodorosty
Zdjęcia
kod programu
//1 kontrolka:


 

#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 ustawienia


 

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;


 

WiFiUDP udp;


 

// Przyciski


 

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;


 

void setup() {


 

  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);


 

}


 

void loop() {


 

  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) {


 

    sendFoodSignal();


 

  }


 

  // 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();  


 

    }


 

  }


 

  if (foodY != -1) {


 

    moveFood();


 

    if (checkCollision()) {


 

      foodX = -1;
      foodY = -1;
      foodCaught++;
        if (foodCaught >= 10) {
        foodCaught = 0;
        octopusX = SCREEN_WIDTH + 10; // reset pozycji poza ekranem po prawej
        octopusVisible = true;


 

      }


 

    }


 

  }


 

  if (octopusVisible) {


 

    moveOctopus();


 

  }


 

  if (!manualControl && millis() - lastMoveTime > moveInterval) {


 

    moveFishRandomly();
    lastMoveTime = millis();


 

  }


 

  drawFish(fishX, fishY); // zawsze rysuj całość na końcu


 

  delay(100);
}


 

// ------------------- UDP ----------------------


 

void sendFoodSignal() {


 

  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;
  }


 

}


 

void spawnFood() {


 

  foodX = random(0, SCREEN_WIDTH);
  foodY = 0;


 

}


 

void moveFood() {


 

  foodY += 2;
  if (foodY >= SCREEN_HEIGHT) {


 

    foodX = -1;
    foodY = -1;


 

  }
  display.fillCircle(foodX, foodY, 2, SSD1306_WHITE);
  display.display();


 

}


 

bool checkCollision() {


 

  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) {


 

  // Główka


 

  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);


 

}
void moveOctopus() {


 

  if (octopusX > -20) {


 

    octopusX -= 2;


 

  } else {


 

    octopusVisible = false;


 

  }


 

}


 

//2 kontrolka:


 

#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 ustawienia


 

// 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;


 

WiFiUDP udp;




 

// Przyciski


 

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;
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);


 

}


 

void loop() {


 

  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) {


 

    sendFoodSignal();


 

  }


 

  // 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


 

    }


 

  }


 

  if (foodY != -1) {


 

    moveFood();


 

    if (checkCollision()) {


 

      foodX = -1;
      foodY = -1;
      foodCaught++;


 

        if (foodCaught >= 10) {
        foodCaught = 0;
        octopusX = SCREEN_WIDTH + 10; // reset pozycji poza ekranem po prawej
        octopusVisible = true;


 

      }


 

    }


 

  }


 

  if (octopusVisible) {


 

    moveOctopus();


 

  }
  if (!manualControl && millis() - lastMoveTime > moveInterval) {


 

    moveFishRandomly();
    lastMoveTime = millis();


 

  }


 

  drawFish(fishX, fishY); // zawsze rysuj całość na końcu
  delay(100);


 

}


 

// ------------------- UDP ----------------------



 

void sendFoodSignal() {


 

  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;


 

  }


 

}


 

void spawnFood() {


 

  foodX = random(0, SCREEN_WIDTH);
  foodY = 0;


 

}


 

void moveFood() {


 

  foodY += 2;


 

  if (foodY >= SCREEN_HEIGHT) {


 

    foodX = -1;
    foodY = -1;


 

  }



 

  display.fillCircle(foodX, foodY, 2, SSD1306_WHITE);


 

  display.display();


 

}


 

bool checkCollision() {


 

  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) {


 

  // Główka


 

  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);


 

}




 

void moveOctopus() {


 

  if (octopusX > -20) {


 

    octopusX -= 2;


 

  } else {
    octopusVisible = false;
  }


 

}
Pliki_projektu
Youtube
Tagi
rybki akwarium ESP8266 pokarm gradladwojga