Das Programm besteht aus zwei Dateien:
* 1. die GameOfLife.py Datei
* 2. die Ausführungsdatei
In der ersten sind das Game of life und seine Regeln implementiert (sie sollte gameoflife.py heißen) und die zweite greift darauf zu und erstellt eine Animation mit der man interagieren kann.
gameoflife.py:
import random
import numpy as np
import matplotlib.pyplot as plt
import time
class GameOfLife(object):
"""
Diese Klasse beschreibt einen Zustand eines Game of Lifes und stellt Funktionen dazu bereit.
Attribute:
zustand: der aktuelle Zustand des Game of Life
groesse: die Seitenlänge des Spielfeldes
tem: die Tripleeinheitsmatrix, die zur Berechnung benötigt wird
"""
def __init__(self, zustand):
"""
Erzeugt ein neues GameOfLife-Objekt.
Parameter:
zustand: der initiale Zustand
"""
self.zustand = zustand
self.groesse = zustand.shape[0]
self.generiereTripleeinheitsmatrix()
def generiereTripleeinheitsmatrix(self):
self.tem = np.zeros((self.groesse, self.groesse))
for i in range(0, self.groesse):
for j in range(0, self.groesse):
if i == j:
self.tem[(i,j)] = 1
else:
self.tem[(i,j)] = 0
for i in range(0, self.groesse):
for j in range(0, self.groesse):
if i == (j + 1):
self.tem[(i,j)] = 1
for i in range(0, self.groesse):
for j in range(0, self.groesse):
if j == (i + 1):
self.tem[(i,j)] = 1
self.tem[0, self.tem.shape[1] - 1] = 1
self.tem[self.tem.shape[1] - 1, 0] = 1
def berechneAnzahlNachbarn(self):
# Zum Aufsummieren der Nachbarn multipliziert man die vorher mit Tripleeinheitsmatrix(z,s) erstellte Tridiagonalmatrix von beiden Seiten
# an die Matrix, in der die Zustaende der zellen gespeichert sind (einmal um die horizontalen und einmal um die vertikalen Partner zu addieren)
# als gleichung sieht das ungefaehr so aus:(tem steht hier fuer die tridiagonalmatrix)
# anzahlnachbarn = tem*Zustand*tem
# mit diesen zwei Matrixmultiplikationen kann man sehr recheneffektiv die 9 nachbarn(zelle selbst mitgezaehlt)
# aller Zellen gleichzeitig aufsummieren und dabei haben wir auch noch direkt etwas ueber lineare Algebra gelernt!
z = np.dot(self.tem, self.zustand)
k = np.dot(z, self.tem)
anzahlNachbarn = k - self.zustand
return anzahlNachbarn
def schritt(self):
""" Führt einen Berechnungsschritt aus und gibt den neuen Zustand zurück. """
anzahlNachbarn = self.berechneAnzahlNachbarn()
# berechne neuen Zustand
# Ursprungszelle Tod + genau 3 lebende Nachbarn = lebend
# Ursprungszelle Lebend + genau 1 lebenden Nachbarn = tot
# Ursprungszelle Lebend + 2 o. 3 Nachbarn = lebend
# Ursprungszelle lebend + 4 oder mehr Nachbarn = tot
for i in range(0, self.groesse):
for j in range(0, self.groesse):
if self.zustand[(i,j)] == 1 and (anzahlNachbarn[(i,j)] in range(2) or anzahlNachbarn[(i,j)] in range(4,9)):
self.zustand[(i,j)] = 0
elif self.zustand[(i,j)] == 0 and anzahlNachbarn[(i,j)] == 3:
self.zustand[(i,j)] = 1
return self.zustand
Die Animationsdatei (anzeige.py):
from gameoflife import GameOfLife
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.pyplot as plt
from PIL import Image
from math import floor, ceil
class Anzeige(object):
"""
Diese Klasse animiert ein GameOfLife-Objekt mithilfe der matplotlib.
Attribute:
gol: das GameOfLife-Objekt, das animiert wird
canvas: der matplotlib-Canvas, auf den gezeichnet wird
im: die aktuell angezeigte Bitmap
zellAlter: speichert für jede Zelle, für wie viele Generationen sie schon lebendig ist
animationLaeuft: gibt an, ob die Animation läuft (also nicht pausiert ist)
benutzerZeichnet: gibt an, ob der Benutzer gerade auf den Canvas zeichnet
anim: das matplotlib-Animationsobjekt
"""
# unterschiedliche colourmaps (weil ein buntes Leben, lebenswerter ist)
# diese werden zufaellig aufgerufen
colormaps = ['Accent', 'Accent_r', 'Blues', 'Blues_r', 'BrBG', 'BrBG_r', 'BuGn', 'BuGn_r', 'BuPu', 'BuPu_r', 'CMRmap', 'CMRmap_r', 'Dark2', 'Dark2_r', 'GnBu', 'GnBu_r', 'Greens', 'Greens_r', 'Greys', 'Greys_r', 'OrRd', 'OrRd_r', 'Oranges', 'Oranges_r', 'PRGn', 'PRGn_r', 'Paired', 'Paired_r', 'Pastel1', 'Pastel1_r', 'Pastel2', 'Pastel2_r', 'PiYG', 'PiYG_r', 'PuBu', 'PuBuGn', 'PuBuGn_r', 'PuBu_r', 'PuOr', 'PuOr_r', 'PuRd', 'PuRd_r', 'Purples', 'Purples_r', 'RdBu', 'RdBu_r', 'RdGy', 'RdGy_r', 'RdPu', 'RdPu_r', 'RdYlBu', 'RdYlBu_r', 'RdYlGn', 'RdYlGn_r', 'Reds', 'Reds_r', 'Set1', 'Set1_r', 'Set2', 'Set2_r', 'Set3', 'Set3_r', 'Spectral', 'Spectral_r', 'Wistia', 'Wistia_r', 'YlGn', 'YlGnBu', 'YlGnBu_r', 'YlGn_r', 'YlOrBr', 'YlOrBr_r', 'YlOrRd', 'YlOrRd_r', 'afmhot', 'afmhot_r', 'autumn', 'autumn_r', 'binary', 'binary_r', 'bone', 'bone_r', 'brg', 'brg_r', 'bwr', 'bwr_r', 'cividis', 'cividis_r', 'cool', 'cool_r', 'coolwarm', 'coolwarm_r', 'copper', 'copper_r', 'cubehelix', 'cubehelix_r', 'flag', 'flag_r', 'gist_earth', 'gist_earth_r', 'gist_gray', 'gist_gray_r', 'gist_heat', 'gist_heat_r', 'gist_ncar', 'gist_ncar_r', 'gist_rainbow', 'gist_rainbow_r', 'gist_stern', 'gist_stern_r', 'gist_yarg', 'gist_yarg_r', 'gnuplot', 'gnuplot2', 'gnuplot2_r', 'gnuplot_r', 'gray', 'gray_r', 'hot', 'hot_r', 'hsv', 'hsv_r', 'inferno', 'inferno_r', 'jet', 'jet_r', 'magma', 'magma_r', 'nipy_spectral', 'nipy_spectral_r', 'ocean', 'ocean_r', 'pink', 'pink_r', 'plasma', 'plasma_r', 'prism', 'prism_r', 'rainbow', 'rainbow_r', 'seismic', 'seismic_r', 'spring', 'spring_r', 'summer', 'summer_r', 'tab10', 'tab10_r', 'tab20', 'tab20_r', 'tab20b', 'tab20b_r', 'tab20c', 'tab20c_r', 'terrain', 'terrain_r', 'twilight', 'twilight_r', 'twilight_shifted', 'twilight_shifted_r', 'viridis', 'viridis_r', 'winter', 'winter_r']
def __init__(self, gol):
"""
Erzeugt ein neues Anzeige-Objekt und beginnt die Animation.
Parameter:
gol: das GamoOfLife-Objekt
"""
self.gol = gol
fig = plt.figure()
self.canvas = fig.canvas
plt.axis('off')
#so wird bei jedem ausfuehren eine zufaellige colormap gewaehlt um ein angenehmeres, vielseitiges Langzeiterlebnis zu kreieren
self.im = plt.imshow(self.gol.zustand, animated=True, cmap=plt.get_cmap(Anzeige.colormaps[int(np.random.random()*len(Anzeige.colormaps))]))
self.zellAlter = np.zeros(self.gol.zustand.shape)
self.zeichne(self.gol.zustand)
#Verbinden, der interaktionsevents mit dem figure, in dem die animation laeuft
self.canvas.mpl_connect('key_press_event', self.onPress)
self.canvas.mpl_connect('button_press_event', self.onClick)
self.canvas.mpl_connect('button_release_event', self.onClickEnd)
self.canvas.mpl_connect('motion_notify_event', self.onMove)
self.animationLaeuft = True
self.benutzerZeichnet = False
#die tatsaechliche Ausfuehrung:
self.anim = animation.FuncAnimation(fig, self.animationsschritt, interval=200)
plt.show()
def animationsschritt(self, e):
''' Führt einen Animationsschritt durch. '''
self.gol.schritt()
self.zeichne(self.gol.zustand)
return self.im,
def zeichne(self, zustand):
''' Zeichnet den angegebenen Zustand auf den matplotlib-Canvas. '''
# enthaelt Werte ueber die 'Lebensdauer' einer Zelle
self.zellAlter = self.zellAlter * zustand + zustand
# lebende Zellen aendern ihre Farbe, je laenger sie leben
self.im.set_array(np.ones(self.zellAlter.shape) - np.power(0.5, self.zellAlter))
self.canvas.draw()
#die vier folgenden Funktionen sind zum Interagieren mit dem Geschehen
def onPress(self, event):
# zum Pausieren mit der Leertaste
if event.key == ' ':
if self.animationLaeuft:
self.anim.event_source.stop()
self.animationLaeuft = False
else:
self.anim.event_source.start()
self.animationLaeuft = True
def onClick(self, event):
# mit dem Cursor kann man den Wert einer Zelle per Klick aendern von lebend zu tot, sowie andersherum
if event.button == 1 and event.xdata != None and event.ydata != None:
self.benutzerZeichnet = True
self.anim.event_source.stop()
self.zustandNeu = np.copy(self.gol.zustand)
pos_x = int(np.round(event.xdata))
pos_y = int(np.round(event.ydata))
self.zustandNeu[pos_y, pos_x] = 1 - self.gol.zustand[pos_y, pos_x]
self.zellAlter = np.zeros(self.zustandNeu.shape)
self.zeichne(self.zustandNeu)
def onMove(self, event):
# mit dem Cursor kann man den Wert mehrerer Zellen per Klick aendern, indem man die Maustaste gedrueckt haelt
# die Animation wird hierfuer pausiert
if event.button == 1 and self.benutzerZeichnet and event.xdata != None and event.ydata != None:
pos_x = int(np.round(event.xdata))
pos_y = int(np.round(event.ydata))
self.zustandNeu[pos_y, pos_x] = 1 - self.gol.zustand[pos_y, pos_x]
self.zellAlter = np.zeros(self.zustandNeu.shape)
self.zeichne(self.zustandNeu)
def onClickEnd(self, event):
# beim loslassen der Maustaste werden die geänderten Werte an die Animation gegeben und diese wird fortgesetzt
if event.button == 1 and self.benutzerZeichnet:
self.gol.zustand = self.zustandNeu
self.zellAlter = np.zeros(self.zustandNeu.shape)
self.zeichne(self.zustandNeu)
self.benutzerZeichnet = False
if self.animationLaeuft:
self.anim.event_source.start()
###### Programmeinstieg #######
def main():
# Erzeuge ein GameOfLife-Objekt anhand der Benutzereingaben
# die 5 Konfigurationsmodi sind:
# 1.zufaellig: hier wird jede Zelle zufaellig einen wert 1 oder 0 bekommen
# 2.alternierend: hier wird das Feld in abwechselnd 1- und 0-groesse eingeteilt, mit einem kleinen Fehler in der Mitte
# 3.Datei: hier kann direkt eine Konfiguration eingelesen werden. z.B. um eine gleiterkanone oder Aehnliches einzulesen
# 4.Bild: eine Bilddatei kann eingelesen und in eine annaehernd erkennbare anfangskonfiguration umgewandelt werden (da der kontrast zu schwarzweiss und die groesse geaendert wird)
# 5.Leer: hier wird das Feld mit Nullen gefuellt
modus = int(input('Gib den Modus 1 (Zufall), 2 (alternierend), 3 (Datei) oder 4 (Bild) ein: '))
if modus == 1 or modus == 2 or modus == 5:
groesse = int(input('Gib die Anzahl der Zeilen/Spalten ein: '))
if modus == 1:
# Zufall
zustand = np.round(np.random.random((groesse, groesse))).astype(int)
elif modus == 2:
# Alternierend
zustand = np.zeros((groesse, groesse))
for i in range(0, groesse):
for j in range (0, groesse):
if j%2==0:
zustand[(i,j)]=0
else:
zustand[(i,j)]=1
if zustand[(int(groesse/2), int(groesse/2))] == 0:
zustand[(int(groesse/2), int(groesse/2))] = 1
zustand[(int((groesse/2)+1), int(groesse/2))] = 1
else:
zustand[(int(groesse/2), int(groesse/2))] = 0
zustand[(int((groesse/2)+1), int(groesse/2))] = 0
elif modus == 3:
# Datei lesen
dateiName = input('Was willste oeffnen? ')
datei = open(dateiName)
dateiAlsListe = []
for line in datei:
dateiAlsListe.append([int(c) for c in list(line.rstrip())])
dateiAlsMatrix = np.array(dateiAlsListe)
zustand = machQuadrat(dateiAlsMatrix)
elif modus == 4:
# Bild lesen
dateiname = input('Gib den Bildpfad ein: ')
bild = Image.open(dateiname)
bild = bild.convert('1')
bild.thumbnail((500,500))
pixel = np.array(bild)
zustand = machQuadrat(pixel)
# erzeuge GameOfLife-Objekt
gol = GameOfLife(zustand)
# initialisiere Anzeige
Anzeige(gol)
def machQuadrat(pixel):
""" Macht das angegebene zweidimensionale Numpy-Array quadratisch, indem es die Ränder mit Nullen auffüllt. """
breite = pixel.shape[0]
hoehe = pixel.shape[1]
size = max(breite, hoehe)
links = floor((size - breite) / 2)
rechts = ceil((size - breite) / 2)
oben = floor((size - hoehe) / 2)
unten = ceil((size - hoehe) / 2)
pad_width = ((links, rechts), (oben, unten))
pixel = np.pad(pixel, pad_width, mode='constant', constant_values=0)
return pixel
main()