Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1920:dokumentierter_code_finale_version

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()
ws1920/dokumentierter_code_finale_version.txt · Zuletzt geändert: 2020/03/26 22:50 von Labo