Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1920:dokumentierter_code_finale_version

Dies ist eine alte Version des Dokuments!


Das Programm besteht aus zwei Dateien

  • 1. die Klassendatei GameOfLife.py
  • 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
from PIL import Image
from math import floor, ceil
import time

#in dieser Datei sind die benoetigten Funktionen definiert, um das Game of Life zu starten
#zum Verstaendnis sollte man mit dem Game Of Life vertraut sein aber die Grundlagen sind: 0=tot und 1=lebend


#Zusammengefassung des Codes:
#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.)

#Um die GameOfLife Update Regel anzuwenden werden in Schritt() die Anzahl der Nachbarn einer jeden Zelle 
#und daraus der jeweils naechste Zustand(1 oder 0) berechnet

#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!

#der neue Zustand wird dann einfach abhaengig von der anzahl der nachbarn, entsprechend der regeln des game of life bestimmt

#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-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

def Tripleeinheitsmatrix(zeilen,spalten):
    tem = np.zeros ((zeilen,spalten))
    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

def BerechneNeuenZustand(zustand, anzahlNachbarn):
    #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, zeilen):
        for j in range(0, spalten):
            if zustand[(i,j)] == 1 and (anzahlNachbarn[(i,j)] in range(2) or anzahlNachbarn[(i,j)] in range(4,9)):
                zustand[(i,j)]=0
            elif zustand[(i,j)] == 0 and anzahlNachbarn[(i,j)]==3:
                zustand[(i,j)]=1
                    
    return zustand

def Schritt():
    global zustand
    anzahlNachbarn = BerechneAnzahlNachbarn(zustand)
    ergebnis = BerechneNeuenZustand(zustand, anzahlNachbarn)
    return ergebnis
                
def Wiederhole(t):
    for i in range(t):
        Schritt()

def MachQuadrat(pixel):
    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

def Initialisiere():

    global zustand, tem, zeilen, spalten, cmap

    modus =  int(input('Gib den Modus 1 (Zufall), 2 (alternierend), 3 (Datei), 4 (Bild) oder 5 (Leer) ein:  '))

    if modus==1 or modus==2 or modus==5:
        spalten = zeilen = int(input('Gib die Anzahl der Zeilen/Spalten ein: '))

    if modus==1:
        zustand = np.round(np.random.random((zeilen,spalten))).astype(int)
    
    elif modus==2:
        zustand=np.zeros((zeilen,spalten))
        for i in range(0,zeilen):
            for j in range (0,spalten):
                if j%2==0:
                    zustand[(i,j)]=0
                else:
                    zustand[(i,j)]=1
    
        if zustand[(int(zeilen/2),int(spalten/2))]==0:
            zustand[(int(zeilen/2),int(spalten/2))]=1
            zustand[(int((zeilen/2)+1),int(spalten/2))]=1
            
        else:
            zustand[(int(zeilen/2),int(spalten/2))]=0
            zustand[(int((zeilen/2)+1),int(spalten/2))]=0
    
    elif modus==3:
        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)
        zeilen = spalten = zustand.shape[0]
        
    elif modus==4:
        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)
        zeilen = spalten = zustand.shape[0]
    
    elif modus==5:
        zustand = np.zeros((zeilen,spalten)).astype(int)

    tem = Tripleeinheitsmatrix(zeilen, spalten)

Die Animationsdatei:

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 Klassendatei 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()
ws1920/dokumentierter_code_finale_version.1584549071.txt.gz · Zuletzt geändert: 2020/03/18 17:31 von hannariegel