Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung | |||
ws1920:dokumentierter_code_finale_version [2020/03/19 12:23] richard.wonneberger |
ws1920:dokumentierter_code_finale_version [2020/03/26 22:50] (aktuell) Labo neuste Version |
||
---|---|---|---|
Zeile 4: | Zeile 4: | ||
* 2. die Ausführungsdatei | * 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. | + | 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: | + | gameoflife.py: |
<code> | <code> | ||
Zeile 14: | Zeile 14: | ||
import numpy as np | import numpy as np | ||
import matplotlib.pyplot as plt | import matplotlib.pyplot as plt | ||
- | from PIL import Image | ||
- | from math import floor, ceil | ||
import time | import time | ||
- | #in dieser Datei sind die benoetigten Funktionen definiert, um das Game of Life zu starten | + | class GameOfLife(object): |
- | #zum Verstaendnis sollte man mit dem Game Of Life vertraut sein aber die Grundlagen sind: 0=tot und 1=lebend | + | """ |
+ | 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 | ||
+ | """ | ||
- | #Zusammengefassung des Codes: | + | def __init__(self, zustand): |
- | #wenn Initialisiere() aufgerufen wird, legt der/die Benutzer/in die Feldgroesse (bis jetzt immer quadratisch) | + | """ |
- | #und die Anfangskonfiguration fest. hierbei ist aus 5 moeglichen Modi zu waehlen(s.u.) | + | Erzeugt ein neues GameOfLife-Objekt. |
- | #Um die GameOfLife Update Regel anzuwenden werden in Schritt() die Anzahl der Nachbarn einer jeden Zelle | + | Parameter: |
- | #und daraus der jeweils naechste Zustand(1 oder 0) berechnet | + | zustand: der initiale Zustand |
+ | """ | ||
+ | self.zustand = zustand | ||
+ | self.groesse = zustand.shape[0] | ||
+ | self.generiereTripleeinheitsmatrix() | ||
- | #Zum Aufsummieren der Nachbarn multipliziert man die vorher mit Tripleeinheitsmatrix(z,s) erstellte Tridiagonalmatrix von beiden Seiten | + | def generiereTripleeinheitsmatrix(self): |
- | #an die Matrix, in der die Zustaende der zellen gespeichert sind (einmal um die horizontalen und einmal um die vertikalen Partner zu addieren) | + | self.tem = np.zeros((self.groesse, self.groesse)) |
- | #als gleichung sieht das ungefaehr so aus:(tem steht hier fuer die tridiagonalmatrix) | + | for i in range(0, self.groesse): |
- | #anzahlnachbarn = tem*Zustand*tem | + | for j in range(0, self.groesse): |
- | #mit diesen zwei Matrixmultiplikationen kann man sehr recheneffektiv die 9 nachbarn(zelle selbst mitgezaehlt) | + | if i == j: |
- | #aller Zellen gleichzeitig aufsummieren und dabei haben wir auch noch direkt etwas ueber lineare Algebra gelernt! | + | 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 | ||
- | #der neue Zustand wird dann einfach abhaengig von der anzahl der nachbarn, entsprechend der regeln des game of life bestimmt | + | def schritt(self): |
+ | """ Führt einen Berechnungsschritt aus und gibt den neuen Zustand zurück. """ | ||
+ | anzahlNachbarn = self.berechneAnzahlNachbarn() | ||
- | #die 5 Konfigurationsmodi sind: | + | # berechne neuen Zustand |
- | #1.zufaellig: hier wird jede Zelle zufaellig einen wert 1 oder 0 bekommen | + | |
- | #2.alternierend: hier wird das Feld in abwechselnd 1- und 0-Spalten 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 | + | |
- | #da das Programm nur mit quadratischen Zustandsmatrizen funktioniert, wird bei Modi 3 und 4 die MachQuadrat(pixel) Funktion angewandt | + | # Ursprungszelle Tod + genau 3 lebende Nachbarn = lebend |
- | + | # Ursprungszelle Lebend + genau 1 lebenden Nachbarn = tot | |
- | def Tripleeinheitsmatrix(zeilen,spalten): | + | # Ursprungszelle Lebend + 2 o. 3 Nachbarn = lebend |
- | tem = np.zeros ((zeilen,spalten)) | + | # Ursprungszelle lebend + 4 oder mehr Nachbarn = tot |
- | for i in range(0, zeilen): | + | |
- | for j in range(0, spalten): | + | |
- | if i==j: | + | |
- | tem[(i,j)] = 1 | + | |
- | else: | + | |
- | tem[(i,j)] = 0 | + | |
- | + | ||
- | for i in range(0, zeilen): | + | |
- | for j in range(0, spalten): | + | |
- | if i==(j+1): | + | |
- | tem[(i,j)] = 1 | + | |
- | for i in range(0, zeilen): | + | |
- | for j in range(0, spalten): | + | |
- | if j==(i+1): | + | |
- | tem[(i,j)] = 1 | + | |
- | + | ||
- | tem[0,tem.shape[1]-1] = 1 | + | |
- | tem[tem.shape[1]-1,0] = 1 | + | |
- | + | ||
- | return tem | + | |
- | + | ||
- | def BerechneAnzahlNachbarn(zustand): | + | |
- | z = np.dot(tem, zustand) | + | |
- | k = np.dot(z, tem) | + | |
- | anzahlNachbarn = k - zustand | + | |
| | ||
- | return anzahlNachbarn | + | 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 | ||
+ | </code> | ||
- | def BerechneNeuenZustand(zustand, anzahlNachbarn): | + | |
- | #Ursprungszelle Tod + genau 3 lebende Nachbarn = lebend | + | Die Animationsdatei (anzeige.py): |
- | #Ursprungszelle Lebend + genau 1 lebenden Nachbarn = tot | + | |
- | #Ursprungszelle Lebend + 2 o. 3 Nachbarn = lebend | + | <code> |
- | #Ursprungszelle lebend + 4 oder mehr Nachbarn = tot | + | 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 | ||
+ | """ | ||
| | ||
- | for i in range(0, zeilen): | + | # unterschiedliche colourmaps (weil ein buntes Leben, lebenswerter ist) |
- | for j in range(0, spalten): | + | # diese werden zufaellig aufgerufen |
- | if zustand[(i,j)] == 1 and (anzahlNachbarn[(i,j)] in range(2) or anzahlNachbarn[(i,j)] in range(4,9)): | + | 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'] |
- | zustand[(i,j)]=0 | + | |
- | elif zustand[(i,j)] == 0 and anzahlNachbarn[(i,j)]==3: | + | |
- | zustand[(i,j)]=1 | + | |
- | + | ||
- | return zustand | + | |
- | def Schritt(): | + | def __init__(self, gol): |
- | global zustand | + | """ |
- | anzahlNachbarn = BerechneAnzahlNachbarn(zustand) | + | Erzeugt ein neues Anzeige-Objekt und beginnt die Animation. |
- | ergebnis = BerechneNeuenZustand(zustand, anzahlNachbarn) | + | |
- | return ergebnis | + | |
- | + | ||
- | def Wiederhole(t): | + | |
- | for i in range(t): | + | |
- | Schritt() | + | |
- | def MachQuadrat(pixel): | + | Parameter: |
- | breite = pixel.shape[0] | + | gol: das GamoOfLife-Objekt |
- | hoehe = pixel.shape[1] | + | """ |
- | size = max(breite, hoehe) | + | self.gol = gol |
- | links = floor((size - breite) / 2) | + | fig = plt.figure() |
- | rechts = ceil((size - breite) / 2) | + | self.canvas = fig.canvas |
- | oben = floor((size - hoehe) / 2) | + | plt.axis('off') |
- | unten = ceil((size - hoehe) / 2) | + | |
- | pad_width = ((links, rechts), (oben, unten)) | + | #so wird bei jedem ausfuehren eine zufaellige colormap gewaehlt um ein angenehmeres, vielseitiges Langzeiterlebnis zu kreieren |
- | pixel = np.pad(pixel, pad_width, mode='constant', constant_values=0) | + | self.im = plt.imshow(self.gol.zustand, animated=True, cmap=plt.get_cmap(Anzeige.colormaps[int(np.random.random()*len(Anzeige.colormaps))])) |
- | return pixel | + | self.zellAlter = np.zeros(self.gol.zustand.shape) |
+ | self.zeichne(self.gol.zustand) | ||
- | def Initialisiere(): | + | #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) | ||
- | global zustand, tem, zeilen, spalten, cmap | + | self.animationLaeuft = True |
+ | self.benutzerZeichnet = False | ||
- | modus = int(input('Gib den Modus 1 (Zufall), 2 (alternierend), 3 (Datei), 4 (Bild) oder 5 (Leer) ein: ')) | + | #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, | ||
- | if modus==1 or modus==2 or modus==5: | + | def zeichne(self, zustand): |
- | spalten = zeilen = int(input('Gib die Anzahl der Zeilen/Spalten ein: ')) | + | ''' 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() | ||
- | if modus==1: | + | #die vier folgenden Funktionen sind zum Interagieren mit dem Geschehen |
- | zustand = np.round(np.random.random((zeilen,spalten))).astype(int) | + | 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: | + | elif modus == 2: |
- | zustand=np.zeros((zeilen,spalten)) | + | # Alternierend |
- | for i in range(0,zeilen): | + | zustand = np.zeros((groesse, groesse)) |
- | for j in range (0,spalten): | + | for i in range(0, groesse): |
+ | for j in range (0, groesse): | ||
if j%2==0: | if j%2==0: | ||
zustand[(i,j)]=0 | zustand[(i,j)]=0 | ||
Zeile 137: | Zeile 240: | ||
zustand[(i,j)]=1 | zustand[(i,j)]=1 | ||
| | ||
- | if zustand[(int(zeilen/2),int(spalten/2))]==0: | + | if zustand[(int(groesse/2), int(groesse/2))] == 0: |
- | zustand[(int(zeilen/2),int(spalten/2))]=1 | + | zustand[(int(groesse/2), int(groesse/2))] = 1 |
- | zustand[(int((zeilen/2)+1),int(spalten/2))]=1 | + | zustand[(int((groesse/2)+1), int(groesse/2))] = 1 |
| | ||
else: | else: | ||
- | zustand[(int(zeilen/2),int(spalten/2))]=0 | + | zustand[(int(groesse/2), int(groesse/2))] = 0 |
- | zustand[(int((zeilen/2)+1),int(spalten/2))]=0 | + | zustand[(int((groesse/2)+1), int(groesse/2))] = 0 |
| | ||
- | elif modus==3: | + | elif modus == 3: |
- | DateiName = input('was willste oeffnen? ') | + | # Datei lesen |
- | Datei = open(DateiName) | + | dateiName = input('Was willste oeffnen? ') |
+ | datei = open(dateiName) | ||
| | ||
- | DateiAlsListe = [] | + | dateiAlsListe = [] |
- | for line in Datei: | + | for line in datei: |
- | DateiAlsListe.append([int(c) for c in list(line.rstrip())]) | + | dateiAlsListe.append([int(c) for c in list(line.rstrip())]) |
- | DateiAlsMatrix = np.array(DateiAlsListe) | + | dateiAlsMatrix = np.array(dateiAlsListe) |
| | ||
- | zustand = MachQuadrat(DateiAlsMatrix) | + | zustand = machQuadrat(dateiAlsMatrix) |
- | zeilen = spalten = zustand.shape[0] | + | |
| | ||
- | elif modus==4: | + | elif modus == 4: |
+ | # Bild lesen | ||
dateiname = input('Gib den Bildpfad ein: ') | dateiname = input('Gib den Bildpfad ein: ') | ||
bild = Image.open(dateiname) | bild = Image.open(dateiname) | ||
Zeile 165: | Zeile 269: | ||
pixel = np.array(bild) | pixel = np.array(bild) | ||
- | zustand = MachQuadrat(pixel) | + | zustand = machQuadrat(pixel) |
- | zeilen = spalten = zustand.shape[0] | + | |
| | ||
- | elif modus==5: | + | # erzeuge GameOfLife-Objekt |
- | zustand = np.zeros((zeilen,spalten)).astype(int) | + | gol = GameOfLife(zustand) |
- | tem = Tripleeinheitsmatrix(zeilen, spalten) | + | # initialisiere Anzeige |
+ | Anzeige(gol) | ||
- | </code> | + | 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) | ||
- | Die Animationsdatei: | + | pad_width = ((links, rechts), (oben, unten)) |
+ | pixel = np.pad(pixel, pad_width, mode='constant', constant_values=0) | ||
+ | return pixel | ||
- | <code> | + | main() |
- | import GameOfLife as gol | + | |
- | import numpy as np | + | |
- | from matplotlib import pyplot as plt | + | |
- | from matplotlib import animation | + | |
- | + | ||
- | # in dieser Datei, wird die Animation der 'Game of Life' - Schritte ausgeführt | + | |
- | # die vorletzte Zeile mit Funcanimation bildet das Grundgeruest, der Rest des Codes ist groesstenteils Vorbereitung | + | |
- | # und die Funktionen zum Interagieren | + | |
- | + | ||
- | + | ||
- | # 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 animate(e): | + | |
- | ''' die Funktion, die von Funcanimation ausgefuehrt wird | + | |
- | + | ||
- | wie gol.Schritt funktioniert ist in der GameOfLife datei beschrieben ''' | + | |
- | gol.Schritt() | + | |
- | draw(gol.zustand) | + | |
- | return im, | + | |
- | + | ||
- | gol.Initialisiere() | + | |
- | fig = plt.figure() | + | |
- | + | ||
- | def draw(zustand): | + | |
- | ''' lebende Zellen aendern ihre Farbe, je laenger sie leben ''' | + | |
- | + | ||
- | global cmap #enthaelt Werte ueber die 'Lebensdauer' einer Zelle | + | |
- | cmap = cmap * zustand + zustand | + | |
- | im.set_array(np.ones(cmap.shape) - np.power(0.5, cmap)) | + | |
- | fig.canvas.draw() | + | |
- | + | ||
- | zustand_neu = None | + | |
- | anim_running = True | + | |
- | drawing = False | + | |
- | + | ||
- | #die vier folgenden Funktionen sind zum Interagieren mit dem Geschehen | + | |
- | def onPress(event): | + | |
- | ''' zum Pausieren mit der Leertaste ''' | + | |
- | global anim_running | + | |
- | if event.key==' ': | + | |
- | if anim_running: | + | |
- | anim.event_source.stop() | + | |
- | anim_running = False | + | |
- | else: | + | |
- | anim.event_source.start() | + | |
- | anim_running = True | + | |
- | + | ||
- | + | ||
- | def onClick(event): | + | |
- | ''' mit dem Cursor kann man den Wert einer Zelle per Klick aendern | + | |
- | + | ||
- | von lebend zu tot, sowie andersherum ''' | + | |
- | global cmap, zustand_neu, drawing | + | |
- | if event.button == 1 and event.xdata != None and event.ydata != None: | + | |
- | drawing = True | + | |
- | anim.event_source.stop() | + | |
- | zustand_neu = np.copy(gol.zustand) | + | |
- | pos_x = int(np.round(event.xdata)) | + | |
- | pos_y = int(np.round(event.ydata)) | + | |
- | zustand_neu[pos_y, pos_x] = 1 - gol.zustand[pos_y, pos_x] | + | |
- | cmap = np.zeros(zustand_neu.shape) | + | |
- | draw(zustand_neu) | + | |
- | + | ||
- | def onMove(event): | + | |
- | ''' mit dem Cursor kann man den Wert mehrerer Zellen per Klick aendern, indem man die Maustaste gedrueckt haelt | + | |
- | die Animation wird hierfuer pausiert''' | + | |
- | global zustand_neu, cmap | + | |
- | if event.button == 1 and drawing and event.xdata != None and event.ydata != None: | + | |
- | pos_x = int(np.round(event.xdata)) | + | |
- | pos_y = int(np.round(event.ydata)) | + | |
- | zustand_neu[pos_y, pos_x] = 1 - gol.zustand[pos_y, pos_x] | + | |
- | cmap = np.zeros(zustand_neu.shape) | + | |
- | draw(zustand_neu) | + | |
- | + | ||
- | def onClickEnd(event): | + | |
- | ''' beim loslassen der Maustaste werden die geänderten Werte an die Animation gegeben und diese wird fortgesetzt ''' | + | |
- | global zustand_neu, cmap, drawing | + | |
- | if event.button == 1 and drawing: | + | |
- | gol.zustand = zustand_neu | + | |
- | cmap = np.zeros(zustand_neu.shape) | + | |
- | draw(zustand_neu) | + | |
- | drawing = False | + | |
- | if anim_running: | + | |
- | anim.event_source.start() | + | |
- | + | ||
- | plt.axis('off') | + | |
- | + | ||
- | #so wird bei jedem ausfuehren eine zufaellige colormap gewaehlt um ein angenehmeres, vielseitiges Langzeiterlebnis zu kreieren | + | |
- | im = plt.imshow(gol.zustand, animated=True, cmap=plt.get_cmap(colormaps[int(np.random.random()*len(colormaps))])) | + | |
- | cmap = np.zeros(gol.zustand.shape) | + | |
- | draw(gol.zustand) | + | |
- | + | ||
- | #Verbinden, der interaktionsevents mit dem figure, in dem die animation laeuft | + | |
- | fig.canvas.mpl_connect('key_press_event', onPress) | + | |
- | fig.canvas.mpl_connect('button_press_event', onClick) | + | |
- | fig.canvas.mpl_connect('button_release_event', onClickEnd) | + | |
- | fig.canvas.mpl_connect('motion_notify_event', onMove) | + | |
- | + | ||
- | #die tatsaechliche Ausfuehrung: | + | |
- | anim = animation.FuncAnimation(fig, animate, interval=200) | + | |
- | plt.show() | + | |
</code> | </code> | ||
- | |||
- | |||
- |