Skip to content

Instantly share code, notes, and snippets.

@benigumocom
Last active February 17, 2024 00:56
Show Gist options
  • Save benigumocom/24bfb3c6295ada5c1a86fbd8408f27f8 to your computer and use it in GitHub Desktop.
Save benigumocom/24bfb3c6295ada5c1a86fbd8408f27f8 to your computer and use it in GitHub Desktop.
【SwiftUI】Conway's Game of Life 👉 https://android.benigumo.com/20240215/conways-game-of-life/
import Foundation
// inspiered by
// lifegame/swift/lifegame.swift at master · tex2e/lifegame
// https://github.com/tex2e/lifegame/blob/master/swift/lifegame.swift
@Observable final class LifeGame {
var field: [[Int]] = []
var generation = 0
private var height: Int = 0
private var width: Int = 0
private var task: Task<Void, Never>?
func create(_ width: Int, _ height: Int) {
self.width = width
self.height = height
self.field = Array(repeating: Array(repeating: 0, count: width), count: height)
for y in 0 ..< height {
for x in 0 ..< width {
self.field[y][x] = self.random(max: 2)
}
}
}
private func random(max maxNumber: Int) -> Int {
return Int(arc4random_uniform(UInt32(maxNumber)))
}
func start() {
task = Task { @MainActor in
do {
while true {
self.field = evolve()
generation += 1
//usleep(100000)
try await Task.sleep(for: .seconds(0.1))
}
} catch {
if Task.isCancelled {
print("cancelled.")
}
}
}
}
func stop() {
task?.cancel()
}
func reset() {
stop()
field = []
generation = 0
create(width, height)
}
private func evolve() -> Array<Array<Int>> {
var newField = Array(repeating: Array(repeating: 0, count: width), count: height)
for y in 0 ..< height {
for x in 0 ..< width {
switch countAliveNeighbours(y: y, x: x) {
case 2:
newField[y][x] = field[y][x]
case 3:
newField[y][x] = 1
default:
newField[y][x] = 0
}
}
}
return newField
}
private func countAliveNeighbours(y: Int, x: Int) -> Int {
var count = 0
for yi in -1 ... 1 {
for xi in -1 ... 1 {
if yi == 0 && xi == 0 { continue }
if field[mod(y + yi, divBy: height)][mod(x + xi, divBy: width)] == 1 {
count += 1
}
}
}
return count
}
private func mod(_ a: Int, divBy b: Int) -> Int {
return (a + b) % b
}
}
import Foundation
import SwiftUI
struct LifeGameView: View {
private var lifeGame = LifeGame()
@State private var on = false
private let num = 20
var body: some View {
VStack(spacing: 0) {
GeometryReader { geo in
let (cols, rows) = nums(geo)
Grid(horizontalSpacing: 1, verticalSpacing: 1) {
ForEach(lifeGame.field.indices, id: \.self) { y in
GridRow {
ForEach(lifeGame.field.first!.indices, id: \.self) { x in
lifeGame.field[y][x] == 1 ? Color.alive : Color.dead
}
}
}
}
.onAppear {
lifeGame.create(cols, rows)
}
}
.onTapGesture(count: 2) {
lifeGame.reset()
}
.onTapGesture(count: 1) {
on.toggle()
if on {
lifeGame.start()
} else {
lifeGame.stop()
}
}
Text("generation \(lifeGame.generation)").font(.headline)
.padding()
}
}
func nums(_ geo: GeometryProxy) -> (Int, Int) {
let (w, h) = (geo.size.width, geo.size.height)
let size = min(w, h) / Double(num)
return (Int(w / size), Int(h / size))
}
}
extension Color {
static let alive = Color(red: 15 / 256, green: 75 / 256, blue: 89 / 256)
static let dead = Color(red: 217 / 256, green: 231 / 256, blue: 226 / 256)
}
#Preview {
LifeGameView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment