Читать книгу 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.

Routineaufgaben mit Python automatisieren

Подняться наверх