box-o-sand/conway/game_of_life.go
Dan Buch 3e013ce1a5 Too much crap for one commit
Breaking out console runner into its own file, adding an empty web
runner, and starting to test that games and game states are able to
represent themselves as images.
2012-12-16 17:59:54 -05:00

191 lines
3.5 KiB
Go

package conway
import (
"errors"
"fmt"
"image"
"math/rand"
)
type GameOfLife struct {
State *GameState
}
func (game *GameOfLife) Mutate() {
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
cell := game.State.Get(cell.X, cell.Y)
if cell.Value == 1 {
if score < 2 {
game.State.Deaden(cell.X, cell.Y)
continue
}
if score == 2 || score == 3 {
continue
}
if score > 3 {
game.State.Deaden(cell.X, cell.Y)
continue
}
} else if cell.Value == 0 {
if score == 3 {
game.State.Enliven(cell.X, cell.Y)
continue
} else {
continue
}
}
}
return nil
}
func (game *GameOfLife) ImportState(state *GameState) {
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.Value = rand.Intn(2)
}
game.ImportState(randState)
return nil
}
func (game *GameOfLife) String() string {
return fmt.Sprintf("%s\n", game.State)
}
func (game *GameOfLife) Image(xMult, yMult int) (*image.Gray16, error) {
return game.State.Image(xMult, yMult)
}
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]
}