MicroPython

Beispielprojekt – Snake

Ein vollständiges Spiel, gebaut aus allem, was du im Tutorial gelernt hast.

Zum Abschluss ein vollständiges Spiel: Du steuerst mit dem Joystick eine Schlange über das Display und sammelst Äpfel. Jeder Apfel macht sie ein Stück länger. Das Spiel endet, wenn sie gegen den Rand oder gegen sich selbst stößt. Das Programm kommt mit dem aus, was du in den sieben Schritten gelernt hast: Variablen, Verzweigungen, Schleifen, Listen und Funktionen.

Öffne das PicoBoy Studio, klicke auf „Zu Text wechseln“ und „Bearbeitung aktivieren“, ersetze den Inhalt des Editors durch den Quelltext und klicke auf ▶ Testen. Im Emulator lenkst du mit den Pfeiltasten. Über „Dateien“ → „Auf den PicoBoy…“ speicherst du das Spiel auf dem Gerät, und es erscheint im Startmenü.

from picoboy import PicoBoy
import time
import random
import machine

xSize = 29
ySize = 15
pb = PicoBoy()
board = [[0 for x in range(ySize)] for y in range(xSize)]

##### Quellcode ueber dieser Linie bitte nicht aendern! #####

laenge = 3
xPos = 14
yPos = 7
richtung = random.randint(1, 4)   # 1 hoch, 2 runter, 3 links, 4 rechts

def putApple():                   # Apfel auf ein zufälliges leeres Feld legen
    global board
    x = random.randint(0, xSize-1)
    y = random.randint(0, ySize-1)
    while board[x][y] != 0:       # Feld besetzt? Dann neu auslosen.
        x = random.randint(0, xSize-1)
        y = random.randint(0, ySize-1)
    board[x][y] = -1              # -1 steht für den Apfel

def erlaubt(x, y):                # Darf der Kopf auf dieses Feld?
    if x < 0 or x >= xSize or y < 0 or y >= ySize or board[x][y] > 0:
        return False
    else:
        return True

def spielfeldLeeren():            # Alle Felder auf 0 setzen
    global board
    i = 0
    while i < xSize:
        j = 0
        while j < ySize:
            board[i][j] = 0
            j = j + 1
        i = i + 1

def score():                      # Punktestand rechts neben das Spielfeld
    global laenge
    punkte = laenge - 3           # Die Schlange beginnt mit Länge 3
    pb.text(str(punkte%10), 120, 43, 1)
    punkte = int(punkte/10)
    pb.text(str(punkte%10), 120, 33, 1)
    punkte = int(punkte/10)
    pb.text(str(punkte%10), 120, 23, 1)

def spielfeldZeichnen():          # Rahmen, Punkte und alle Felder zeichnen
    global xPos
    global yPos
    global board
    global laenge
    score()
    pb.rect(0, 0, 4*(xSize+1), 4*(ySize+1), 1)
    i = 0
    while i < xSize:
        j = 0
        while j < ySize:
            if board[i][j] > 0:                  # Ein Stück der Schlange
                if board[i][j] == laenge:        # Der Kopf ist gefüllt
                    pb.fill_rect(i*4+2, j*4+2, 4, 4, 1)
                else:                            # Der Rest ist nur ein Umriss
                    pb.rect(i*4+2, j*4+2, 4, 4, 1)
                board[i][j] = board[i][j] - 1    # Ein Durchlauf weniger
            if board[i][j] == -1:                # Der Apfel
                pb.fill_rect(i*4+3, j*4+3, 2, 2, 1)
            j = j + 1
        i = i + 1

def schritt():                    # Lenken, bewegen, Anstoß und Apfel prüfen
    global xPos
    global yPos
    global board
    global richtung
    global laenge

    if pb.pressedUp() and richtung != 2:  # Nicht direkt umkehren
        richtung = 1
    if pb.pressedDown() and richtung != 1:
        richtung = 2
    if pb.pressedLeft() and richtung != 4:
        richtung = 3
    if pb.pressedRight() and richtung != 3:
        richtung = 4

    if richtung == 1:
        yPos = yPos - 1
    if richtung == 2:
        yPos = yPos + 1
    if richtung == 3:
        xPos = xPos - 1
    if richtung == 4:
        xPos = xPos + 1

    if not erlaubt(xPos, yPos):   # Angestoßen: Punkte zeigen, Neustart
        pb.fill_rect(10, 10, 4*(xSize+1)-20, 4*(ySize+1)-20, 0)
        pb.rect(10, 10, 4*(xSize+1)-20, 4*(ySize+1)-20, 1)
        pb.text("Score:", 18, 18)
        pb.text(str(laenge - 3), 18, 28)
        pb.show()
        time.sleep_ms(4000)
        machine.reset()

    if board[xPos][yPos] == -1:   # Apfel gefressen?
        laenge = laenge + 1
        putApple()

    board[xPos][yPos] = laenge    # Hier ist jetzt der Kopf

spielfeldLeeren()
putApple()

last = time.ticks_us()            # Zeitpunkt des letzten Durchlaufs merken

while True:
    schritt()
    pb.fill(0)
    spielfeldZeichnen()
    pb.show()

    while time.ticks_diff(time.ticks_us(), last) < 75000:
        pass                      # "pass" heißt: hier nichts tun
    last = time.ticks_us()

Das Spielfeld

Das Spielfeld board ist eine Liste aus Listen, wie das Schachbrett im Kapitel über Listen. Es hat 29 Spalten und 15 Zeilen (xSize und ySize). Hier steht die x-Position an erster Stelle: board[x][y]. Jedes Feld ist auf dem Display 4 mal 4 Pixel groß. Rechts daneben bleibt Platz für den Punktestand.

Die Zahl in einem Feld sagt, was dort liegt:

  • 0: Das Feld ist leer.
  • -1: Hier liegt ein Apfel.
  • Eine Zahl über 0: Hier liegt ein Stück der Schlange.

Der Trick mit der Schlange

Das Programm führt nirgends eine Liste aller Schlangenteile. Stattdessen schreibt schritt() in das Feld des Kopfs den Wert von laenge, und spielfeldZeichnen() zieht von jedem Schlangenfeld pro Durchlauf 1 ab. Die Zahl sagt also, wie viele Durchläufe dieses Stück noch bleibt. Bei 0 ist das Feld wieder leer, und das Ende der Schlange rückt von selbst nach.

Frisst die Schlange einen Apfel, wächst laenge um 1. Jedes neue Feld bleibt dann einen Durchlauf länger bestehen, und die Schlange wird länger.

Die Funktionen

  • putApple() lost ein Feld aus. Die while-Schleife lost so lange neu, bis das Feld leer ist.
  • erlaubt(x, y) liefert False, wenn die Position außerhalb des Spielfelds liegt oder dort schon ein Stück der Schlange ist. Die Reihenfolge ist wichtig: Python prüft die Teile von or von links nach rechts und hört beim ersten erfüllten auf. Liegt x außerhalb, liest es board[x][y] also gar nicht erst.
  • spielfeldLeeren() setzt alle Felder auf 0.
  • score() zerlegt den Punktestand in Ziffern: punkte%10 ist die letzte Ziffer, und int(punkte/10) schneidet sie ab. Die drei Ziffern stehen untereinander rechts vom Spielfeld.
  • spielfeldZeichnen() geht alle Felder durch. Der Kopf erscheint als gefülltes Quadrat, der Rest der Schlange als Umriss und der Apfel als kleiner Punkt.
  • schritt() liest den Joystick, bewegt den Kopf und prüft, ob die Schlange anstößt oder einen Apfel frisst. richtung steht für hoch (1), runter (2), links (3) und rechts (4). Bedingungen wie richtung != 2 verhindern, dass die Schlange direkt umkehrt und sich selbst beißt.

Die Hauptschleife

Ganz unten steht die Spielschleife. Sie macht einen Schritt, zeichnet das Bild und zeigt es an. Die innere while-Schleife wartet dann, bis seit dem letzten Durchlauf 75000 Mikrosekunden vergangen sind, also 75 Millisekunden. Anders als ein festes pb.delay() berücksichtigt sie die Zeit, die das Rechnen und Zeichnen gebraucht hat. So läuft die Schlange immer gleich schnell.

Zwei Dinge sehen anders aus als in den Kapiteln: Der Quelltext rückt mit vier Leerzeichen ein statt mit zwei, und global steht öfter da als nötig. Gebraucht wird es nur für Variablen, die einen neuen Wert bekommen, etwa laenge in schritt().

Übung

  1. Ersetze 75000 durch 50000. Die Schlange wird schneller.
  2. Rufe putApple() vor der Hauptschleife ein zweites Mal auf. Jetzt liegen ständig zwei Äpfel auf dem Feld, denn für jeden gefressenen Apfel legt das Programm einen neuen hin.