Читать книгу Routineaufgaben mit Python automatisieren - Al Sweigart - Страница 138
Ein kurzes Programm: Conways Spiel des Lebens
ОглавлениеConways Spiel des Lebens ist ein zellulärer Automat, in dem das Verhalten eines aus diskreten Zellen bestehenden Spielfeldes durch einen Satz Regeln bestimmt wird. Außerdem erzeugt er eine recht hübsche Animation. Die einzelnen Schritte können Sie auch auf kariertes Papier zeichnen, wobei die Kästchen die Zellen darstellen. Ein ausgefülltes Kästchen »lebt«, ein leeres Kästchen ist »tot«. Eine lebende Zelle mit zwei oder drei lebenden Nachbarn lebt im nächsten Schritt weiter. Eine tote Zelle mit genau drei lebenden Nachbarn wird im nächsten Schritt lebendig. Alle anderen Zellen sterben im nächsten Schritt oder bleiben tot. Abb. 4–8 zeigt ein Beispiel für eine Folge von Schritten.
Abb. 4–8 Vier Schritte in Conways Spiel des Lebens
Trotz der einfachen Regeln treten viele überraschende Verhaltensweisen auf. In dem Spiel können sich Muster fortbewegen, selbst replizieren und sogar Computerprozessoren nachahmen. Die Grundlage für all diese komplexen Verhaltensweisen ist jedoch ein ziemlich einfaches Programm.
Das zweidimensionale Spielfeld können wir durch eine Liste aus Listen darstellen. Die inneren Listen sind dabei die einzelnen Spalten, wobei der String '#' für eine lebende Zelle steht und der String ' ' (also ein Leerzeichen) für eine tote. Geben Sie den folgenden Code in den Dateieditor ein und speichern Sie ihn als conway.py. Wenn Ihnen jetzt noch nicht alle Einzelheiten des Codes klar sein sollten, ist das kein Beinbruch. Geben Sie alles ein und versuchen Sie die Kommentare und Erklärungen so gut wie möglich nachzuvollziehen.
# Conways Spiel des Lebens
import random, time, copy
WIDTH = 60
HEIGHT = 20
# Erstellt eine Liste aus Listen für die Zellen
nextCells = []
for x in range(WIDTH):
column = [] # Erstellt eine neue Spalte
for y in range(HEIGHT):
if random.randint(0, 1) == 0:
column.append('#') # Fügt eine lebende Zelle hinzu
else:
column.append(' ') # Fügt eine tote Zelle hinzu
nextCells.append(column) # nextCells ist eine Liste aus Spaltenlisten
while True: # Hauptschleife des Programms
print('\n\n\n\n\n') # Trennt die einzelnen Schritte durch Zeilenumbrüche
currentCells = copy.deepcopy(nextCells)
# Gibt currentCells auf dem Bildschirm aus
for y in range(HEIGHT):
for x in range(WIDTH):
print(currentCells[x][y], end='') # Gibt # oder Leerzeichen aus
print() # Gibt am Zeilenende einen Zeilenumbruch aus
# Berechnet die Zellen des nächsten Schritts anhand der aktuellen Zellen
for x in range(WIDTH):
for y in range(HEIGHT):
# Ruft die Koordinaten der Nachbarn ab
# Durch % WIDTH liegt leftCoord immer zwischen 0 und WIDTH - 1
leftCoord = (x - 1) % WIDTH
rightCoord = (x + 1) % WIDTH
aboveCoord = (y - 1) % HEIGHT
belowCoord = (y + 1) % HEIGHT
# Zählt die lebenden Nachbarn
numNeighbors = 0
if currentCells[leftCoord][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben links lebt
if currentCells[x][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben lebt
if currentCells[rightCoord][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben rechts lebt
if currentCells[leftCoord][y] == '#':
numNeighbors += 1 # Nachbarzelle links lebt
if currentCells[rightCoord][y] == '#':
numNeighbors += 1 # Nachbarzelle rechts lebt
if currentCells[leftCoord][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten links lebt
if currentCells[x][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten lebt
if currentCells[rightCoord][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten rechts lebt
# Bestimmt Zellenzustand nach den Regeln
if currentCells[x][y] == '#' and (numNeighbors == 2 or
numNeighbors == 3):
# Lebende Zelle mit 2 oder 3 lebenden Nachbarn lebt weiter
nextCells[x][y] = '#'
elif currentCells[x][y] == ' ' and numNeighbors == 3:
# Tote Zelle mit 3 Nachbarn wird lebendig
nextCells[x][y] = '#'
else:
# Alle anderen Zellen sterben oder bleiben tot
nextCells[x][y] = ' '
time.sleep(1) # Legt eine Pause von 1 s ein, um Flackern zu verhindern
Sehen wir uns diesen Code nun Zeile für Zeile an:
# Conways Spiel des Lebens
import random, time, copy
WIDTH = 60
HEIGHT = 20
Da wir die Funktionen random.randint.(), time.sleep() und copy.deepcopy() benötigen, importieren wir als Erstes die entsprechenden Module.
# Erstellt eine Liste aus Listen für die Zellen
nextCells = []
for x in range(WIDTH):
column = [] # Erstellt eine neue Spalte
for y in range(HEIGHT):
if random.randint(0, 1) == 0:
column.append('#') # Fügt eine lebende Zelle hinzu
else:
column.append(' ') # Fügt eine tote Zelle hinzu
nextCells.append(column) # nextCells ist eine Liste aus Spaltenlisten
Der erste Schritt unseres zellulären Automaten ist völlig willkürlich. Wir müssen eine Liste aus Listen erstellen, in denen wir die Strings '#' und ' ' zur Darstellung lebender und toter Zellen speichern, wobei die Position dieser Strings in den Listen die Position der entsprechenden Zellen auf dem Bildschirm bestimmt. Die inneren Listen stehen jeweils für eine Zellenspalte. Mit random.randint(0, 1) sorgen wir dafür, dass jede Zelle zu Anfang mit gleicher Wahrscheinlichkeit lebendig oder tot sein kann.
Diese Liste aus Listen stellen wir in die Variable nextCells, da der erste Schritt in der Hauptschleife des Programms darin besteht, nextCells in currentCells zu kopieren. In unserer Liste aus Listen wird die x-Koordinate bei 0 beginnend von links nach rechts gezählt und die y-Koordinate von oben nach unten. Daher entspricht nextCells[0][0] der Zelle in der oberen linken Ecke, nextCells[1][0] der Zelle rechts daneben und nextCells[0][1] der Zelle darunter.
while True: # Hauptschleife des Programms
print('\n\n\n\n\n') # Trennt die einzelnen Schritte durch Zeilenumbrüche
currentCells = copy.deepcopy(nextCells)
Jede Iteration der Hauptschleife bildet einen Schritt des zellulären Automaten. Dabei kopieren wir jeweils nextCells in currentCells, geben currentCells auf dem Bildschirm aus und berechnen dann anhand der Zellen in currentCells die Zellen in nextCells.
# Gibt currentCells auf dem Bildschirm aus
for y in range(HEIGHT):
for x in range(WIDTH):
print(currentCells[x][y], end='') # Gibt # oder Leerzeichen aus
print() # Gibt am Zeilenende einen Zeilenumbruch aus
Mit diesen verschachtelten for-Schleifen geben wir jeweils eine komplette Zeilenreihe auf dem Bildschirm aus, gefolgt von einem Zeilenumbruch am Ende. Dieser Vorgang wird für jede Zeile in currentCells wiederholt.
# Berechnet die Zellen des nächsten Schritts anhand der aktuellen Zellen
for x in range(WIDTH):
for y in range(HEIGHT):
# Ruft die Koordinaten der Nachbarn ab
# Durch % WIDTH liegt leftCoord immer zwischen 0 und WIDTH - 1
leftCoord = (x - 1) % WIDTH
rightCoord = (x + 1) % WIDTH
aboveCoord = (y - 1) % HEIGHT
belowCoord = (y + 1) % HEIGHT
Als Nächstes brauchen wir zwei verschachtelte for-Schleifen, um die einzelnen Zellen für den nächsten Schritt zu berechnen. Ob eine Zelle lebendig oder tot ist, hängt von ihren Nachbarn ab, weshalb wir als Erstes die Indizes der Zellen links, rechts, über und unter der aktuellen x- und y-Koordinate bestimmen müssen.
Mit dem Modulo-Operator % können wir auch den Umlauf an den Rändern berücksichtigen. Die äußerste linke Spalte hat den Index 0, der linke Nachbar einer Zelle in dieser Spalte hat also rein rechnerisch den Index 0 - 1 oder -1. Um stattdessen auf den Index der äußersten rechten Spalte zu kommen, nämlich 59, rechnen wir (0 - 1) % WIDTH. Da WIDTH den Wert 60 hat, wird dieser Ausdruck zu 59 ausgewertet. Diese Umlauftechnik mit dem Modulo-Operator funktioniert ebenso auch für die rechten, oberen und unteren Nachbarn.
# Zählt die lebenden Nachbarn
numNeighbors = 0
if currentCells[leftCoord][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben links lebt
if currentCells[x][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben lebt
if currentCells[rightCoord][aboveCoord] == '#':
numNeighbors += 1 # Nachbarzelle oben rechts lebt
if currentCells[leftCoord][y] == '#':
numNeighbors += 1 # Nachbarzelle links lebt
if currentCells[rightCoord][y] == '#':
numNeighbors += 1 # Nachbarzelle rechts lebt
if currentCells[leftCoord][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten links lebt
if currentCells[x][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten lebt
if currentCells[rightCoord][belowCoord] == '#':
numNeighbors += 1 # Nachbarzelle unten rechts lebt
Um zu bestimmen, ob die Zelle in nextCells[x][y] lebendig oder tot sein soll, müssen wir die lebenden Nachbarzellen von currentCells[x][y] zählen. Die vorstehende Folge von if-Anweisung prüft jeden der acht Nachbarn der Zelle und addiert für jeden lebenden 1 zu numNeighbors.
# Bestimmt Zellenzustand nach den Regeln
if currentCells[x][y] == '#' and (numNeighbors == 2 or
numNeighbors == 3):
# Lebende Zelle mit 2 oder 3 lebenden Nachbarn lebt weiter
nextCells[x][y] = '#'
elif currentCells[x][y] == ' ' and numNeighbors == 3:
# Tote Zelle mit 3 Nachbarn wird lebendig
nextCells[x][y] = '#'
else:
# Alle anderen Zellen sterben oder bleiben tot
nextCells[x][y] = ' '
time.sleep(1) # Legt eine Pause von 1 s ein, um Flackern zu verhindern
Damit wissen wir nun, wie viele lebende Nachbarn die Zelle in currentCells[x] [y] hat, sodass wir nextCells[x][y] dementsprechend auf '#' oder ' ' setzen können. Nachdem wir alle möglichen x- und y-Koordinaten durchlaufen haben, lassen wir das Programm durch den Aufruf von time.sleep(1) eine Pause von einer Sekunde einlegen. Danach geht die Programmausführung zum Anfang der Hauptschleife zurück, um mit dem nächsten Schritt weiterzumachen.
Je nach Ausgangsverteilung der Zellen können verschiedene bekannte Muster auftreten, für die Namen wie »Gleiter«, »Propeller« oder »schweres Raumschiff« geprägt wurden. Das in Abb. 4–8 gezeigte Gleitermuster bewegt sich alle vier Schritte in diagonaler Richtung fort. Um einen einzelnen Gleiter zu erzeugen, ersetzen Sie die folgende Zeile in conway.py:
if random.randint(0, 1) == 0:
durch diese Zeile:
if (x, y) in ((1, 0), (2, 1), (0, 2), (1, 2), (2, 2)):
Wenn Sie mehr über die faszinierenden Muster erfahren möchten, die Sie erzeugen können, suchen Sie im Web nach »Conways Spiel des Lebens« oder »Conway’s Game of Life«. Ähnliche Python-Programme wie das hier vorgestellte finden Sie auf https://github.com/asweigart/pythonstdiogames.