Projekt to prosta gra zręcznościowa na, uruchomiona na ESP8266 w MicroPythonie, z kontrolerem zrobionym na drugim ESP8266. Sterujemy samochodzikiem tylko w osi X, czyli na lewo lub prawo, a droga i przeszkody jadą z góry na dół. Na drodze pojawiają się nadjeżdżające samochody w postaci czerwonych pikseli. Gdy gracz w nie wjedzie gra się kończy i wyświetlany jest ekran GAME OVER oraz wynik, którym jest ilość przetrwanych w grze sekund. Dodatkowo co 10 sekund gra automatycznie zwiększa prędkość przeszkód, aby zwiększyć trudność.
1. ESP8266 (NodeMCU) ×2
2. Matryca LED 16×16 pixeli
3. Joystick analogowy
4. przewody
5. komputer PC z Thonny
OPIS PROJEKTU
Zrealizowano prostą grę zręcznościową na wyświetlaczu LED 16×16, uruchomioną na ESP8266 w MicroPythonie, z prostym sterowaniem wyłącznie w osi X. Założeniem rozgrywki jest symulacja ruchu drogi poprzez przesuwanie otoczenia od góry do dołu, przy jednoczesnym pojawianiu się przeszkód w postaci nadjeżdżających samochodzików przedstawionych jako piksele.
ZASADY GRY
Zadaniem gracza jest omijanie przeszkód poprzez zmianę położenia w poziomie. Przeszkody w postaci czerwonych pikseli nadjeżdżają od góry ekranu w dół, tworząc efekt jadącej drogi. Dodatkowo co jakiś czas generowane są przeszkody w innym kolorze, które poruszają się szybciej niż standardowe czerwone samochody. W przypadku najechania na przeszkodę następuje zakończenie gry, po czym wyświetlany jest komunikat końca gry oraz wynik określony jako czas przetrwania wyrażony w sekundach.
ARCHITEKTURA SPRZĘTOWA
Układ został podzielony na dwa moduły połączone bezprzewodowo przez Wi-Fi. Pierwsza płytka ESP8266 odpowiada za obsługę matrycy LED oraz całą logikę gry (generowanie i przesuw przeszkód, wykrywanie kolizji, pomiar czasu i prezentację wyniku). Druga płytka ESP8266 pełni funkcję kontrolera z joystickiem i wysyła do modułu wyświetlacza proste komendy sterujące ruchem w lewo lub w prawo.
POŁĄCZENIE PŁYTEK PRZEZ WI-FI
Komunikację pomiędzy modułem joysticka a modułem z matrycą zrealizowano bezprzewodowo z wykorzystaniem Wi-Fi. Moduł z matrycą tworzy własną sieć Wi-Fi, dzięki czemu do działania nie jest potrzebny zewnętrzny router. Moduł joysticka łączy się z tą siecią i utrzymuje połączenie w trakcie gry, aby na bieżąco przekazywać informacje o sterowaniu. W kodzie uwzględniono mechanizm ponawiania połączenia (funkcja connect_wifi()), tak aby w razie chwilowej utraty zasięgu komunikacja mogła zostać automatycznie przywrócona.
KOMUNIKACJA SIECIOWA (UDP)
Do przesyłania komend sterujących zastosowano prostą komunikację sieciową opartą na protokole UDP. Jest to rozwiązanie dobrze pasujące do gier i sterowania w czasie rzeczywistym, ponieważ liczy się szybka reakcja, a pojedyncza zagubiona wiadomość nie powoduje problemu - w kolejnym kroku i tak wysyłana jest następna, aktualna informacja o ruchu. Moduł joysticka cyklicznie wysyła krótkie dane opisujące kierunek sterowania (wysyłka realizowana w pętli głównej while True, poprzez metodę sendto()), a moduł z matrycą odbiera je i wykorzystuje do aktualizacji pozycji gracza. Odbiór danych działa w sposób, który nie zatrzymuje działania gry (ustawienie krótkiego czasu oczekiwania na pakiet przez sock.settimeout(0.05)), dzięki czemu animacja i odświeżanie wyświetlacza pozostają płynne nawet wtedy, gdy przez moment nie pojawi się nowa komenda.
DZIAŁANIE PROGRAMU
W trakcie działania programu cyklicznie odbierana jest ostatnia komenda sterująca z kontrolera, aktualizowana jest pozycja gracza z uwzględnieniem ograniczeń ekranu, przesuwane są przeszkody w dół, losowane są nowe przeszkody pojawiające się u góry, a następnie renderowany jest aktualny stan gry na macierzy LED. Po wykryciu kolizji uruchamiana jest procedura zakończenia gry i prezentowany jest wynik.
MECHANIZM ZWIĘKSZANIA TRUDNOŚCI
Zaimplementowano mechanizm zwiększania trudności - co 10 sekund automatycznie zwiększana jest prędkość nadjeżdżających przeszkód, co powoduje stopniowy wzrost tempa rozgrywki i wymaga szybszej reakcji. Przyspieszenie następuje maksymalnie 5 razy w trakcie 50 sekund.
STRUKTURA KODU
JOYSTICK (kontroler)
Kod kontrolera zawiera konfigurację sprzętową wejść (odczyt osi joysticka przez ADC oraz przycisku jako wejścia cyfrowego), konfigurację połączenia Wi-Fi w trybie STA oraz pętlę główną wysyłającą dane sterujące. W każdej iteracji pętli sprawdzany jest stan połączenia z siecią i w razie potrzeby wykonywane jest ponowne połączenie, następnie odczytywane są wartości z osi joysticka oraz stan przycisku, po czym dane są pakowane i wysyłane jako pakiet UDP do modułu z matrycą.
MATRYCA (gra + wyświetlanie)
Kod modułu z matrycą obejmuje konfigurację biblioteki obsługi diod adresowalnych, mapowanie współrzędnych (X,Y) na indeks fizycznego piksela w układzie zygzakowym, uruchomienie punktu dostępowego Wi-Fi oraz serwera UDP, a także pełną logikę gry. Logika rozgrywki opiera się na aktualizacji list obiektów (przeszkody, elementy tła, szybsze pojazdy), detekcji kolizji oraz renderowaniu klatki w każdej iteracji pętli. Zaimplementowano też proste ekrany interfejsu (start, game over, wynik, najlepszy wynik).
MAPOWANIE PIKSELI I WYŚWIETLANIE TEKSTU
Zastosowano funkcję przeliczającą współrzędne (X,Y) na indeks diody w łańcuchu, uwzględniając fizyczny sposób połączenia taśmy LED w matrycy (co drugi wiersz prowadzony w przeciwnym kierunku). Dodatkowo zastosowano wariant mapowania dedykowany do tekstu, odwracający oś X w celu uzyskania poprawnej orientacji liter. Wyświetlanie napisów zrealizowano na bazie prostego fontu bitmapowego, a rysowanie tekstu odbywa się przez rysowanie kolejnych znaków piksel po pikselu.
LOGIKA GRY I MASZYNA STANÓW
Program główny działa jako maszyna stanów, która rozdziela logikę ekranu startowego, właściwej rozgrywki oraz ekranów po zakończeniu gry. W stanie oczekiwania wyświetlany jest ekran powitalny i program czeka na akcję użytkownika, następnie w stanie rozgrywki wykonywana jest aktualizacja sterowania, przesuw obiektów, generowanie nowych przeszkód, skalowanie trudności oraz detekcja kolizji. Po kolizji następuje przejście do stanu końca gry, a wynik jest prezentowany jako czas przetrwania. Dodatkowo można wyświetlić najlepszy wynik zapisany w pamięci nieulotnej.
KLUCZOWE FUNKCJE
connect_wifi() - nawiązuje i utrzymuje połączenie Wi-Fi (reset interfejsu, próba połączenia, timeout, auto-reconnect).
xy_to_index(x, y) - mapuje współrzędne ekranu na indeks piksela w układzie zygzakowym.
xy_to_index_text(x, y) - wariant mapowania do tekstu (korekta orientacji napisów).
reset_game() - inicjalizuje nową rozgrywkę (pozycja gracza, obiekty, czas, prędkość).
is_free(x, y) - sprawdza, czy pole jest wolne aby wygenerować nowe obiekty.
update_player_position(x_val) - zamienia odczyt joysticka na ruch w lewo/prawo z uwzględnieniem granic drogi.
draw_game() - renderuje scenę gry i odświeża matrycę.
draw_wait_screen(), draw_game_over(), draw_time_screen(), draw_best_screen() - rysują ekrany interfejsu (start, koniec gry, wynik, rekord).
MOŻLIWOŚCI ROZBUDOWY
Zastosowany podział na moduł sterowania oraz moduł wyświetlania i logiki gry upraszcza strukturę programu, stabilizuje działanie oraz ułatwia ewentualną rozbudowę projektu o dodatkowe elementy interfejsu lub nowe typy przeszkód.
#kod do joysticka
#zip do matrycy poniżej
import network
import socket
from machine import ADC, Pin
import time
vrx = ADC(Pin(34))
vry = ADC(Pin(35))
sw = Pin(4, Pin.IN, Pin.PULL_UP)
vrx.atten(ADC.ATTN_11DB)
vry.atten(ADC.ATTN_11DB)
vrx.width(ADC.WIDTH_12BIT)
vry.width(ADC.WIDTH_12BIT)
SSID = "ESP32_LED_AP"
PASS = "12345678"
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
def connect_wifi():
print("Łączenie z AP matrycy...")
wlan.disconnect()
time.sleep(0.2)
wlan.connect(SSID, PASS)
timeout = 0
while not wlan.isconnected():
timeout += 1
print("·", end="")
time.sleep(1)
if timeout > 10:
print("\nRestart próby połączenia...")
wlan.disconnect()
time.sleep(0.5)
wlan.connect(SSID, PASS)
timeout = 0
print("\nPołączono! IP:", wlan.ifconfig()[0])
connect_wifi()
UDP_IP = "192.168.4.1"
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
if not wlan.isconnected():
print("Utracono połączenie – próbuję ponownie!")
connect_wifi()
x_val = vrx.read()
y_val = vry.read()
sw_val = sw.value()
data = {
"x": x_val,
"y": y_val,
"sw": sw_val
}
sock.sendto(str(data).encode(), (UDP_IP, UDP_PORT))
time.sleep(0.05)