box-o-sand/conway/game_of_life.go

203 lines
3.6 KiB
Go

package conway
import (
"errors"
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
type GameOfLife struct {
State *GameState
}
func (game *GameOfLife) Mutate() error {
return game.State.Mutate()
}
func (game *GameOfLife) Checksum() (string, error) {
return game.State.Checksum()
}
func NewGameOfLife(height, width int) *GameOfLife {
return &GameOfLife{
State: NewGameState(height, width),
}
}
func (game *GameOfLife) EvaluateGeneration() error {
height, width := game.State.Height(), game.State.Width()
genScore := NewGenerationScoreCard(height, width)
genScore.Calculate(game.State)
cells, err := genScore.Cells()
if err != nil {
return err
}
for cell := range cells {
score := cell.Value
curState, err := game.State.Get(cell.X, cell.Y)
if err != nil {
return err
}
if curState == 1 {
if score < 2 {
err = game.State.Deaden(cell.X, cell.Y)
if err != nil {
return err
}
continue
}
if score == 2 || score == 3 {
continue
}
if score > 3 {
err = game.State.Deaden(cell.X, cell.Y)
if err != nil {
return err
}
continue
}
} else if curState == 0 {
if score == 3 {
err = game.State.Enliven(cell.X, cell.Y)
if err != nil {
return err
}
continue
} else {
continue
}
}
}
return nil
}
func (game *GameOfLife) ImportState(state *GameState) error {
return game.State.Import(state)
}
func (game *GameOfLife) ImportRandomState() error {
height := game.State.Height()
width := game.State.Width()
if height < 0 || width < 0 {
errStr := fmt.Sprintf("current game has invalid dimensions! %vx%v",
height, width)
return errors.New(errStr)
}
randState := NewGameState(height, width)
cells, err := randState.Cells()
if err != nil {
return err
}
for cell := range cells {
cell.SetValue(rand.Intn(2))
}
return game.ImportState(randState)
}
func (game *GameOfLife) String() string {
return fmt.Sprintf("%s\n", game.State)
}
func (game *GameOfLife) emitGenerations(genTicks chan *GenerationTick) {
defer close(genTicks)
n := 0
checksums := make([]string, 4)
checksums[0], checksums[1], checksums[2], checksums[3] = "foo", "bar", "baz", "qwx"
ck, err := game.Checksum()
if err != nil {
genTicks <- &GenerationTick{
N: n,
Error: err,
Message: "NAY NAY",
}
}
pushChecksum(ck, checksums)
for {
game.EvaluateGeneration()
curChecksum, err := game.Checksum()
if err != nil {
genTicks <- &GenerationTick{
N: n,
Error: err,
Message: "Failed to calculate current game checksum",
}
break
}
if checksums[0] == curChecksum || checksums[1] == curChecksum {
genTicks <- &GenerationTick{
N: n,
Error: nil,
Message: "Stasis!",
}
break
}
if checksums[2] == curChecksum {
genTicks <- &GenerationTick{
N: n,
Error: nil,
Message: "Stasis with 2-period oscillator(s)!",
}
break
}
if checksums[3] == curChecksum {
genTicks <- &GenerationTick{
N: n,
Error: nil,
Message: "Stasis with 3-period oscillator(s)!",
}
break
}
pushChecksum(curChecksum, checksums)
genTicks <- &GenerationTick{
N: n,
Error: nil,
Message: "",
}
n++
}
}
func (game *GameOfLife) Generations() (<-chan *GenerationTick, error) {
genTicks := make(chan *GenerationTick)
go game.emitGenerations(genTicks)
return (<-chan *GenerationTick)(genTicks), nil
}
func pushChecksum(checksum string, checksums []string) {
head := make([]string, 3)
copy(head, checksums[:3])
checksums[0] = checksum
checksums[1] = head[0]
checksums[2] = head[1]
checksums[3] = head[2]
}