2012-12-09 02:47:21 +00:00
|
|
|
package conway
|
|
|
|
|
2012-12-09 03:44:45 +00:00
|
|
|
import (
|
|
|
|
"errors"
|
2012-12-10 02:05:40 +00:00
|
|
|
"fmt"
|
2012-12-16 22:59:54 +00:00
|
|
|
"image"
|
2012-12-10 04:20:43 +00:00
|
|
|
"math/rand"
|
2012-12-09 03:44:45 +00:00
|
|
|
)
|
|
|
|
|
2012-12-09 02:47:21 +00:00
|
|
|
type GameOfLife struct {
|
2012-12-10 02:05:40 +00:00
|
|
|
State *GameState
|
2012-12-09 03:44:45 +00:00
|
|
|
}
|
|
|
|
|
2012-12-16 17:17:44 +00:00
|
|
|
func (game *GameOfLife) Mutate() {
|
|
|
|
game.State.Mutate()
|
2012-12-10 05:19:31 +00:00
|
|
|
}
|
|
|
|
|
2012-12-12 03:30:34 +00:00
|
|
|
func (game *GameOfLife) Checksum() (string, error) {
|
2012-12-10 05:35:09 +00:00
|
|
|
return game.State.Checksum()
|
|
|
|
}
|
|
|
|
|
2012-12-09 05:22:10 +00:00
|
|
|
func NewGameOfLife(height, width int) *GameOfLife {
|
|
|
|
return &GameOfLife{
|
2012-12-10 02:05:40 +00:00
|
|
|
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)
|
|
|
|
|
2012-12-12 03:30:34 +00:00
|
|
|
cells, err := genScore.Cells()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2012-12-10 02:05:40 +00:00
|
|
|
|
2012-12-12 03:30:34 +00:00
|
|
|
for cell := range cells {
|
|
|
|
score := cell.Value
|
2012-12-10 03:16:16 +00:00
|
|
|
|
2012-12-16 17:17:44 +00:00
|
|
|
cell := game.State.Get(cell.X, cell.Y)
|
2012-12-10 03:33:42 +00:00
|
|
|
|
2012-12-16 17:17:44 +00:00
|
|
|
if cell.Value == 1 {
|
2012-12-12 03:30:34 +00:00
|
|
|
if score < 2 {
|
2012-12-16 17:17:44 +00:00
|
|
|
game.State.Deaden(cell.X, cell.Y)
|
2012-12-12 03:30:34 +00:00
|
|
|
continue
|
|
|
|
}
|
2012-12-10 03:33:42 +00:00
|
|
|
|
2012-12-12 03:30:34 +00:00
|
|
|
if score == 2 || score == 3 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if score > 3 {
|
2012-12-16 17:17:44 +00:00
|
|
|
game.State.Deaden(cell.X, cell.Y)
|
2012-12-12 03:30:34 +00:00
|
|
|
continue
|
|
|
|
}
|
2012-12-16 17:17:44 +00:00
|
|
|
} else if cell.Value == 0 {
|
2012-12-12 03:30:34 +00:00
|
|
|
|
|
|
|
if score == 3 {
|
2012-12-16 17:17:44 +00:00
|
|
|
game.State.Enliven(cell.X, cell.Y)
|
2012-12-12 03:30:34 +00:00
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
continue
|
2012-12-10 02:05:40 +00:00
|
|
|
}
|
|
|
|
}
|
2012-12-09 05:22:10 +00:00
|
|
|
}
|
2012-12-10 02:05:40 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-12-16 17:17:44 +00:00
|
|
|
func (game *GameOfLife) ImportState(state *GameState) {
|
|
|
|
game.State.Import(state)
|
2012-12-10 02:05:40 +00:00
|
|
|
}
|
|
|
|
|
2012-12-10 04:20:43 +00:00
|
|
|
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)
|
|
|
|
|
2012-12-12 03:30:34 +00:00
|
|
|
cells, err := randState.Cells()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for cell := range cells {
|
2012-12-16 17:17:44 +00:00
|
|
|
cell.Value = rand.Intn(2)
|
2012-12-10 04:20:43 +00:00
|
|
|
}
|
|
|
|
|
2012-12-16 17:17:44 +00:00
|
|
|
game.ImportState(randState)
|
|
|
|
return nil
|
2012-12-10 04:20:43 +00:00
|
|
|
}
|
|
|
|
|
2012-12-10 03:51:10 +00:00
|
|
|
func (game *GameOfLife) String() string {
|
2012-12-10 04:20:43 +00:00
|
|
|
return fmt.Sprintf("%s\n", game.State)
|
|
|
|
}
|
2012-12-12 04:35:49 +00:00
|
|
|
|
2012-12-16 22:59:54 +00:00
|
|
|
func (game *GameOfLife) Image(xMult, yMult int) (*image.Gray16, error) {
|
|
|
|
return game.State.Image(xMult, yMult)
|
|
|
|
}
|
|
|
|
|
2012-12-12 04:35:49 +00:00
|
|
|
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]
|
|
|
|
}
|