Skip to content

Instantly share code, notes, and snippets.

@IronSavior
Created July 14, 2023 14:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IronSavior/b775790bf3c0924eb7b9cc97ab2d3c20 to your computer and use it in GitHub Desktop.
Save IronSavior/b775790bf3c0924eb7b9cc97ab2d3c20 to your computer and use it in GitHub Desktop.
Conway's Type-Safe Game of Life
package main
import (
"fmt"
)
type Loc struct { X, Y int }
func (l Loc) Neighbors() []Loc {
neighbors := make([]Loc, 0, 8)
for dx := -1; dx <= 1; dx++ {
for dy := -1; dy <= 1; dy++ {
if dx == 0 && dy == 0 {
continue
}
neighbors = append(neighbors, Loc{l.X + dx, l.Y + dy})
}
}
return neighbors
}
type Rect struct { Min, Max Loc }
func (r Rect) Grow(n int) Rect {
return Rect{
Min: Loc{
X: r.Min.X - n,
Y: r.Min.Y - n,
},
Max: Loc{
X: r.Max.X + n,
Y: r.Max.Y + n,
},
}
}
func (b Rect) Rows() [][]Loc {
var o [][]Loc
for y := b.Min.Y; y <= b.Max.Y; y++ {
var row []Loc
for x := b.Min.X; x <= b.Max.X; x++ {
row = append(row, Loc{X: x, Y: y})
}
o = append(o, row)
}
return o
}
type LocSet struct {
db map[Loc]struct{}
}
func NewLocSet(given ...Loc) LocSet {
ls := LocSet{
db: make(map[Loc]struct{}),
}
ls.Add(given...)
return ls
}
func (ls LocSet) Members() []Loc {
var o []Loc
for loc := range ls.db {
o = append(o, loc)
}
return o
}
func (ls *LocSet) Add(given ...Loc) {
for _, loc := range given {
ls.db[loc] = struct{}{}
}
}
func (ls LocSet) Contains(given Loc) bool {
_, ok := ls.db[given]
return ok
}
func (ls LocSet) Len() int {
return len(ls.db)
}
type Living struct {}
func (Living) String() string { return "■" }
type Dead struct {}
func (Dead) String() string { return "□" }
type GameState struct {
alive LocSet
}
func NewGameState(cells ...Loc) GameState {
return GameState{
alive: NewLocSet(cells...),
}
}
func (g GameState) Bounds() Rect {
if g.alive.Len() < 1 {
return Rect{}
}
living := g.alive.Members()
bounds := Rect{
Min: living[0],
Max: living[0],
}
for _, loc := range living {
if loc.X < bounds.Min.X {
bounds.Min.X = loc.X
}
if loc.X > bounds.Max.X {
bounds.Max.X = loc.X
}
if loc.Y < bounds.Min.Y {
bounds.Min.Y = loc.Y
}
if loc.Y > bounds.Max.Y {
bounds.Max.Y = loc.Y
}
}
return bounds
}
func (g GameState) Alive(loc Loc) bool {
return g.alive.Contains(loc)
}
func (g GameState) CellState(loc Loc) fmt.Stringer {
if g.Alive(loc) {
return Living{}
}
return Dead{}
}
func (g GameState) relevantCells() []Loc {
ls := NewLocSet()
for _, loc := range g.alive.Members() {
ls.Add(loc)
ls.Add(loc.Neighbors()...)
}
return ls.Members()
}
func (g GameState) LivingNeighborCount(cell Loc) int {
count := 0
for _, neighbor := range cell.Neighbors() {
if g.Alive(neighbor) {
count++
}
}
return count
}
func (g GameState) Next() GameState {
next := NewGameState()
for _, loc := range g.relevantCells() {
count := g.LivingNeighborCount(loc)
if g.Alive(loc) && count == 2 || count == 3 {
next.alive.Add(loc)
}
}
return next
}
func main() {
// Set the initial state of the game with two gliders
gameState := NewGameState(
// Glider 1 pointing left
Loc{5, 5},
Loc{5, 6},
Loc{5, 7},
Loc{6, 7},
Loc{7, 6},
// Glider 2 pointing right
Loc{11, 5},
Loc{11, 6},
Loc{11, 7},
Loc{10, 7},
Loc{9, 6},
)
printGameState(gameState)
for i := 0; i < 10; i++ {
fmt.Print("\n\n")
gameState = gameState.Next()
printGameState(gameState)
}
}
func printGameState(g GameState) {
for _, row := range g.Bounds().Grow(1).Rows() {
for _, loc := range row {
fmt.Printf("%s ", g.CellState(loc))
}
fmt.Print("\n")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment