Am linken und rechten Rand sind jeweils ein Tennis-Schläger (durch ein Rechteck dargestellt) zu sehen; diese können mit einem Joystick auf- und ab bewegt werden. Damit kann der Ball (durch ein kleines Quadrat oder einen Kreis dargestellt) im Spiel gehalten werden; dazu müssen die Spieler den Schläger mit dem Joystick so positionieren, dass der Ball nicht am Schläger vorbei auf die dahinter liegende Begrenzung gelangt.
Diese Spiel-Idee kann man relativ einfach auf dem TTGO T-Display realisieren. Wir beschränken uns dabei allerdings auf einen einzigen Spieler, der gegen den Microcontroller antritt. Hier kommt es nun darauf an, den Ball möglichst lange im Spiel zu halten.
Als Joy-Stick benutzen wir hier das Modul KY-023, welches für wenige Euro zu erwerben ist. Diees Modul schließen wir folgendermaßen an:
Code: Alles auswählen
# KY-023 -- ESP32
# GND -- G
# +5V -- 3V (real: 3.3 V)
# VRY -- 36
Im Wesentlichen besteht dieses Modul aus zwei Potentiometern, deren Mittenabgriffe eine Spannung (gegenüber GND) ausgeben, die nahezu proportional (s. u.) zur jeweiligen Auslenkung ist. In unserem Fall benutzen wir das Y-Signal; dieses wird mit einem ADC-Objekt in einen Zahlenwert umgesetzt. Über diesen Zahlenwert kann der Schläger (das rote Rechteck auf der rechten Seite) gesteuert werden. Zusätzlich ermittelt das Programm, ob der Ball auf den Schläger trifft oder nicht. Trifft er auf den Schläger wird der Ball reflektiert; dabei weicht der Ausfallswinkel (zufallsbedingt) mal etwas mehr, mal etwas weniger vom Einfallswinkel ab. Trifft der Ball nicht auf den Schläger, ist das Spiel beendet und die erzielte Spieldauer wird angezeigt.
Nach dem Start des Programms kann man zunächst den Schwierigkeitsgrad festlegen. Man kann mit den Tasten A (oben links) und B (unten links) wählen zwischen den Einstellungen
- slow
- medium
- fast
Dadurch wird die (durchschnittliche) Geschwindigkeit des Balls festgelegt. Nachdem man die Taste B betätigt hat, beginnt das Spiel.
Hier nun das kommentierte Programm:
Code: Alles auswählen
# Pong Version 1.3
# Joystick KY-023 anschließen (s.u.)
# Beachte: ADC des ESP32 arbeitet nicht ganz linear!
from machine import Pin, SPI, ADC
from time import sleep, time
from random import randint
import vga1_8x8 as font0
import vga1_bold_16x32 as font1
import vga1_16x16 as font2
import st7789
# Joystick:
# KY-023 -- ESP32
# GND -- G
# +5V -- 3V (real: 3.3 V)
# VRY -- 36
pot = ADC(Pin(36)) # ADC0
pot.atten(ADC.ATTN_11DB) # Messbereich: 0 - 3.6 V;
pot.width(ADC.WIDTH_12BIT) # 12 Bit: 0-4095
# Konstanten für Grafik:
max_x = 239
max_y = 134
ball_size = 10
ball_size_2 = int(ball_size / 2)
up = ball_size_2
up2 = ball_size
down1 = max_y - ball_size_2
down2 = max_y - ball_size
left1 = ball_size_2
left2 = ball_size
right1 = max_x - ball_size
right2 = max_x - ball_size * 2
mid_y = int(max_y / 2)
rack_size = 30
rack_size_2 = int(rack_size / 2)
########################## Menu-Funktionen #############################
def menu_rahmen():
display.rect(0, 0, 239, 134, st7789.YELLOW)
display.rect(0, 0 ,20 ,20, st7789.YELLOW)
display.text(font2, 'A', 2, 2, st7789.YELLOW)
display.rect(0, 114 ,20 ,20, st7789.YELLOW)
display.text(font2, 'B', 2, 117, st7789.YELLOW)
display.text(font0, 'Mit Taster A scrollen', 35, 30, st7789.RED)
display.text(font0, b'Mit Taster B ausw\x84hlen', 35, 40, st7789.RED)
def taster_eingabe(): # gibt Taster-Nummer zurück (A = 1, B = 2)
tasterA = Pin(0, Pin.IN, Pin.PULL_UP)
tasterB = Pin(35, Pin.IN) # hat auf dem Board schon einen 100K-Pullup (bei Key)
t = 0
weiter = True
while weiter:
if tasterA.value() == 0:
t = 1
if tasterB.value() == 0:
t = 2
if t > 0:
sleep(0.2)
if tasterA.value() and tasterB.value() == True:
weiter = False
return t
def auswahl_anzeigen(awliste):
for item in awliste:
if item[4]: # if selected...
display.text(font2,item[1],item[2],item[3],st7789.YELLOW)
else:
display.text(font2,item[1],item[2],item[3],st7789.WHITE)
def waehlen(awliste): # gibt das ausgewählte Item zurück
global itemindex
weiter = True
while weiter:
menu_rahmen()
auswahl_anzeigen(awliste)
while True:
taster = taster_eingabe()
if taster == 1: # Scrollen
itemindex += 1
itemindex = itemindex % len(auswahlliste)
for item in awliste: # erst alle auf False setzen
item[4] = False
awliste[itemindex][4]=True # dann indiziertes Item auf True
auswahl_anzeigen(awliste)
if taster == 2: # auswählen
return awliste[itemindex]
weiter = False
################## pong-Funktionen ################################
def pong_rahmen():
display.rect(0, 0, right1, max_y, st7789.YELLOW)
def welcome():
display.text(font1, 'Pong 1.3', 50, 30, st7789.GREEN, st7789.BLACK)
display.text(font1, 'Welcome!', 50, 70, st7789.GREEN, st7789.BLACK)
def rack_pos():
pot_wert = pot.read()
spannung = 0.0007815*pot_wert + 0.13
spannung = spannung - 1.667 # Mittelstellung auf 0; Wert negativ, wenn Joystick nach oben/rechts ausgelenkt.
# oben/rechts: -1.52V; unten/links: 1.68V
return int(spannung/1.7*50) + mid_y
def show_rack(y):
display.fill_rect(right1, y-rack_size_2, ball_size, rack_size, st7789.RED)
def del_rack(y):
display.fill_rect(right1, y-rack_size_2, ball_size, rack_size, bg)
def show_ball(x,y):
display.rect(x-ball_size_2, y-ball_size_2, ball_size, ball_size, st7789.WHITE)
def del_ball(x,y):
display.rect(x-ball_size_2, y-ball_size_2, ball_size, ball_size, bg)
# Display initialisieren...
spi = SPI(2, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19))
display = st7789.ST7789(spi, 135, 240, reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3) # Landscape
display.init()
display.fill(0) # loeschen; Bildschirm schwarz
bg = st7789.color565(20, 20, 20)
############################### Hauptprogramm #########################
welcome()
sleep(2)
display.fill(0) # loeschen; Bildschirm schwarz
# Menue-Rahmen mit Taster-Kennung, Überschrift
menu_rahmen()
display.text(font2, 'Auswahl', 50, 5) # Überschrift
sleep(0.5)
# Menue-Auswahlliste initialisieren (Zahl, Text, x-Position, y-Position, ausgewählt/nicht ausgewählt)
auswahl0 = [0.06, ' slow ', 35, 60, False]
auswahl1 = [0.04, ' medium ', 35, 75, False]
auswahl2 = [0.02, ' fast ', 35, 90, False]
auswahlliste = [auswahl0, auswahl1, auswahl2]
itemindex = 0 # Standardvorgabe: slow
auswahlliste[itemindex][4] = True
# Auswahl durchführen und ausgeben (hier: delta_t als Maß für den Schwierigkeitsgrad):
delta_t = waehlen(auswahlliste)[0] # das erste Element des ausgewählten Items -> delta_t
# print(delta_t)
# Initialisierung:
ball_pos_x = 20
ball_pos_y = 50
ball_delta_x = 5
ball_delta_y = randint(-1,1)
display.fill(bg)
pong_rahmen()
sleep(1)
# Animation:
time1 = time()
while True:
s_position = rack_pos()
# print(s_position)
show_rack(s_position)
show_ball(ball_pos_x, ball_pos_y)
sleep(delta_t) # ggf. schneller
del_rack(s_position)
del_ball(ball_pos_x,ball_pos_y)
if ball_pos_x >= right2 and abs(ball_pos_y - s_position) > rack_size_2:
time2 = time()
display.text(font1, 'Game Over!', 35, 25, st7789.RED, bg)
display.text(font1, 'Time: '+str(time2-time1)+' s', 35, 65, st7789.RED, bg)
break
if ball_pos_x >= right2 or ball_pos_x <= left2:
ball_delta_x = - ball_delta_x
ball_delta_y = ball_delta_y + randint(-1,1) # kleine zufällige Änderung von ball_delta_y an linker/rechter Seite
if abs(ball_delta_y) >3: # ball_delta_y soll nicht zu groß werden
ball_delta_y = randint(-1,1)
print(ball_delta_y)
ball_pos_x = ball_pos_x + ball_delta_x
if ball_pos_y > down2 or ball_pos_y < up2:
ball_delta_y = - ball_delta_y
ball_pos_y = ball_pos_y + ball_delta_y
Bemerkungen zu den im Programm benutzten Klassen und Funktionen
Informationen zu Pin, SPI, sleep sowie zum Display findet man in dem Skript WLAN MIT DEM ESP32.
Weitere Informationen zum Betrieb des Displays gibt es hier.
Die Programmierung des Menü-Teils ist schon an dieser Stelle vorgestellt worden.
Zum Messen von Spannungswerten benutzen wir hier eine Instanz von ADC:
pot = ADC(Pin(36)) # ADC0
Damit wird der GPIO36 als Analog-Eingang festgelegt. Über den Bezeichner pot können wir dann den Verstärkungsgrad (Eigenschaft atten) und die Bit-Breite des Messwertes (width) einstellen:
pot.atten(ADC.ATTN_11DB) # Messbereich: 0 - 3.6 V;
pot.width(ADC.WIDTH_12BIT) # 12 Bit: 0-4095
Der Messwert lässt sich dann mit
pot_wert = pot.read()
auslesen. Der zugehörige Spannungswert ergibt sich bei meinem ESP32 dann durch
spannung = 0.0007815*pot_wert + 0.13
Diese Beziehung wurde durch eine lineare Anpassung an eine Messkurve aufgestellt.
Die Zeitmessung wurde mit der time-Funktion des Moduls time durchgeführt. Diese Funktion gibt die Zeitdauer vom Einschalten bzw. Hard Reset bis zur Abfrage (in Sekunden) an.
Die "zufälligen" Änderungen beim Ausfallswinkel werden mit Hilfe der Funktion randint(a, b) erzeugt. Diese Funktion liefert ganzzahlige Zufallswerte im Bereich von a bis b (einschließlich!); sie wird zuvor aus dem Modul random importiert.
.