3e013ce1a5
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.
191 lines
3.5 KiB
Go
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]
|
|
}
|