Technologische Neuigkeiten, Bewertungen und Tipps!

Conways Spiel des Lebens in Swift

Hinweis: Der folgende Artikel hilft Ihnen weiter: Conways Spiel des Lebens in Swift

Zurück zum Blog

Conway’s Game of Life ist ein unterhaltsames Simulationsspiel, und wir werden es in Swift programmieren! Anhand von 3 einfachen Regeln werden wir sehen, welches der Pixel es in die nächste Generation schafft. Es ist eine großartige Programmierübung, perfekt für einen Sonntagnachmittag.

Game of Life ist ein zellulärer Automat, der vom britischen Mathematiker John Conway (1937-2020) erfunden wurde. Es handelt sich um eine Simulation, die einfache Regeln dafür definiert, wie sich eine Population (von Pixeln!) nach der Erstellung einer Erstkonfiguration entwickelt.

Das klingt langweilig, ist aber absolut faszinierend. Schauen Sie sich das an:

Was Sie hier sehen, ist Gospers Segelflugzeugpistole. Es ist eine Konfiguration, die Segelflugzeuge hervorbringt, die winzigen raumschiffähnlichen Dinger, die aus der Mitte herausschießen. Basierend auf drei einfachen Regeln und einem anfänglichen Setup geht dieses „Spiel“ auf unbestimmte Zeit weiter.

Das Spiel des Lebens findet beispielsweise auf einem zweidimensionalen Gitter aus Zellen statt, ähnlich einem Pixelbild. Jede Zelle kann lebendig oder tot sein. In jeder Generation des Spiels bestimmen Sie, welche Zellen in der nächsten Generation weiterleben. Dies geschieht basierend auf dem Lebend-/Totzustand der 8 Nachbarn einer Zelle und 3 Regeln.

Diese Regeln, um genau zu sein:

  1. Eine lebende Zelle mit 2 oder 3 lebenden Nachbarn überlebt.
  2. Eine tote Zelle mit drei lebenden Nachbarn wird zu einer lebenden Zelle.
  3. Alle anderen lebenden Zellen sterben in der nächsten Generation und alle anderen toten Zellen bleiben tot.

Man könnte sagen, dass eine Zelle am Leben bleibt, wenn sie von ein paar Zellen umgeben ist (1). Neue Zellen werden „geboren“, wenn sich drei Zellen um sie herum befinden (2). Alles andere ist verloren (3).

Hier ist ein Beispiel dafür, wie das bei den Segelflugzeugen funktioniert, die Sie zuvor gesehen haben.

Sie sehen sich die Startkonfiguration eines Segelflugzeugs an. Die Nachbarn der mittleren Zelle werden hervorgehoben. Wird dieses Pixel die nächste Generation überleben? Zählen Sie die Anzahl der lebenden Nachbarn und überzeugen Sie sich selbst! (Der Zustand der anderen Zellen wird auch im zweiten Bild angezeigt.)

Wie wäre es mit einem Blinker? Es handelt sich um eine einfache Konfiguration aus 3 Zellen, die zwischen einer horizontalen und vertikalen Linie wechselt. Es ist stabil und blinkt daher ewig weiter.

Warum allerdings? Die mittlere Zelle bleibt immer am Leben. Die beiden Zellen an den Enden wechseln zwischen horizontal und vertikal, da sie immer drei Nachbarn haben. Faszinierend, oder?

Das ist nicht alles…

  • Das Spiel des Lebens kann eine Turing-Maschine simulieren; Es ist Turing vollständig. Sie können im Grunde jeden möglichen Algorithmus im Spiel des Lebens simulieren. Theoretisch können Sie einen Anfangszustand für das Leben erstellen, der die Ziffern von Pi erzeugt. Sie können Game of Life selbst erstellen. Ihrer Fantasie gehen die Ideen aus, bevor Sie „Game of Life“ ausgeschöpft haben!
  • Ein Konzept innerhalb von Game of Life besteht darin, ob sich ein Zellmuster in einer bestimmten Anzahl von Generationen stabilisiert. Viele Muster bleiben lange Zeit chaotisch, bis sie sich stabilisieren. Aufgrund des Halteproblems, einer allgemeinen Regel (oder Herausforderung) in der Berechnungstheorie, gibt es keinen Algorithmus, der vorhersagen kann, ob ein späteres Muster auftritt. Sie können das Spiel des Lebens buchstäblich unbegrenzt weiterspielen. Es ist unvermeidlich!
  • Game of Life ist faszinierend und ziemlich verrückt. Eine schnelle Online-Suche zeigt Ihnen zahlreiche Videos mit komplizierten, chaotischen Konfigurationen, die die erstaunlichsten Muster erzeugen. Sie müssen jedoch nicht verrückt werden, um ein paar hübsche Muster zu erhalten. Mit einer einfachen Erstkonfiguration erhalten Sie Segelflugzeuge, Raumschiffe, Blinker, Pulsare, Laibs, Boote und so weiter.

Autor Note: John Horton Conway (82 Jahre) starb am 11. April 2020 an den Folgen von COVID-19. Seine unschätzbaren Beiträge zur Mathematik, Spieltheorie und Informatik gehen weit über mein Verständnis hinaus. Gleichzeitig bin ich von der Einfachheit von „Game of Life“ unendlich fasziniert.

Den Beispielcode für dieses Tutorial finden Sie in diesem GitHub-Repository. Sie finden 3 Projekte:

  1. Game of Life mit Arrays: Ein Spielplatz mit dem Code in diesem Tutorial
  2. Spiel des Lebens mit Sets: Eine alternative Implementierung basierend auf Set
  3. Game of Life Xcode-Projekt: Eine iOS-App mit besserer Leistung auf dem iPhone

In diesem Tutorial erstellen wir die Implementierung, die Arrays verwendet, da sie auf einem Xcode-Playground eine bessere Leistung erbringt. Die Game of Life-Implementierung, die Set verwendet, ist recht elegant, aber aufgrund der umfangreichen Objekterstellung/-zerstörung schneidet sie auf einem Xcode-Spielplatz schlecht ab.

Der Code in diesem Tutorial wurde von Conways Game of Life auf Wikipedia, The Game of Life mit Functional Swift von Colin Eberhardt und Conways Game of Life auf Rosetta Code inspiriert.

Bevor wir beginnen, besprechen wir die Struktur des Codes, den wir schreiben werden. Aus der Vogelperspektive besteht diese Game of Life-Implementierung aus zwei Komponenten:

  1. Die Grid-Struktur, die die Zellen des Spiels des Lebens in einem 2D-Array darstellt. Es ist für die Berechnung der nächsten Generation verantwortlich.
  2. Die GridView-Ansicht (UIView), die das Raster auf dem Bildschirm zeichnet. Es iteriert einfach über die Zellen und zeichnet schwarze Pixel, wenn eine Zelle aktiv ist.

Wir erstellen außerdem eine Factory-Struktur mit statischen Funktionen, die ein Game of Life-Muster erzeugen. Mit ein wenig X/Y-Zauberkunst fügen wir diese Muster dem Raster hinzu, sodass Sie Ihre Ersteinrichtung ganz einfach erstellen können.

Lasst uns anfangen!

Beginnen Sie Ihr Projekt, indem Sie in Xcode einen leeren Spielplatz erstellen. Wir fangen ganz neu an – spannend!

Fügen Sie dann oben im Playground den folgenden Code hinzu:

UIKit importieren
PlaygroundSupport importieren

Wir importieren UIKit für den UIView-Typ und PlaygroundSupport, damit wir den Spielplatz auf unbestimmte Zeit ausführen können.

Fügen Sie als Nächstes den folgenden Code hinzu:

Strukturgitter
{
Var-Größe = (Breite: 50, Höhe: 50)
Var-Zellen:[[Int]]
}

Diese Grid-Struktur ist die Datenstruktur für die Zellen in Game of Life. Darin werden beispielsweise die Funktionen untergebracht, mit denen eine neue Generation berechnet wird.

Wir haben zwei Eigenschaften hinzugefügt: Größe und Zellen. Der Größentyp ist (Int, Int), ein Tupel. Wir haben die beiden Werte im Tupel „Breite“ und „Höhe“ benannt. Sie haben die Größe des Rasters, sodass wir jetzt ein Raster von 50×50 Zellen haben.

Die Art der Zellen ist [[Int]]. Dies ist ein Array von Arrays von Ganzzahlen, oder besser gesagt, ein zweidimensionales Array von Ganzzahlen. Sie können sich dies als ein zweidimensionales X/Y-Gitter mit Einsen und Nullen vorstellen. Mit Zellen können wir den Zustand jeder Zelle ermitteln[x][y]†.

Fügen Sie abschließend den folgenden Initialisierer zur Grid-Struktur hinzu:

drin() {
self.cells = Array(repeating: Array(repeating: 0, count: size.height), count: size.width)
}

Was ist denn hier los? Der obige Code initialisiert die Zelleneigenschaft mit einem 2D-Array aus Nullen. Das resultierende Array hat die Größe Breite mal Höhe. Es ist ein leeres Zellengitter; die leeren Anfänge des Spiels des Lebens.

Wenn Sie genau hinschauen, werden Sie sehen, dass wir zwei Aufrufe an Array(repeating:count:) durchführen. Der innere Aufruf wiederholt 0 für size.height mal, also eine Reihe von Nullen. Der äußere Aufruf wiederholt das innere Array() size.width mal, also eine Reihe von Reihen mit Nullen.

†: Der Einfachheit halber verwenden wir das Zellenraster als Zellen[x][y] und nenne das ein X/Y-Gitter. Ein kluger Leser wird nun darauf hinweisen, dass die Indizes im Zellenarray so wie sie sind, der Y-Koordinate und den Indizes für Zellen entsprechen[y] entspricht der X-Koordinate. Das heißt, wenn Sie die Werte in Zellen ausdrucken, sehen Sie ein Y/X-Raster. Wenn Sie das stört, können Sie das Array gerne transponieren!

OK, als nächstes kommt die Factory-Struktur. Diese Komponente verfügt über einige hartcodierte Zellmuster, die wir in die Grid-Struktur einfügen können. Seine API ermöglicht es Ihnen, schnell einige nette Anfangskonfigurationen für Game of Life zu erstellen, ohne jede 1 und 0 von Hand programmieren zu müssen.

Fügen Sie Ihrem Spielplatz den folgenden Code hinzu:

Strukturfabrik
{
statische Funktion glider() -> [[Int]]
{
zurückkehren [
[0, 1, 0],
[0, 0, 1],
[1, 1, 1],
]
}

statische Funktion blinker() -> [[Int]]
{
zurückkehren [
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
]
}

statische Funktion random(size: Int) -> [[Int]]
{
var cell = Array(repeating: Array(repeating: 0, count: size), count: size)
Lassen Sie die Quote = 1,0 / 5,0

für x in 0..= 0 && yd >= 0 && xd < size.width && yd < size.height { Zellen[xd][yd] = eingefügte Zellen[x][y] } } } } Schauen wir uns einmal an, wie das funktioniert. Diese Funktion hat 4 wichtige Aspekte:

  1. Die Funktion heißt insertCells(_:at:). Sie können ein 2D-Array, also ein Zellengitter, an einer Start.x- und einer Start.y-Koordinate einfügen.
  2. Innerhalb der Funktion durchlaufen wir eine Schleife über das 2D-Array insertCells. Wir betrachten jede einzelne Zelle im 2D-Array.
  3. Innerhalb der Schleife berechnen wir zunächst die Zielkoordinaten xd und yd. Wir tun dies, indem wir die 2D-Koordinate einer Zelle mit start.x und start.y des Startpunkts versetzen.
  4. Abschließend prüfen wir, ob das Ziel xd und yd innerhalb der Zellgrenzen liegt. Wenn ja, fügen wir den Wert der Zelle aus insertCells zur Zelleneigenschaft des Rasters hinzu.

Sehen Sie, wie das funktioniert? Wir nehmen im Wesentlichen das 2D-Array – das (kleine) Muster – und fügen es dem (großen) Raster für Game of Life hinzu. Ein zusätzlicher Vorteil ist der Startpunkt. Sie können beispielsweise ein Segelflugzeug in der Mitte des Rasters hinzufügen, indem Sie einen Wert für start.x und start.y angeben.

Da nun Code für das Raster vorhanden ist, ist es an der Zeit, dieses Raster auf dem Bildschirm zu zeichnen. Dies erreichen wir durch die Definition von GridView, einer Unterklasse des UIView-Typs. Sie können diese Ansicht in jede UIKit-basierte App einfügen.

Fügen Sie zunächst den folgenden Code zum Spielplatz hinzu:

Klasse GridView: UIView
{
var Grid = Grid()
}

Dies ist die GridView-Klasse, die eine Unterklasse von UIView ist. Es verfügt über eine Eigenschaft namens Grid vom Typ Grid. Dies ist die Struktur, die wir zuvor definiert haben; Wir heften diese Datenstruktur im Wesentlichen an die GridView-Ansicht.

Als nächstes fügen Sie der GridView-Klasse die folgende Funktion hinzu:

überschreibe func draw(_ rect: CGRect)
{

}

Diese Funktion „draw(_:)“ ist Teil von UIView und wir überschreiben sie hier mit unserer eigenen Implementierung. Es wird jedes Mal aufgerufen, wenn die Ansicht (neu) gezeichnet werden muss. Was auch immer wir in dieser Funktion „zeichnen“, wird in der Ansicht angezeigt. Das ist also ein perfekter Einstieg in das Zeichnen des Rasterinhalts (in Pixel).

So funktioniert die Zeichnung:

  1. Holen Sie sich den Grafikkontext, also die „Leinwand“, auf der wir zeichnen werden
  2. Räumen Sie die Leinwand frei, damit wir mit einer sauberen Tafel beginnen können
  3. Füllen Sie die Leinwand mit einem weißen Hintergrund
  4. Bestimmen Sie die Größe einer Zelle in Pixeln basierend auf dem Raster und der Ansichtsgröße
  5. Führen Sie eine Schleife über jede X/Y-Koordinate im Zellenraster aus und zeichnen Sie bei entsprechender Zelle ein schwarzes Rechteck auf der Leinwand an der entsprechenden Koordinate

Lass uns gehen!

Einrichten der Leinwand

Fügen Sie zunächst den folgenden Code zur Funktion draw(_:) hinzu:

Guard let context = UIGraphicsGetCurrentContext() else {
zurückkehren
}

context.clear(CGRect(x: 0,0, y: 0,0, Breite: Bounds.width, Höhe: Bounds.height))

Folgendes passiert:

  • Zuerst erhalten wir einen Verweis auf den Grafikkontext und weisen ihn dem Kontext zu. Wenn dies fehlschlägt, kehrt die Funktion zurück und beendet die Ausführung.
  • Anschließend löschen wir den Grafikkontext. Alles, was sich dort befindet, wird entfernt. Wir tun dies innerhalb des Rechtecks ​​(0, 0, Breite, Höhe).

Weißen Hintergrund füllen

Als nächstes fügen Sie diesen Code zur Funktion draw(_:) hinzu:

context.setFillColor(UIColor.white.cgColor)
context.addRect(CGRect(x: 0,0, y: 0,0, Breite: Bounds.width, Höhe: Bounds.height))
context.fillPath()

Das macht das:

  1. Stellen Sie die Füllfarbe auf Weiß ein, nehmen Sie also den weißen Farbeimer
  2. Definieren Sie ein Rechteck, das dieselbe Größe wie die Ansicht hat
  3. Füllen Sie dieses Rechteck mit der weißen Farbe

Wir haben jetzt eine komplett weiße Ansicht gezeichnet.

Zeichnen der Zellen

Bevor wir die Zellen des Spiels des Lebens auf dem Bildschirm zeichnen können, müssen wir die Größe einer Zelle in Pixel bestimmen. Unser Raster besteht beispielsweise aus 50×50 Zellen und die Größe der Ansicht könnte 400×400 Punkte (Pixel†) betragen, was bedeutet, dass 1 Zelle 8×8 Pixel groß ist.

Fügen Sie der Funktion den folgenden Code hinzu:

let cellSize = (width:bounds.width / CGFloat(grid.size.width), height:bounds.height / CGFloat(grid.size.height))
Die cellSize-Konstante ist ein Tupel mit Breiten- und Höhenwerten. Beide werden berechnet, indem die Breite der Ansicht durch width.grid und die Ansichtshöhe durch grid.height geteilt wird

bzw. Die Ansicht wird durch das Raster unterteilt, und jetzt haben wir eine einzelne Zellengröße von cellSize.width × cellSize.height Pixeln.

†: Technisch gesehen verwenden iOS-Apps das Konzept von „Punkten“, um Bildschirmdichten (DPI) zwischen verschiedenen iPhone/iPad-Geräten zu berücksichtigen. In diesem Tutorial können Sie Punkte und Pixel als Synonyme betrachten. Erfahren Sie hier mehr: 1x, 2x und 3x Bildskalierung unter iOS erklärt

Fügen Sie dann den folgenden Code hinzu. Dadurch wird die Füllfarbe auf Schwarz gesetzt:

context.setFillColor(UIColor.black.cgColor)

Fügen Sie abschließend den folgenden Code zur Funktion draw(_:) hinzu:

für x in 0..

  • Überprüfen Sie, ob der Wert der Zelle an dieser Koordinate 1 ist, da wir sie sonst nicht schwarz malen müssen (sie ist bereits weiß).
  • Fügen Sie ein Rechteck an der entsprechenden Koordinate in der Ansicht hinzu, dh multiplizieren Sie X/Y im Raster mit X/Y in der Ansicht basierend auf cellSize
  • Füllen Sie das Rechteck mit einer schwarzen Farbe
  • Eindrucksvoll!

    Bisher haben wir das Raster mit Zellen erstellt, eine Fabrik für Zellmuster (wie ein Segelflugzeug) und die GridView erstellt, die das Game of Life-Raster auf dem Bildschirm zeichnet. Lassen Sie uns diesen Code nutzen!

    Fügen Sie den folgenden Code zu Ihrem Spielplatz hinzu, am Ende des Codes, also unter allem anderen:

    PlaygroundPage.current.needsIndefiniteExecution = true

    let gridView = GridView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
    PlaygroundPage.current.liveView = GridView

    gridView.grid.insertCells(Factory.glider(), at: (x: 2, y: 2))
    gridView.grid.insertCells(Factory.glider(), at: (x: 10, y: 10))
    gridView.grid.insertCells(Factory.blinker(), at: (x: 5, y: 10))
    gridView.grid.insertCells(Factory.random(size: 20), at: (x: 20, y: 20))

    GridView.setNeedsDisplay()

    Folgendes bewirkt der Code:

    1. Aktivieren Sie die unendliche Ausführung für diesen Spielplatz. Das bedeutet, dass die Ausführung des Playgrounds am Ende des Codes nicht aufhört, sodass wir Timer und asynchrone Programmierung verwenden können. (Diese Einstellung benötigen wir später.)
    2. Erstellen Sie eine GridView-Instanz mit 400 x 400 Punkten und weisen Sie diese der LiveView-Komponente des Spielplatzes zu. Wenn dieses Raster festgelegt ist, wird es nun in der Live-Ansicht des Spielplatzes angezeigt. Sie können es mit Option + Befehl + Eingabetaste ein-/ausblenden.
    3. Mit insertCells(_:at:) fügen wir dem Raster eine Reihe voreingestellter Game of Life-Muster hinzu. Sie sehen ein paar Segelflugzeuge, einen Blinker und einige zufällige Punkte. Fühlen Sie sich frei, noch mehr hinzuzufügen! (Nur innerhalb des (0, 0, 50, 50)-Rechtecks.)
    4. Schließlich pingt setNeedsDisplay() die GridView an, dass sie neu gezeichnet werden muss. Dadurch wird die Funktion draw(_:) aufgerufen, die den Inhalt von „gridView.grid.cells“ auf dem Bildschirm zeichnet.

    Folgendes sollten Sie jetzt auf Ihrem Bildschirm sehen:

    Im nächsten Abschnitt berechnen wir die nächste Game of Life-Generation, indem wir jede Zelle durchlaufen und prüfen, ob sie lebt oder tot ist. Doch bevor wir das tun können, müssen wir eine Funktion programmieren, die bestimmt, ob eine einzelne Zelle in der nächsten Generation überlebt. Lasst uns das codieren!

    Fügen Sie zunächst die folgende Funktion zur Grid (!)-Struktur hinzu:

    funcstaysAlive(_ x: Int, _ y: Int, isAlive: Bool) -> Bool
    {

    }

    Die Funktion „staysAlive(_:_:isAlive:)“ bestimmt, ob eine Zelle an der Gitterkoordinate (x, y) in der nächsten Generation am Leben bleibt. Es wird wahr sein, wenn es lebendig ist, und falsch, wenn es tot ist.

    Der Parameter isAlive vom Typ Bool wird verwendet, um anzugeben, dass die Zelle in der aktuellen Generation aktiv ist. Dieser Status ist wichtig, um festzustellen, ob die Zelle in der nächsten Generation am Leben bleibt.

    Innerhalb der Funktion „staysAlive()“ müssen wir feststellen, ob eine Zelle am Leben bleibt. Wie wir zu Beginn dieses Tutorials besprochen haben, verwenden wir drei Regeln, um das Schicksal einer Zelle zu bestimmen:

    1. Eine lebende Zelle mit 2 oder 3 lebenden Nachbarn überlebt.
    2. Eine tote Zelle mit drei lebenden Nachbarn wird zu einer lebenden Zelle.
    3. Alle anderen lebenden Zellen sterben in der nächsten Generation und alle anderen toten Zellen bleiben tot.

    Der Algorithmus, den wir dafür verwenden, ist einfacher als Sie denken – er besteht nur aus 2 Komponenten! Wir zählen zunächst die Anzahl der aktiven Nachbarn und treffen dann eine Entscheidung über diese Anzahl und den Status von isAlive. Kinderleicht!

    Fügen Sie der Funktion zunächst den folgenden Code hinzu:

    Var-Anzahl = 0

    lass Paare = [
    [-1,-1], [0,-1], [1,-1],
    [-1, 0], [1, 0],
    [-1, 1], [0, 1], [1, 1]
    ]

    Die Zählvariable wird verwendet, um die Anzahl der aktiven Nachbarn zu verfolgen. Es beginnt natürlich bei Null.

    Die Paarkonstante ist ein 2D-Array mit relativen X/Y-Koordinaten. Es handelt sich im Wesentlichen um eine Matrix aus X/Y-Paaren. Stellen Sie sich eine Zelle vor und stellen Sie sich dann vor, diese 3×3-Matrix darauf zu platzieren.

    Jeder der acht Nachbarn um die Zelle herum entspricht einem Element im Paar-Array. Beispielsweise ist (-1, -1) die Zelle in der oberen linken Ecke relativ zur mittleren Zelle. (Wir verwenden einige Formatierungen, um die Lesbarkeit dieses Codes zu erleichtern.)

    Als nächstes werden wir die Paare durchlaufen. Fügen Sie diesen Code zur Funktion hinzu:

    für Paar in Paaren
    {
    Sei xd = x + Paar[0]
    Sei yd = y + Paar[1]

    }

    Kommt mir bekannt vor? Während wir das Array „pairs“ durchlaufen, nehmen wir die X- und Y-Werte „pair“.[0] und Paar[1] bzw. und fügen Sie diese zu den x- und y-Parametern der Funktion „staysAlive()“ hinzu.

    Ein Beispiel:

    • Wir bestimmen, ob die Zelle bei (3, 3) am Leben bleiben soll
    • Wenn wir Paare durchlaufen, finden wir die relative Koordinate (-1, -1).
    • Dies entspricht der absoluten Koordinate (2, 2), weil (2, 2) == (3 + -1, 3 + -1) == (3 – 1, 3 – 1). (Denken Sie daran, Plus und Minus sind Minus!)

    Fügen Sie als Nächstes den folgenden Code innerhalb der for in-Schleife unterhalb des vorhandenen Codes hinzu:

    wenn xd >= 0 && yd >= 0 &&
    xd < size.width && yd < size.height && Zellen[xd][yd] == 1 { count += 1 } Was ist hier los? Sie betrachten 4 Schritte:

    1. Überprüfen Sie bei festgelegten relativen Koordinaten xd und yd, ob diese größer oder gleich Null sind, dh innerhalb der Grenzen des Gitters
    2. Überprüfen Sie, ob sie kleiner als die Breite und Höhe des Rasters sind, also innerhalb der Grenzen des Rasters liegen
    3. Überprüfen Sie, ob die Zelle bei Zellen ist[xd][yd]also die Nachbarzelle, lebt – ihr Wert ist 1, wenn sie lebt, und 0, wenn sie tot ist
    4. Wenn das alles zutrifft, erhöhen Sie die Zahl um 1, denn wir haben eine lebende Nachbarzelle gefunden!

    Lassen Sie uns jetzt einen kurzen Rückblick geben. Wir versuchen herauszufinden, ob eine bestimmte Zelle im Gitter in der nächsten Generation am Leben bleiben sollte. Wir kennen seine Koordinate, also überprüfen wir ihren Status, indem wir eine Matrix von Zellen um diese Koordinate herum verwenden. Wir durchlaufen jede dieser benachbarten Zellen und prüfen, ob sie lebendig sind. Wenn ein Nachbar lebt, erhöhen wir die Anzahl um 1.

    Fügen Sie abschließend den folgenden Code zur Funktion „staysAlive()“ außerhalb der for-in-Schleife unterhalb des vorhandenen Codes hinzu:

    if isAlive && (count == 2 || count == 3) {
    Rückkehr wahr
    } else if !isAlive && count == 3 {
    Rückkehr wahr
    }

    falsch zurückgeben

    Ach, was ist das!? Das sieht nach den Regeln für Game of Life aus, oder? Wer hätte gedacht, dass das so einfach sein könnte …

    1. Wenn die Zelle, die wir überprüfen, derzeit aktiv ist und die Anzahl der aktiven Nachbarn entweder 2 oder 3 beträgt, bleibt die aktuelle Zelle am Leben.
    2. Wenn die Zelle, die wir überprüfen, nicht am Leben ist und drei lebende Nachbarn hat, bleibt/wird die aktuelle Zelle am Leben.
    3. Noch etwas? Entschuldigung, du bist tot!

    Eindrucksvoll! Damit ist die Arbeit an der FunktionstaysAlive() abgeschlossen. Wir sind jetzt bereit, das auf das Netz anzuwenden und die nächste Generation zu berechnen.

    Wenn Sie ein Problem in kleinere Teilprobleme aufteilen und diese lösen, wird das „größere“ Problem einfacher zu lösen. Das ist eines der Wunder der Computerprogrammierung. Wir haben diese ganze Arbeit geleistet, nur um den Kern von Game of Life – das Computing der nächsten Generation – einfacher programmierbar zu machen. Lasst uns anfangen!

    Fügen Sie der Grid-Struktur den folgenden Code hinzu:

    mutierende Funktionsgenerierung()
    {
    var nextCells = Array(repeating: Array(repeating: 0, count: size.height), count: size.width)

    für x in 0..

  • Erstellen Sie ein leeres 2D-Array nextCells mit einer Reihe von Nullen der Größe Breite × Höhe. Dies ist praktisch dasselbe wie das, was wir in der Funktion init() von Grid tun. Wir starten die nächste Generation mit einem leeren Raster.
  • Schlingen Sie das Gitter mit einer inneren und einer äußeren Schlaufe. Die äußere Schleife verläuft von 0 bis size.width (ohne), und die innere Schleife von 0 bis size.height (ohne). Nach wie vor erhalten wir dadurch Zugriff auf alle Gitterzellenkoordinaten (x, y) zwischen den Grenzen des Gitters.
  • Verwenden Sie die Funktion „staysAlive(_:_:isAlive:)“, um zu bestimmen, ob eine Zelle bis zur nächsten Generation überleben soll. Beachten Sie hier die Parameter! Wir stellen x und y der aktuellen Zelle sowie den Status der aktuellen Zelle mit Zellen bereit[x][y]. Wenn die Zelle lebt, Zellen[x][y] gleich 1.
  • Wenn dann in der innersten Zeile der Schleife „staysAlive()“ „true“ zurückgibt, setzen Sie die gleiche (x, y)-Koordinate für „nextCells“ auf 1. Diese Zelle ist in der nächsten Generation lebendig. Yay!
  • Überschreiben Sie abschließend die Zellen mit nextCells. Dies ist das Gitter der nächsten Generation, daher wird die aktuelle Generation verworfen.
  • Was gibt es sonst noch zu dieser Funktion zu sagen!? Wir durchlaufen das Raster, berechnen den Tot-/Lebendstatus jeder Zelle und übergeben die nächste Generation an die Zelleneigenschaft des Rasters. Eindrucksvoll!

    Zu guter Letzt benötigen wir noch etwas Code, um das alles zusammenzusetzen. Wir haben das Grid, die GridView und etwas Code erstellt, um die nächsten Generationen zu berechnen. Sie können das im Prinzip in eine Schleife packen und es für immer laufen lassen.

    Genau das werden wir tun! Fügen Sie dem Spielplatz unterhalb des vorhandenen Codes den folgenden Code hinzu:

    let timer = DispatchSource.makeTimerSource()
    timer.schedule(Deadline: .now(), Wiederholung: .milliseconds(500))
    timer.setEventHandler(handler: {

    GridView.grid.generation()

    DispatchQueue.main.async {
    GridView.setNeedsDisplay()
    }
    })

    timer.activate()

    Dieser Code erstellt einen Timer, der alle 500 Millisekunden einen Code wiederholt. Sie können sehen, dass wir die Funktion generation() im Raster aufrufen und dann setNeedsDisplay() aufrufen, um die Ansicht neu zu zeichnen. In der letzten Zeile aktivieren wir den Timer.

    Ein Problem beim Funktionieren von Game of Life besteht darin, dass die Berechnung in einer seriellen Warteschlange erfolgen muss. Sie können nur eine Generation berechnen, dann die nächste, die nächste und so weiter. Was nicht funktioniert, ist ein gleichzeitiger oder paralleler Prozess.

    Auch die Geschwindigkeit der Berechnung ist wichtig, insbesondere auf einem Xcode-Spielplatz. Die Berechnung kann möglicherweise langsamer werden, wenn mehr Zellen im Raster vorhanden sind oder wenn Ihr Mac etwas anderes tut. Aus diesem Grund lösen wir die Funktion generation() nur einmal alle 0,5 Sekunden aus.

    Warum haben wir hier nicht eine einfachere Timer-Komponente verwendet? Diese Timer-Komponente verwendet einen Runloop, um ihre Arbeit zu erledigen, und sie arbeitet asynchron. Der DispatchSourceTimer, den makeTimerSource() zurückgibt, verwendet die standardmäßige serielle Hintergrundwarteschlange, sodass wir garantiert sind, dass die Arbeit seriell erfolgt. Zwei Generationen können sich sozusagen nicht überschneiden.

    Innerhalb des Handlers des Timers springen wir nach dem Aufruf von generation() zum Hauptthread und planen dort setNeedsDisplay(), also einen Neuaufbau. Dies muss asynchron geschehen, aber das bedeutet auch, dass die Berechnung in generation() möglicherweise schneller ausgeführt werden kann, als die Ansicht aktualisiert werden kann. Wir vermeiden dies, indem wir für den Timer ein angemessenes Tempo (500 ms) festlegen.

    Schnell Note: Im Beispielcode habe ich eine Beispiel-iOS-App eingefügt, die Sie auf Ihrem iPhone ausführen können. Das Rendern von Ansichten auf einem iPhone ist viel schneller als auf einem Xcode-Playground. Ich habe eine gute Leistung beim Auslösen des Timers etwa alle 50 Millisekunden gesehen. Das bedeutet, dass Sie mehr Generationen in kürzerer Zeit simulieren können!

    Das ist es! Starten Sie Ihren Xcode-Spielplatz oder Ihre iPhone-App und erleben Sie, wie Conways Game of Life zum Leben erwacht. Eindrucksvoll!