Game of Life

Hier werden einzelne Projekte mit MicroPython vorgestellt
Antworten
Heinrichs
Beiträge: 173
Registriert: Do 21. Okt 2010, 18:31

Game of Life

Beitrag von Heinrichs » Sa 27. Nov 2021, 21:18

Game of Life wurde von dem Mathematiker John Conway erfunden; 1970 wurde es weltweit bekannt durch einen Artikel im Scientific American. Das "Spiel" besteht aus einer Anzahl von Zellen - angeordnet in einem tabellenartigen Schema wie in der folgenden Abbildung. Diese Zellen können leben (in der Abbildung durch ein x angedeutet), sterben oder in ihrer Nachbarschaft neue lebende Zellen erzeugen.

Game_of_life_Beispiel.jpg
Abb. 1: Game-of-Life-Tabelle
Game_of_life_Beispiel.jpg (5.96 KiB) 3606 mal betrachtet

Wie die davon gebildete Population sich schrittweise entwickelt, ist durch folgende Regeln festgelegt:
  1. Eine Zelle stirbt (an Vereinsamung), wenn sie nur 0 oder 1 Nachbarn hat; in unserem Beispiel ist das für D1 und C3 der Fall.
  2. Eine Zelle stirbt (an Überpopulation), wenn sie 4 oder mehr Nachbarn hat; in unserem Beispiel ist das für B2 der Fall.
  3. Eine Zelle bleibt am Leben, wenn sie 3 oder 4 Nachbarn hat; in unserem Beispiel ist das für A1, B1 und A2 der Fall.
  4. Eine (unbelebte) Zelle wird belebt, wenn sie von genau drei lebenden Zellen umgeben ist; in unserem Beispiel ist das für C1 und B3 der Fall.
Ausgehend von einer beliebigen Startpopulation wird nach diesen Regeln eine neue Population bestimmt; aus dieser wird ihrerseits eine nächste Population erzeugt u.s.w. Je nach Startpopulation können sich folgende Entwicklungen ergeben:
  1. Es bildet sich eine stabile Population aus.
  2. Es bildet sich eine oszillierende Population aus, d. h. ab einem bestimmten Punkt verläuft die Entwicklung zyklisch.
  3. Die gesamte Population stirbt aus.
  4. Die Population entwickelt sich (ohne Periode) weiter; dabei kann die Anzahl der lebenden Zellen unbegrenzt ansteigen.
  5. Interessant sind die sogenannten Spaceships; hierbei handelt es sich um oszillierende Populationen, wobei sich die Population nach einer Periode aber an einer anderen Position befinden. Sie gleitet quasi durch den Raum.
Weitere Informationen findet man unter https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life


Mit Hilfe unseres TTGO-Moduls können wir solche Entwicklungsprozesse studieren. Hierbei stellen wir eine Population durch eine Matrix dar: Das Matrix-Element einer lebenden Zelle wird durch den Wert 1 gekennzeichnet, andernfalls ist der Wert 0. (Wie man bei Micropython mit Matrizen umgeht, habe ich in dem Beitrag https://www.forum.g-heinrichs.de/viewto ... f=18&t=159 ausgeführt.)

Mit Hilfe der oben angegebenen Regeln wird aus der "alten" Matrix eine "neue Matrix" bestimmt und anschließend auf dem Display dargestellt; dabei werden nur lebende Zellen angezeigt, und zwar durch ein kleines Quadrat.

Diese "neue" Matrix wird nun in die "alte" Matrix kopiert und aus dieser wird wieder eine "neue" Matrix bestimmt...

In unserem Programm werden verschiedene Startpopulationen zur Verfügung gestellt:

Code: Alles auswählen

# zur Auswahl:
# startmatrix_r()
# startmatrix_block()
# startmatrix_glider()
# startmatrix_glider_blinker1()
# startmatrix_glider_blinker2()
# startmatrix_glider_blinker3()
# startmatrix_glider_blinker4()
# startmatrix_glider_blinker5()
# startmatrix_diamant()
# startmatrix_balken_am_rand()
# startmatrix_balken_im_zentrum()
Wählen Sie in dem folgenden Programm die gewünschte Population aus, indem Sie das Kommentarzeichen (#) bei der zugehörigen Startpopulation in dem Programm-Abschnitt "zur Auswahl" entfernen. Natürlich können Sie das Programm auch durch eigene Startmatrizen ergänzen.

Code: Alles auswählen

# game_of_life_0_4.py
# Version 0.4 (Experimental-Version)

# etwa 1 Dutzend Start-Populationen (Startmatrizen)
# Abbrechen der Simulation mit Taster A (GPIO0)

# Quellen:
# https://playgameoflife.com/
# https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life


from time import sleep
from machine import Pin, SPI
import st7789

tasterA = Pin(0, Pin.IN, Pin.PULL_UP)

spi = SPI(2, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19))
display = st7789.ST7789(spi, 135, 240,  reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3)
# Landscape

display.init()
display.fill(0)

def zeige_matrix(m):
    size = 4
    display.fill(0)
    display.rect(0,0,size*w,size*h,st7789.GREEN)
    for i  in range(h):
        for j in range(w):
            if m[i][j] == 1:
                display.rect(size*j, size*i, size, size, st7789.WHITE)
                
def kopiere_neu_zu_alt(): # neue_matrix = alte_matrix funktioniert nicht, weil hier nur Pointer neu zugeordnet werden
    for i  in range(h):
        for j in range(w):
            alte_matrix[i][j] = neue_matrix[i][j]
            
def erzeuge_naechste_generation():
    for i in range(h):
        for j in range(w):
            if alte_matrix[i][j] == 1: # Zelle besetzt
                n = anzahl_der_nachbarn(alte_matrix, i, j)
                if n <= 1: # 0; 1, vgl. anzahl_der_nachbarn  
                    neue_matrix[i][j] = 0
                else:
                    if n <= 3: # 2; 3
                        neue_matrix[i][j] = 1
                    else: # 4; 5; ...; 8
                        neue_matrix[i][j] = 0
            else: # Zelle nicht besetzt
                 n = anzahl_der_nachbarn(alte_matrix, i, j)
                 if n == 3 :
                     neue_matrix[i][j] = 1
            
def anzahl_der_nachbarn(m, i, j):
    n = 0 # Zellen am Rand werden sterben
    if i > 0 and j > 0 and i < (h-1) and j < (w-1): # Zellen nicht am Rand
        n = m[i][j-1] + m[i-1][j-1] + m[i-1][j] + m[i-1][j+1] + m[i][j+1] + m[i+1][j+1] + m[i+1][j] + m[i+1][j-1]
    return n

################## verschiedene Startmatrizen #############################      

def startmatrix_r():
    # Startmatrix ('r'):
    alte_matrix[19][25] = 1
    alte_matrix[19][26] = 1
    alte_matrix[20][25] = 1
    alte_matrix[20][24] = 1
    alte_matrix[21][25] = 1
    
def startmatrix_block():
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1
    alte_matrix[16][30] = 1
    alte_matrix[16][31] = 1
    
def startmatrix_glider():
    alte_matrix[2][2] = 1
    alte_matrix[3][3] = 1
    alte_matrix[3][4] = 1
    alte_matrix[4][2] = 1
    alte_matrix[4][3] = 1
    
def startmatrix_blinker():
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1
    
def startmatrix_glider_blinker1():
    alte_matrix[2][22] = 1
    alte_matrix[3][23] = 1
    alte_matrix[3][24] = 1
    alte_matrix[4][22] = 1
    alte_matrix[4][23] = 1
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1
    
def startmatrix_glider_blinker2():
    alte_matrix[2][20] = 1
    alte_matrix[3][21] = 1
    alte_matrix[3][22] = 1
    alte_matrix[4][20] = 1
    alte_matrix[4][21] = 1
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1

def startmatrix_glider_blinker3():
    alte_matrix[2][18] = 1
    alte_matrix[3][19] = 1
    alte_matrix[3][20] = 1
    alte_matrix[4][18] = 1
    alte_matrix[4][19] = 1
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1
    
def startmatrix_glider_blinker4():
    alte_matrix[2][16] = 1
    alte_matrix[3][17] = 1
    alte_matrix[3][18] = 1
    alte_matrix[4][16] = 1
    alte_matrix[4][17] = 1
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1
    
def startmatrix_glider_blinker5():
    alte_matrix[2][13] = 1
    alte_matrix[3][14] = 1
    alte_matrix[3][15] = 1
    alte_matrix[4][13] = 1
    alte_matrix[4][14] = 1
    alte_matrix[15][29] = 1
    alte_matrix[15][30] = 1
    alte_matrix[15][31] = 1

def startmatrix_diamant():
    for i in range(4):
        alte_matrix[11][i+28] = 1
    for i in range(8):
        alte_matrix[13][i+26] = 1        
    for i in range(12):
        alte_matrix[15][i+24] = 1
    for i in range(8):
        alte_matrix[17][i+26] = 1
    for i in range(4):
        alte_matrix[19][i+28] = 1
        
def startmatrix_balken_am_rand():
    for i in range(1, 30):
        alte_matrix[15][i] = 1
        alte_matrix[16][i] = 1
        
def startmatrix_balken_im_zentrum():
    for i in range(1, 30):
        alte_matrix[15][i+15] = 1
        alte_matrix[16][i+15] = 1
        
################################# Hauptprogramm #################################

zeitintervall = 0.3

# Erzeuge 2 Matrizen
w, h = 60, 33
alte_matrix = [[0 for x in range(w)] for y in range(h)]
# [0 for x in range(w)] erzeugt Liste mit w 0-Elementen (1 Zeile mit w Elementen)
# [[...] for y in range(h)] erzeugt Liste mit h [...]-Elementen
# beachte: alte_matrix[i][j] bezieht sich auf das Matrixelement in Zeile i | Spalte j
neue_matrix = [[0 for x in range(w)] for y in range(h)]

# zur Auswahl:
# startmatrix_r()
# startmatrix_block()
# startmatrix_glider()
# startmatrix_glider_blinker1()
# startmatrix_glider_blinker2()
# startmatrix_glider_blinker3()
# startmatrix_glider_blinker4()
# startmatrix_glider_blinker5()
# startmatrix_diamant()
# startmatrix_balken_am_rand()
startmatrix_balken_im_zentrum()

# Start-Generation:
zeige_matrix(alte_matrix)
sleep(3) # Startgeneration 3 s anzeigen

while tasterA.value(): # zum Beenden der Simulation tasterA mindestens zeitinterval Sekunden gedrückt halten
    sleep(zeitintervall)
    erzeuge_naechste_generation()  
    display.fill(0)
    zeige_matrix(neue_matrix) # nächste Generation
    kopiere_neu_zu_alt()
  

Das obige Programm ist nicht besonders benutzerfreundlich; es sollte ja möglichst einfach gehalten werden, damit es leicht nachzuvollziehen und nach eigenen Vorstellungen abzuändern ist.

Es gibt auch ein entsprechendes Programm mit einer tastengesteuerter Menüführung:

GoL_Menu.jpg
Game of Life: Menü
GoL_Menu.jpg (37.25 KiB) 3463 mal betrachtet

gof.wmv
Video: Diamant-GoL
(4.47 MiB) 586-mal heruntergeladen

Das Programm mitsamt gof.mp4-Video finden Sie im Anhang.

.
Dateianhänge
game_of_life_1_1.zip
(4.4 MiB) 587-mal heruntergeladen

Antworten