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] }