box-o-sand/conway/game_of_life.go

239 lines
4.3 KiB
Go
Raw Normal View History

package conway
2012-12-09 03:44:45 +00:00
import (
"errors"
"fmt"
"math/rand"
"time"
2012-12-09 03:44:45 +00:00
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
type GameOfLife struct {
State *GameState
2012-12-09 03:44:45 +00:00
}
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
2012-12-10 03:16:16 +00:00
curState, err := game.State.Get(cell.X, cell.Y)
if err != nil {
return err
}
2012-12-10 03:16:16 +00:00
if curState == 1 {
2012-12-10 03:33:42 +00:00
if score < 2 {
err = game.State.Deaden(cell.X, cell.Y)
if err != nil {
return err
2012-12-10 03:33:42 +00:00
}
continue
}
2012-12-10 03:33:42 +00:00
if score == 2 || score == 3 {
continue
}
if score > 3 {
err = game.State.Deaden(cell.X, cell.Y)
if err != nil {
return err
2012-12-10 03:33:42 +00:00
}
continue
}
} else if curState == 0 {
if score == 3 {
err = game.State.Enliven(cell.X, cell.Y)
if err != nil {
return err
2012-12-10 03:16:16 +00:00
}
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]
}