MicroPython

Die Macht der Wiederholung – Schleifen

Mit while und for lässt du Teile deines Programms beliebig oft laufen.

Wiederholen statt abschreiben

Oft soll ein Programmabschnitt mehrmals durchlaufen werden. Ein Spiel, das den Joystick nur ein einziges Mal abfragt, wäre nicht besonders spannend.

Dafür brauchst du Schleifen. Python kennt zwei Arten: die „while“-Schleife und die „for“-Schleife. Die „while“-Schleife wiederholt einen Abschnitt so lange, wie eine Bedingung erfüllt ist. Die „for“-Schleife führt eine vorher bekannte Anzahl von Durchläufen aus.

Wiederholen, solange etwas gilt: while

Die while-Schleife verwendest du meist, wenn vorher nicht bekannt ist, wie viele Durchläufe es geben wird. Zu Beginn jedes Durchlaufs – auch des ersten – wird geprüft, ob die Bedingung erfüllt ist. Ist sie das, läuft der Programmteil innerhalb der Schleife. Danach wird die Bedingung erneut geprüft, und so weiter. Ist sie nicht erfüllt, geht es hinter der Schleife weiter.

Zahlenraten

Der PicoBoy hat eine Zahl in einer Variablen abgelegt, die du erraten sollst. Die while-Schleife fragt so lange nach, bis du richtig geraten hast:

richtigeZahl = 7                     # Diese Zahl soll erraten werden
gerateneZahl = 0                     # Startwert für die erste Prüfung
while gerateneZahl != richtigeZahl:  # Solange die Zahlen ungleich (!=) sind:
  # Eingabe einlesen und in "gerateneZahl" ablegen
  gerateneZahl = int(input("Rate eine Zahl zwischen 1 und 10: "))
print("Richtig geraten!")            # Läuft erst nach der Schleife

„gerateneZahl“ wird vor der Schleife auf 0 gesetzt, damit die Bedingung beim ersten Durchlauf überhaupt geprüft werden kann. Ohne diese Zeile kennt Python die Variable noch nicht und bricht mit einem Fehler ab.

Programmablaufplan der while-Schleife: richtigeZahl = 7, gerateneZahl = 0, Frage gerateneZahl ungleich richtigeZahl; bei ja Eingabe und zurück zur Frage, bei nein „Richtig geraten!“

Spiele den Ablauf einmal durch. Zu Beginn ist gerateneZahl 0 und damit ungleich 7, die Schleife startet also. Gibst du 3 ein, ist die Bedingung immer noch erfüllt, und die Frage kommt erneut. Gibst du 7 ein, ist sie nicht mehr erfüllt. Die Schleife endet, und die letzte Zeile läuft. Sie ist nicht eingerückt und gehört deshalb nicht mehr zur Schleife.

Zählen mit while

Mit einer Variablen kann eine while-Schleife auch zählen. Das Programm gibt die Zahlen von 1 bis 5 aus:

i = 1                  # Startwert festlegen
while i < 6:           # Solange i kleiner als 6 ist:
  print(i)             # i ausgeben
  i = i + 1            # i um 1 erhöhen
print("Fertig")        # Läuft erst nach der Schleife

Vergisst du die Zeile i = i + 1, bleibt i immer 1, die Bedingung bleibt immer erfüllt, und die Schleife hört nie auf. Eine solche Endlosschleife macht nichts kaputt. Mit ■ Stop im Studio brichst du das Programm ab.

Übung: eine echte Zufallszahl

Bisher steht die gesuchte Zahl im Programm. Eine Zufallszahl bekommst du aus dem Modul random:

import random                           # Ganz oben im Programm: das Modul für Zufallszahlen laden

richtigeZahl = random.randint(1, 10)    # Zufällige ganze Zahl von 1 bis 10

Fehlt die import-Zeile, gibt es NameError: name 'random' isn't defined, und das Studio nennt dir die Zeile, die fehlt. Ergänze das Zahlenraten damit und baue in die Schleife eine Verzweigung ein, die „Zu klein“ oder „Zu groß“ ausgibt.

Eine feste Anzahl von Durchläufen: for

Die for-Schleife wird oft im Zusammenhang mit Listen verwendet – darauf kommst du im nächsten Kapitel zurück.

Zunächst nutzen wir sie in ihrer einfachen Form: Sie führt eine bestimmte Anzahl von Wiederholungen aus. Die sogenannte Schleifenvariable steigt dabei bei jedem Durchlauf um Eins.

Ein Fächer aus Linien

from picoboy import PicoBoy

pb = PicoBoy()

# Den Teil oberhalb dieser Zeile nicht verändern

pb.fill(0)
pb.show()

for i in range(0, 16):         # Der eingerückte Teil läuft 16-mal.
                               # Die Schleifenvariable i geht von 0 bis 15.
  pb.line(0, 0, i * 8, 63, 1)  # Linie von (0,0) nach (i*8,63) zeichnen
  pb.delay(50)                 # 50 Millisekunden warten
  pb.show()                    # Bildspeicher auf das Display übertragen

Beachte, dass range(0, 16) bei 0 beginnt und bei 15 endet – die obere Zahl gehört nicht mehr dazu. Es sind also 16 Durchläufe.

In jedem Durchlauf ändert sich nur der Endpunkt der Linie: Er liegt bei x = 0, 8, 16 und so weiter bis 120, weil i * 8 mit jedem Durchlauf um 8 wächst. Alle Linien beginnen in der Ecke (0,0). So entsteht der Fächer. pb.show() steht in der Schleife, deshalb siehst du jede neue Linie sofort.

Was range() noch kann

range() gibt es in drei Formen. Mit einer Zahl beginnt die Reihe bei 0. Eine dritte Zahl gibt die Schrittweite an:

for i in range(5):           # Kurzform für range(0, 5): 0, 1, 2, 3, 4
  print(i)
for i in range(2, 10, 2):    # Von 2 bis unter 10 in Zweierschritten: 2, 4, 6, 8
  print(i)
for i in range(3, 0, -1):    # Rückwärts: 3, 2, 1
  print(i)

Übung: ein zweiter Fächer

Füge im Fächer-Programm direkt unter pb.line(0, 0, i * 8, 63, 1) die Zeile pb.line(127, 0, i * 8, 63, 1) ein, gleich weit eingerückt. Überlege vorher, wo der zweite Fächer entsteht.

Schachteln

Wie schon die Verzweigungen lassen sich auch Schleifen schachteln und mit Verzweigungen kombinieren.

Ein Punkteraster

from picoboy import PicoBoy

pb = PicoBoy()

# Den Teil oberhalb dieser Zeile nicht verändern

pb.fill(0)
pb.show()

for i in range(0, 8):          # Der eingerückte Teil läuft 8-mal, auch die
                               # innere Schleife. i nimmt die Werte 0 bis 7 an.
  for j in range(0, 16):       # Der eingerückte Teil läuft je 16-mal.
                               # Dabei nimmt j die Werte 0 bis 15 an.
    pb.pixel(j * 8, i * 8, 1)  # Dieser Teil läuft also 8 * 16 = 128-mal.
                               # Zuerst ist i 0, und j geht von 0 bis 15.
                               # Dann ist i 1, und j geht wieder von 0 bis 15.
    pb.delay(20)
    pb.show()

Die Zahlen passen genau zum Display: 16 Spalten mal 8 Pixel ergeben 128, 8 Zeilen mal 8 Pixel ergeben 64. j bestimmt deshalb die x-Position und i die y-Position. pb.delay(20) und pb.show() sind so weit eingerückt wie pb.pixel() und laufen darum nach jedem einzelnen Punkt. So kannst du zusehen, wie das Raster Zeile für Zeile entsteht.

Die Spielschleife

Fast jedes Spiel ist eine einzige große while-Schleife: Eingaben abfragen, Bild zeichnen, anzeigen, kurz warten – und das immer wieder. while True: bedeutet „wiederhole endlos“.

from picoboy import PicoBoy

pb = PicoBoy()

# Den Teil oberhalb dieser Zeile nicht verändern

xPos = 64                                # Startposition: Mitte des Displays
yPos = 32

pb.fill(0)

while True:
  if pb.pressedUp() and yPos > 0:        # Joystick nach oben gedrückt?
    yPos = yPos - 1                      # Verringere yPos um 1
  if pb.pressedDown() and yPos < 63:     # Joystick nach unten gedrückt?
    yPos = yPos + 1                      # Erhöhe yPos um 1
  if pb.pressedLeft() and xPos > 0:      # Joystick nach links gedrückt?
    xPos = xPos - 1                      # Verringere xPos um 1
  if pb.pressedRight() and xPos < 127:   # Joystick nach rechts gedrückt?
    xPos = xPos + 1                      # Erhöhe xPos um 1
  if pb.pressedCenter():                 # Joystick in die Mitte gedrückt?
    pb.fill(0)                           # Bildspeicher leeren

  pb.pixel(xPos, yPos, 1)                # Punkt an (xPos,yPos) zeichnen
  pb.show()                              # Auf das Display übertragen
  pb.delay(20)                           # 20 Millisekunden warten

Die Abfragen auf yPos > 0 und yPos < 63 sorgen dafür, dass der Punkt das Display nicht verlässt. Weil der Bildspeicher nur beim Druck auf die Mitte geleert wird, zeichnest du eine Spur – wie mit einem Stift.

Wie schnell der Punkt läuft, bestimmst du selbst: Ohne pb.delay(20) rast er so schnell über das Display, dass du ihn kaum noch steuern kannst.

Am PicoBoy steuerst du den Punkt mit dem Joystick. Im Emulator nimmst du die Pfeiltasten, die Leertaste entspricht dem Druck in die Mitte. Reagiert der Punkt nicht, steht der Cursor vermutlich noch im Editor oder in der Konsole, denn dort landen die Tasten dann. Klicke außerhalb davon. Dieses Programm endet nie von selbst. Du beendest es mit ■ Stop.

Das steckt im Programm:

  • pb.pressedUp() und die anderen Abfragen liefern True, solange der Joystick in diese Richtung gedrückt ist, sonst False. Darum können sie ohne Vergleich direkt im if stehen.
  • Nach oben heißt yPos - 1, denn die y-Werte wachsen auf dem Display von oben nach unten.
  • Die Abfragen sind einzelne if-Zeilen und kein elif. So kann der Punkt in einem Durchlauf nach oben und nach rechts wandern, also schräg.
  • xPos und yPos stehen vor der Schleife. Stünden sie in der Schleife, begänne der Punkt bei jedem Durchlauf wieder in der Mitte.

Übung: Punkt ohne Spur

Füge direkt vor pb.pixel(xPos, yPos, 1) die Zeile pb.fill(0) ein, gleich weit eingerückt. Jetzt wird das Bild in jedem Durchlauf neu aufgebaut, und aus dem Stift wird eine Spielfigur. Fast alle Spiele arbeiten so.

Noch tiefer geschachtelt

In einer Schleife darf auch eine Verzweigung stehen:

from picoboy import PicoBoy

pb = PicoBoy()

# Den Teil oberhalb dieser Zeile nicht verändern

pb.fill(0)

for i in range(0, 8):
  for j in range(0, 16):
    if i * 2 != j:                      # Nur zeichnen, wenn i*2 ungleich j ist
      pb.fill_rect(j * 8, i * 8, 7, 7, 1)
pb.show()

Das Programm geht wieder alle 8 mal 16 Felder durch. pb.fill_rect() zeichnet ein Quadrat von 7 mal 7 Pixeln, und ein Pixel bleibt als Lücke frei. Das if lässt aber jedes Feld aus, bei dem i * 2 gleich j ist. Das trifft auf die Felder (i=0, j=0), (i=1, j=2), (i=2, j=4) und so weiter zu. Auf dem Display entsteht dadurch eine schwarze Diagonale. pb.show() steht diesmal ganz links und läuft nur einmal, nachdem beide Schleifen fertig sind. Das Bild erscheint also auf einen Schlag.

Übung: die Bedingung umdrehen

Ersetze != durch ==. Sage vorher, was du erwartest.