Created
July 14, 2023 14:30
-
-
Save IronSavior/b775790bf3c0924eb7b9cc97ab2d3c20 to your computer and use it in GitHub Desktop.
Conway's Type-Safe Game of Life
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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