package conway import ( "errors" "fmt" "math/rand" "time" ) func init() { rand.Seed(time.Now().UTC().UnixNano()) } type GameOfLife struct { State *GameState } func RunConsoleGame(height, width, sleepMs int, mutate bool) (int, error) { sleepInterval := time.Duration(sleepMs * 1000 * 1000) game := NewGameOfLife(height, width) err := game.ImportRandomState() if err != nil { return 2, err } genTicks, err := game.Generations() if err != nil { return 3, err } for genTick := range genTicks { fmt.Printf("\nGeneration %v\n%v\n", genTick.N, time.Now()) fmt.Println(game) if genTick.Error != nil { return 4, genTick.Error } if len(genTick.Message) > 0 { fmt.Println(genTick.Message) } if mutate && genTick.N%2 == 0 { game.Mutate() } time.Sleep(sleepInterval) } return 0, nil } 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] }