Entwicklung eines eigenen Spiels
Zur Anzeige des Futters haben wir zu Beginn schon Variablen vorbereitet:
reward = [0, 0]
reward_captured = True
Wenn das Futter gefressen wurde (reward_captured == True), soll die Snake wachsen und neue Koordinaten
für das Futter zufällig generiert werden, dabei wird darauf geachtet, dass das Futter nicht innerhalb der Snake liegt.
Außerdem wird die Abfragevariable zurückgesetzt. Wenn nicht, bleibt die
Snake gleich lang. Da in jeder Iteration ein neuer Kopf der Snake hinzugefügt wird, muss dazu das letzte
Koordinatenpaar entfernt werden. Für diese Funktionalität wird die folgende Anweisung in die Spieliteration hinzugefügt:
if reward_captured:
reward = [random.randint(0, GAME_WIDTH), random.randint(0, GAME_HEIGHT)]
while reward in snake:
reward = [random.randint(0, GAME_WIDTH), random.randint(0, GAME_HEIGHT)]
reward_captured = False
else:
snake.pop(0)
Außerdem muss nach Generieren der Koordinaten des Snakekopfes abgefragt werden, ob diese den Koordinaten des Futters entsprechen:
if [snake_head_x, snake_head_y] == [reward]:
reward_captured = True
Zuletzt soll das Futter natürlich angezeigt werden:
pgb.fill_rect(reward[0] * PIXEL_FACTOR, reward[1] * PIXEL_FACTOR, PIXEL_FACTOR, PIXEL_FACTOR, RED)
Nun lässt sich die Snake steuern und interagiert wie gewollt mit dem Futter. Zuletzt muss noch auf eine Kollision mit sich selbst überprüft werden. Dazu wird nach Generieren des Schlangenkopfes abgefragt, ob dieser in der Snake schon vorkommt. Ist dies der Fall, wird die Spielschleife beendet.
if [snake_head_x, snake_head_y] in snake:
break
Nach der Schleife wird dementsprechend "Game Over" angezeigt. Dann wird auf einen Tastendruck gewartet, der die Konsole komplett zurücksetzt.
time.sleep_ms(100)
pgb.text("GAME OVER", 85, 75, WHITE)
pgb.text("Press A or B to restart", 30, 100, WHITE)
pgb.show()
# auf Input warten
while True:
if pgb.button_A() or pgb.button_B():
machine.reset() # alles zurücksetzen
Damit ist unser Spiel fertig programmiert. Der fertige Code sieht so aus:
import random
from PicoGameBoy import PicoGameBoy
from random import randint
import time
from machine import Timer
import machine
BLACK = PicoGameBoy.color(0, 0, 0)
WHITE = PicoGameBoy.color(255, 255, 255)
RED = PicoGameBoy.color(255, 0, 0)
pgb = PicoGameBoy()
# Konstanten für die Unterteilung des Displays in größere Pixel
PIXEL_FACTOR = 10
GAME_WIDTH = int(pgb.width / PIXEL_FACTOR)
GAME_HEIGHT = int(pgb.height / PIXEL_FACTOR)
# Snake als Liste von Koordinaten, startet in der Mitte des Spielfelds
snake = [[int(GAME_WIDTH / 2), int(GAME_HEIGHT / 2)]]
# Position des Futters
reward = [0, 0]
reward_captured = True
# aktuelle Richtung: 0 ist oben, dann im Uhrzeigersinn durchnummeriert
direction = 0
# Statusvariablen der Taster
button_pressed = False
button_left_previous = True
button_right_previous = True
button_left_time = 0
button_right_time = 0
# Zur Steuerung der regelmäßigen Iteration
timer = Timer()
game_step = False
def game_iteration(t):
# Funktion zur Aktivierung der nächsten Spieliteration
global game_step
game_step = True
timer.init(freq=4, mode=Timer.PERIODIC, callback=game_iteration)
# Hauptschleife
while True:
# Taster abfragen
if not button_pressed: # nur ein Richtungswechsel pro Iteration ist möglich
if pgb.button_right() and not button_right_previous:
direction = (direction + 1) % 4 # Rollover über Modulo
button_pressed = True
button_right_previous = pgb.button_right()
if pgb.button_left() and not button_left_previous:
# statt einmal nach links wird dreimal nach rechts abgebogen → Rollover mit Modulo funktioniert auch hier
direction = (direction + (4 - 1)) % 4
button_pressed = True
button_left_previous = pgb.button_left()
# Spieliteration
if game_step: # Iteration nur, wenn die entsprechende Zeit abgelaufen ist
# Richtung in x- und y-Komponenten zerlegen
if direction == 0:
dir_x = 0
dir_y = -1
elif direction == 1:
dir_x = 1
dir_y = 0
elif direction == 2:
dir_x = 0
dir_y = 1
else:
dir_x = -1
dir_y = 0
# Futter abfragen
if reward_captured:
reward = [random.randint(0, GAME_WIDTH - 1), random.randint(0, GAME_HEIGHT - 1)]
while reward in snake:
reward = [random.randint(0, GAME_WIDTH - 1), random.randint(0, GAME_HEIGHT - 1)]
reward_captured = False
else:
snake.pop(0)
# neue Koordinaten für Schlangenkopf
snake_head_x = snake[-1][0] + dir_x
snake_head_y = snake[-1][1] + dir_y
if snake_head_x < 0:
snake_head_x = GAME_WIDTH - 1
elif snake_head_x >= GAME_WIDTH:
snake_head_x = 0
if snake_head_y < 0:
snake_head_y = GAME_HEIGHT - 1
elif snake_head_y >= GAME_HEIGHT:
snake_head_y = 0
if [snake_head_x, snake_head_y] in snake:
break
# neuen Schlangenkopf ans Ende der Snake hängen
snake.append([snake_head_x, snake_head_y])
# Abfrage, ob Futter erreicht
if [snake_head_x, snake_head_y] == reward:
reward_captured = True
# Display aktualisieren
pgb.fill(BLACK) # clear
pgb.fill_rect(reward[0] * PIXEL_FACTOR, reward[1] * PIXEL_FACTOR, PIXEL_FACTOR, PIXEL_FACTOR, RED)
for pixel in snake: # Snake zeichnen
pgb.fill_rect(pixel[0] * PIXEL_FACTOR + 1, pixel[1] * PIXEL_FACTOR + 1, PIXEL_FACTOR - 2, PIXEL_FACTOR - 2,
WHITE)
pgb.show() # Alles anzeigen
game_step = False
button_pressed = False
# Game Over- Screen anzeigen
time.sleep_ms(100)
pgb.text("GAME OVER", 85, 75, WHITE)
pgb.text("Press A or B to restart", 30, 100, WHITE)
pgb.show()
# auf Input warten
while True:
if pgb.button_A() or pgb.button_B():
machine.reset() # alles zurücksetzen
Wir haben hier das Spiel Snake auf eine relativ einfache Weise implementiert, sicherlich könnte man das Spiel an einigen Stellen verbessern oder erweitern. Nach dem selben Schema können auch andere Spiele implementiert werden, hier sind deiner Kreativität keine Grenzen gesetzt. Wenn du selbst eine Spiel implementiert hast, schreib mir gerne eine Nachricht an jens.hellhorst@siemens.com, dann können wir das Spiel in das offizielle Programm einbinden.