PicoGame

Minimale Spielekonsole der SPE Karlsruhe auf Basis des Raspberry Pi Pico

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.