Removing now-extracted conway subtree

This commit is contained in:
Dan Buch 2013-01-09 23:14:45 -05:00
parent e427f23c90
commit 4107485591
15 changed files with 0 additions and 2384 deletions

1
conway/.gitignore vendored
View File

@ -1 +0,0 @@
/web_assets/web_assets

View File

@ -1,24 +0,0 @@
LIBS := \
github.com/meatballhat/box-o-sand/conway
TARGETS := \
$(LIBS) \
github.com/meatballhat/box-o-sand/conway/conways-game-of-life
all: test
test: build
go test -x -v -test.parallel=4 $(LIBS)
build: web_assets.go deps
go install -x $(TARGETS)
web_assets.go: $(wildcard web_assets/*.*)
./gen-web-assets
clean:
go clean -x -i $(TARGETS)
deps:
go get -n -x $(TARGETS)
.PHONY: all build clean deps test

View File

@ -1,47 +0,0 @@
package conway
import (
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
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
}

View File

@ -1,593 +0,0 @@
package conway_test
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"image/png"
"math/rand"
"net/http"
"regexp"
"strings"
"testing"
)
import (
. "github.com/meatballhat/box-o-sand/conway"
)
var TEST_SIMPLE_INITIAL_STATE = NewGameState(16, 16)
func init() {
s := TEST_SIMPLE_INITIAL_STATE
s.Set(0, 0, 1)
s.Set(0, 1, 1)
s.Set(0, 2, 1)
s.Set(1, 0, 1)
s.Set(1, 1, 1)
s.Set(2, 2, 1)
s.Set(3, 0, 1)
s.Set(3, 2, 1)
s.Set(13, 13, 1)
s.Set(13, 14, 1)
s.Set(14, 14, 1)
s.Set(14, 15, 1)
s.Set(15, 13, 1)
s.Set(15, 15, 1)
}
func newTestGameState() *GameState {
state := NewGameState(16, 16)
state.Import(TEST_SIMPLE_INITIAL_STATE)
return state
}
func newTestGameOfLife() *GameOfLife {
game := NewGameOfLife(16, 16)
game.State.Import(TEST_SIMPLE_INITIAL_STATE)
return game
}
func TestLiveCellWithFewerThanTwoLiveNeighborsDies(t *testing.T) {
game := newTestGameOfLife()
game.EvaluateGeneration()
cell := game.State.Get(3, 2)
if cell.Value != 0 {
t.Errorf("%v != 0", cell.Value)
return
}
}
func TestLiveCellWithTwoOrThreeLiveNeighborsLivesOn(t *testing.T) {
game := newTestGameOfLife()
game.EvaluateGeneration()
cell := game.State.Get(0, 2)
if cell.Value != 1 {
t.Errorf("%v != 1", cell.Value)
return
}
}
func TestLiveCellWithMoreThanThreeLiveNeighborsDies(t *testing.T) {
game := newTestGameOfLife()
game.EvaluateGeneration()
cell := game.State.Get(0, 1)
if cell.Value != 0 {
t.Errorf("%v != 0", cell.Value)
return
}
}
func TestDeadCellWithExactlyThreeLiveNeighborsBecomesAlive(t *testing.T) {
game := newTestGameOfLife()
game.EvaluateGeneration()
cell := game.State.Get(3, 1)
if cell.Value != 1 {
t.Errorf("%v != 1", cell.Value)
return
}
}
func TestGameOfLifeCanDisplayItselfAsAString(t *testing.T) {
game := newTestGameOfLife()
grid := fmt.Sprintf("%s\n", game)
gridLen := len(grid)
if gridLen < 256 {
t.Errorf("%v < 256", gridLen)
t.Fail()
}
}
func TestGameOfLifeCanDisplayItselfAsAnImage(t *testing.T) {
game := newTestGameOfLife()
img, err := game.Image(1, 1)
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 16 {
t.Errorf("image.Max.Y < 16!: %d", bounds.Max.Y)
return
}
if bounds.Max.X < 16 {
t.Errorf("image.Max.X < 16!: %d", bounds.Max.X)
return
}
}
func TestNewGameOfLifeHasCorrectDimensions(t *testing.T) {
game := newTestGameOfLife()
if game.State.Height() != 16 || game.State.Width() != 16 {
t.Fail()
}
}
func TestNewGameOfLifeHasGameStateOfSameDimensions(t *testing.T) {
game := newTestGameOfLife()
if len(game.State.Rows) < 16 {
t.Fail()
}
if len(game.State.Rows[0].Cells) < 16 {
t.Fail()
}
}
func TestGameStateCanImportState(t *testing.T) {
game := newTestGameOfLife()
if game.State.Get(0, 0).Value != 1 {
t.Fail()
}
if game.State.Get(1, 0).Value != 1 {
t.Fail()
}
if game.State.Get(0, 1).Value != 1 {
t.Fail()
}
if game.State.Get(1, 1).Value != 1 {
t.Fail()
}
if game.State.Get(2, 2).Value != 1 {
t.Fail()
}
}
func TestGameOfLifeCanImportRandomState(t *testing.T) {
game := newTestGameOfLife()
err := game.ImportRandomState()
if err != nil {
t.Error(err)
return
}
zeroCount := 0
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
cell := game.State.Get(x, y)
if cell.Value == 0 {
zeroCount++
}
}
}
if zeroCount == 256 {
t.Fail()
}
}
func TestGameStateCanDisplayItselfAsAString(t *testing.T) {
state := newTestGameState()
grid := fmt.Sprintf("%s\n", state)
lenGrid := len(grid)
if lenGrid < 256 {
t.Errorf("%v < 256", lenGrid)
return
}
nLines := strings.Count(grid, "\n")
if nLines < 16 {
t.Errorf("%v < 16 (lines)", nLines)
return
}
}
func TestGameStateCanDisplayItselfAsAnImage(t *testing.T) {
state := newTestGameState()
img, err := state.Image(1, 1)
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 16 {
t.Errorf("image.Max.Y < 16!: %d", bounds.Max.Y)
return
}
if bounds.Max.X < 16 {
t.Errorf("image.Max.X < 16!: %d", bounds.Max.X)
return
}
}
func TestNewGameStatesAreAllDead(t *testing.T) {
state := NewGameState(4, 4)
for x := 0; x < 3; x++ {
for y := 0; y < 3; y++ {
if state.Get(x, y).Value != 0 {
t.Fail()
}
}
}
}
func TestGameStateCanGetCellValueByCoords(t *testing.T) {
state := NewGameState(8, 8)
if state.Get(2, 2).Value != 0 {
t.Fail()
}
}
func TestGameStateCanSetCellValueByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Set(2, 5, 1)
if state.Get(2, 5).Value != 1 {
t.Fail()
}
}
func TestGameStateCanEnlivenCellsByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Enliven(0, 1)
if state.Get(0, 1).Value != 1 {
t.Fail()
}
}
func TestGameStateCanDeadenCellsByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Enliven(0, 1)
if state.Get(0, 1).Value != 1 {
t.Fail()
}
state.Deaden(0, 1)
if state.Get(0, 1).Value != 0 {
t.Fail()
}
}
func TestNewGenerationScoreCardIsAllZeros(t *testing.T) {
gen := NewGenerationScoreCard(4, 4)
for y := 0; y < gen.Height(); y++ {
for x := 0; x < gen.Width(); x++ {
if gen.Get(x, y).Value != 0 {
t.Fail()
}
}
}
}
func TestGenerationScoreCardCalculateResultsInCorrectNeighborCounts(t *testing.T) {
genScore := NewGenerationScoreCard(16, 16)
state := newTestGameState()
genScore.Calculate(state)
cell := genScore.Get(0, 0)
if cell.Value != 4 {
t.Errorf("[0, 0] value %v != 4", cell.Value)
return
}
cell = genScore.Get(3, 2)
if cell.Value != 1 {
t.Errorf("[3, 2] value %d != 1", cell.Value)
return
}
}
func TestStateCanMutate(t *testing.T) {
state := newTestGameState()
curCksum, err := state.Checksum()
if err != nil {
t.Error(err)
return
}
state.Mutate()
newCksum, err := state.Checksum()
if err != nil {
t.Error(err)
return
}
if curCksum == newCksum {
t.Errorf("Checksum did not change! %v", newCksum)
}
}
func TestStateProvidesCellIteratorChannel(t *testing.T) {
state := newTestGameState()
cells, err := state.Cells()
if err != nil {
t.Error(err)
return
}
cellCount := 0
for _ = range cells {
cellCount++
}
if cellCount != 256 {
t.Fail()
}
}
func TestGameStateCellsCanHaveTheirValueSet(t *testing.T) {
state := newTestGameState()
cells, err := state.Cells()
if err != nil {
t.Error(err)
return
}
for cell := range cells {
cell.Value = 1
}
cells, err = state.Cells()
if err != nil {
t.Error(err)
return
}
for cell := range cells {
if cell.Value != 1 {
t.Fail()
}
}
}
func TestGameStateCellsCanBeFetchedByXAndYCoords(t *testing.T) {
state := newTestGameState()
if state.Get(3, 4) == nil {
t.Fail()
}
}
func TestGameOfLifeEmitsGenerationTicksUntilStasis(t *testing.T) {
game := newTestGameOfLife()
genTicks, err := game.Generations()
if err != nil {
t.Error(err)
return
}
nGens := 0
for gen := range genTicks {
if gen.N > 3000 {
t.Errorf("%v is too many generations!", gen.N)
return
}
nGens = gen.N
}
if nGens < 3 {
t.Errorf("%v is too few generations!", nGens)
}
}
func TestGameStateCoordsAreNeverOutOfBounds(t *testing.T) {
state := newTestGameState()
oobX, oobY := state.Width()+rand.Intn(99), state.Height()+rand.Intn(99)
newVal := rand.Intn(9999)
state.Set(oobX, oobY, newVal)
cell := state.Get(oobX, oobY)
if cell == nil {
t.Fail()
}
if cell.Value != newVal {
t.Errorf("(%d, %d) != %d: %d", oobX, oobY, newVal, cell.Value)
}
}
type testResponseWriter struct {
Status int
Headers http.Header
Body []byte
}
func (w *testResponseWriter) WriteHeader(status int) {
w.Status = status
}
func (w *testResponseWriter) Header() http.Header {
return w.Headers
}
func (w *testResponseWriter) Write(bytes []byte) (int, error) {
w.Body = append(w.Body, bytes[:]...)
return len(bytes), nil
}
func newTestResponseWriter() *testResponseWriter {
return &testResponseWriter{
Body: []byte(""),
Headers: make(map[string][]string),
}
}
func TestHandleWebGameRootReturnsIndexPageForGET(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:9775/", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameRoot(w, req)
if w.Status != http.StatusOK {
t.Errorf("response status != %d: %d", http.StatusOK, w.Status)
return
}
matched, err := regexp.MatchString("<h1>Conway's Game of Life</h1>", string(w.Body))
if err != nil {
t.Error(err)
return
}
if !matched {
t.Errorf("Body did not match expected <h1> element")
return
}
}
func TestHandleWebGameRootReturns405ForNonGET(t *testing.T) {
req, err := http.NewRequest("OPTIONS", "http://localhost:9775/", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameRoot(w, req)
if w.Status != http.StatusMethodNotAllowed {
t.Fail()
return
}
matched, err := regexp.MatchString("Nope", string(w.Body))
if err != nil {
t.Error(err)
return
}
if !matched {
t.Fail()
return
}
}
func TestHandleWebGameStateGETReturnsRandomImage(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:9775/state", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameState(w, req)
if w.Status != http.StatusOK {
t.Errorf("status != %d: %d", http.StatusOK, w.Status)
return
}
webState := &WebGameState{}
err = json.Unmarshal(w.Body, webState)
if err != nil {
t.Error(err)
return
}
imgB64 := []byte(webState.Img)
imgBytes := make([]byte, len(imgB64))
_, err = base64.StdEncoding.Decode(imgBytes, imgB64)
if err != nil {
t.Error(err)
return
}
img, err := png.Decode(bytes.NewBuffer(imgBytes))
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 80 || bounds.Max.X < 80 {
t.Errorf("Image has incorrect bounds: %+v", bounds)
return
}
}
func TestHandleWebGameStatePOSTWithStateReturnsNextGeneration(t *testing.T) {
state := newTestGameState()
webGameState := &WebGameState{State: state, Img: ""}
stateJson, err := json.Marshal(webGameState)
if err != nil {
t.Error(err)
return
}
reqBuf := bytes.NewBuffer(stateJson)
req, err := http.NewRequest("POST", "http://localhost:9975/state", reqBuf)
if err != nil {
t.Errorf("err != nil: %+v", err)
return
}
w := newTestResponseWriter()
HandleWebGameState(w, req)
if w.Status != http.StatusCreated {
t.Errorf("response status != %d: %d", http.StatusCreated, w.Status)
return
}
}

View File

@ -1,45 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
)
import (
. "github.com/meatballhat/box-o-sand/conway"
)
var (
height = flag.Int("height", 40, "Game height")
width = flag.Int("width", 80, "Game width")
mutate = flag.Bool("mutate", false, "Mutate every other generation")
web = flag.Bool("web", false, "Run a web-based game.")
addr = flag.String("addr", ":9775",
"Address for server of web-based game (ignored if running a console game.)")
sleepMs = flag.Int("sleep.ms", 200,
"Millisecond sleep interval per generation")
)
func main() {
flag.Parse()
var (
retCode int
err error
)
if *web {
retCode, err = RunWebGame(*addr, *height, *width, *sleepMs, *mutate)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
}
} else {
retCode, err = RunConsoleGame(*height, *width, *sleepMs, *mutate)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
}
}
os.Exit(retCode)
}

View File

@ -1,4 +0,0 @@
/*
Conway's Game of Life in Go (console and web.)
*/
package conway

View File

@ -1,190 +0,0 @@
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.Gray, 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]
}

View File

@ -1,206 +0,0 @@
package conway
import (
"crypto/sha1"
"errors"
"fmt"
"image"
"image/color"
"image/draw"
"math"
"math/rand"
"strings"
)
const (
deadCell = "■"
liveCell = " "
)
type GameStateCell struct {
Value int `json:"v"`
X int `json:"x"`
Y int `json:"y"`
}
type GameStateRow struct {
Y int `json:"y"`
Cells []*GameStateCell `json:"c"`
}
type GameState struct {
Rows []*GameStateRow `json:"r"`
}
func NewGameState(height, width int) *GameState {
state := &GameState{}
for i := 0; i < height; i++ {
row := &GameStateRow{Y: i}
for j := 0; j < width; j++ {
row.Cells = append(row.Cells, &GameStateCell{
Value: 0,
X: j,
Y: i,
})
}
state.Rows = append(state.Rows, row)
}
return state
}
func (state *GameState) Height() int {
return len(state.Rows)
}
func (state *GameState) Width() int {
if len(state.Rows) < 1 {
return -1
}
return len(state.Rows[0].Cells)
}
func (state *GameState) GetRow(y int) *GameStateRow {
lenRows := len(state.Rows)
if y < 0 {
return state.GetRow(lenRows + y)
}
if y+1 > lenRows {
return state.GetRow(y % lenRows)
}
return state.Rows[y]
}
func (state *GameState) Set(x, y, value int) {
cell := state.Get(x, y)
cell.Value = value
}
func (state *GameState) Get(x, y int) *GameStateCell {
row := state.GetRow(y)
lenCells := len(row.Cells)
if x < 0 {
return state.Get(lenCells+x, row.Y)
}
if x+1 > lenCells {
return state.Get(x%lenCells, row.Y)
}
return row.Cells[x]
}
func (state *GameState) Enliven(x, y int) {
state.Set(x, y, 1)
}
func (state *GameState) Deaden(x, y int) {
state.Set(x, y, 0)
}
func (state *GameState) Import(other *GameState) {
for y, row := range other.Rows {
for x, cell := range row.Cells {
state.Set(x, y, cell.Value)
}
}
}
func (state *GameState) Cells() (<-chan *GameStateCell, error) {
height := state.Height()
width := state.Width()
if height < 0 || width < 0 {
return nil, errors.New("state has invalid dimensions!")
}
cells := make(chan *GameStateCell)
go func(state *GameState, c chan *GameStateCell) {
defer close(c)
height := state.Height()
width := state.Width()
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
c <- state.Get(x, y)
}
}
}(state, cells)
return (<-chan *GameStateCell)(cells), nil
}
func (state *GameState) Mutate() {
randX := rand.Intn(state.Width())
randY := rand.Intn(state.Height())
cell := state.Get(randX, randY)
state.Set(randX, randY, int(math.Abs(float64(cell.Value-1))))
}
func (state *GameState) Checksum() (string, error) {
ck := sha1.New()
cells, err := state.Cells()
if err != nil {
return "", err
}
for cell := range cells {
fmt.Fprintf(ck, "%v", cell.Value)
}
return fmt.Sprintf("%x", ck.Sum(nil)), nil
}
func (state *GameState) String() string {
var rows []string
height := state.Height()
width := state.Width()
for y := 0; y < height; y++ {
var cells []string
for x := 0; x < width; x++ {
stringVal := deadCell
cell := state.Get(x, y)
if cell.Value == 1 {
stringVal = liveCell
}
cells = append(cells, stringVal)
}
rows = append(rows, strings.Join(cells, ""))
}
return strings.Join(rows, "\n")
}
func (state *GameState) Image(xMult, yMult int) (*image.Gray, error) {
img := image.NewGray(image.Rect(0, 0,
(state.Height()*xMult)+1, (state.Width()*yMult)+1))
draw.Draw(img, img.Bounds(), &image.Uniform{color.Gray16{0xff}},
image.ZP, draw.Src)
cells, err := state.Cells()
if err != nil {
return nil, err
}
black := &image.Uniform{color.Black}
white := &image.Uniform{color.White}
for cell := range cells {
color := white
if cell.Value == 1 {
color = black
}
square := image.Rect((cell.X*xMult)+1, (cell.Y*yMult)+1,
(cell.X*xMult)+xMult, (cell.Y*yMult)+yMult)
draw.Draw(img, square, color, image.ZP, draw.Src)
}
return img, nil
}

View File

@ -1,29 +0,0 @@
#!/bin/sh
set -e
case "$1" in
"shell")
cat > _tmp.go <<EOWEBASSETSGO
// WARNING: GENERATED FILE, NERDS! $(date)
package conway
const (
gameOfLifeIndexHtml = \`
$(cat web_assets/index.html | gzip -9 | base64)\`
normalizeCss = \`
$(curl -s http://necolas.github.com/normalize.css/2.0.1/normalize.css | gzip -9 | base64)\`
jqueryMinJs = \`
$(curl -s http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js | gzip -9 | base64)\`
)
EOWEBASSETSGO
cat _tmp.go | gofmt > web_assets.go
rm -v _tmp.go
;;
*)
cd web_assets
go build -x .
./web_assets index.html > _tmp.go
gofmt < _tmp.go > ../web_assets.go
rm -vf _tmp.go
;;
esac

View File

@ -1,62 +0,0 @@
package conway
var (
neighbors = []neighbor{
neighbor{-1, -1},
neighbor{-1, 0},
neighbor{-1, 1},
neighbor{0, -1},
neighbor{0, 1},
neighbor{1, -1},
neighbor{1, 0},
neighbor{1, 1},
}
)
type GenerationScoreCard struct {
GameState
}
type neighbor struct {
X int
Y int
}
func NewGenerationScoreCard(height, width int) *GenerationScoreCard {
genScore := &GenerationScoreCard{}
for i := 0; i < height; i++ {
row := &GameStateRow{Y: i}
for j := 0; j < width; j++ {
row.Cells = append(row.Cells, &GameStateCell{
Value: 0,
X: j,
Y: i,
})
}
genScore.Rows = append(genScore.Rows, row)
}
return genScore
}
func (genScore *GenerationScoreCard) Calculate(state *GameState) error {
stateCells, err := state.Cells()
if err != nil {
return err
}
for stateCell := range stateCells {
if stateCell.Value == 0 {
continue
}
for _, neighbor := range neighbors {
xTarget := stateCell.X + neighbor.X
yTarget := stateCell.Y + neighbor.Y
cell := genScore.Get(xTarget, yTarget)
genScore.Set(xTarget, yTarget, cell.Value+1)
}
}
return nil
}

View File

@ -1,7 +0,0 @@
package conway
type GenerationTick struct {
N int
Error error
Message string
}

View File

@ -1,272 +0,0 @@
package conway
import (
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"image/png"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"strconv"
"time"
)
type WebGameParams struct {
Height int
Width int
Xmul int
Ymul int
}
type WebGameState struct {
State *GameState `json:"s"`
Img string `json:"i"`
}
type webAssetStrings struct {
IndexHTML []byte
NormalizeCSS []byte
JqueryMinJS []byte
}
var WebAssets webAssetStrings
func init() {
rand.Seed(time.Now().UTC().UnixNano())
idxHt, err := fromGzB64(gameOfLifeIndexHtml)
if err != nil {
log.Fatal("Failed to decode internal index.html: ", err)
}
WebAssets.IndexHTML = idxHt
normCss, err := fromGzB64(normalizeCss)
if err != nil {
log.Fatal("Failed to decode internal normalize.css: ", err)
}
WebAssets.NormalizeCSS = normCss
jqmin, err := fromGzB64(jqueryMinJs)
if err != nil {
log.Fatal("Failed to decode internal jquery.min.js: ", err)
}
WebAssets.JqueryMinJS = jqmin
}
func fromGzB64(input string) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(input)
if err != nil {
return []byte(""), err
}
buf := bytes.NewBuffer(decoded)
zReader, err := gzip.NewReader(buf)
if err != nil {
return []byte(""), err
}
defer zReader.Close()
gunzipped, err := ioutil.ReadAll(zReader)
if err != nil {
return []byte(""), err
}
return gunzipped, nil
}
func NewWebGameParams(q url.Values) *WebGameParams {
params := &WebGameParams{
Height: 50,
Width: 50,
Xmul: 10,
Ymul: 10,
}
h := q.Get("h")
if len(h) > 0 {
if i, err := strconv.Atoi(h); err == nil {
params.Height = i
}
}
w := q.Get("w")
if len(w) > 0 {
if i, err := strconv.Atoi(w); err == nil {
params.Width = i
}
}
xm := q.Get("xm")
if len(xm) > 0 {
if i, err := strconv.Atoi(xm); err == nil {
params.Xmul = i
}
}
ym := q.Get("ym")
if len(ym) > 0 {
if i, err := strconv.Atoi(ym); err == nil {
params.Ymul = i
}
}
return params
}
func handle405(method, uri string, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte(fmt.Sprintf("Nope. %v not allowed at %v\n", method, uri)))
}
func handle500(err error, w http.ResponseWriter) {
log.Println("ERROR:", err)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("We exploded!: %v\n", err)))
}
func HandleWebGameRoot(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
handle405(req.Method, req.RequestURI, w)
return
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write(WebAssets.IndexHTML)
return
}
func HandleWebGameStatic(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
handle405(req.Method, req.RequestURI, w)
return
}
if req.URL.Path == "/static/normalize.css" {
w.Header().Set("Content-Type", "text/css")
w.WriteHeader(http.StatusOK)
w.Write(WebAssets.NormalizeCSS)
return
}
if req.URL.Path == "/static/jquery.min.js" {
w.Header().Set("Content-Type", "text/javascript")
w.WriteHeader(http.StatusOK)
w.Write(WebAssets.JqueryMinJS)
return
}
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(fmt.Sprintf("Don't have: %v\n", req.URL.Path)))
}
func HandleWebGameState(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
handleWebGameStateGET(w, req)
return
case "POST":
handleWebGameStatePOST(w, req)
return
default:
handle405(req.Method, req.RequestURI, w)
return
}
}
func handleWebGameStateGET(w http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
params := NewWebGameParams(q)
game := NewGameOfLife(params.Height, params.Width)
err := game.ImportRandomState()
if err != nil {
handle500(err, w)
return
}
sendWebGameStateJSONPayload(w, http.StatusOK, game, params)
}
func handleWebGameStatePOST(w http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
params := NewWebGameParams(q)
defer func() {
r := recover()
if r != nil {
handle500(errors.New(fmt.Sprintf("OH NOES! %v", r)), w)
}
}()
body, err := ioutil.ReadAll(req.Body)
if err != nil {
handle500(err, w)
}
webGameState := &WebGameState{}
err = json.Unmarshal(body, webGameState)
if err != nil {
handle500(err, w)
}
height, width := webGameState.State.Height(), webGameState.State.Width()
game := NewGameOfLife(height, width)
game.ImportState(webGameState.State)
game.EvaluateGeneration()
sendWebGameStateJSONPayload(w, http.StatusCreated, game, params)
}
func sendWebGameStateJSONPayload(w http.ResponseWriter, status int, game *GameOfLife, params *WebGameParams) {
img, err := game.Image(params.Xmul, params.Ymul)
if err != nil {
handle500(err, w)
return
}
log.Println("Serving state image.")
var pngBuf bytes.Buffer
err = png.Encode(&pngBuf, img)
if err != nil {
handle500(err, w)
}
imgB64 := base64.StdEncoding.EncodeToString(pngBuf.Bytes())
webGameState := &WebGameState{State: game.State, Img: imgB64}
wgsJson, err := json.Marshal(webGameState)
if err != nil {
handle500(err, w)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write(wgsJson)
}
func RunWebGame(address string, height, width, sleepMs int, mutate bool) (int, error) {
http.HandleFunc("/", HandleWebGameRoot)
http.HandleFunc("/state", HandleWebGameState)
http.HandleFunc("/static/", HandleWebGameStatic)
fmt.Printf("Serving on %v\n", address)
err := http.ListenAndServe(address, nil)
if err != nil {
return 1, err
}
return 0, nil
}

View File

@ -1,646 +0,0 @@
package conway
// WARNING: GENERATED FILE, NERDS! 2012-12-26 10:57:04.843003 -0500 EST
const (
gameOfLifeIndexHtml = `
H4sIAAAJbogC/5RV32/bNhB+719xUwJIxlQpCboBsRW/dMOWYWiDxAO2p4GRzjJdilTJU1ov8P8+
Ur8tu0HjB1ni3X3f3cfjMfnhl4/vV//c/QobKsTyTdL8ASQbZJl7sa/ESeDyvZJf2M438BsrENQa
/uRrTOLG2DgKLj8B7Uq88Qi/Upwa44FGceMZ2gk0G0TyYKNxfePFhhjxNJZKF0zw/zCqveMWyqSa
lzQG27In1qx6YHQ6IGw/V6h3UcFltDXeMokbr+8BanwA0ko/WDCEG3jeL4bF2yK3S563eNOurSuZ
ElcSSlVWwkbcSk6ciTo6mMFz6wdwHuVIfzx8/BD4daLohxY7BCXv2tB7NKWSBmcd4f6IJVcr9cHm
3MDXMCEIpcpDplIZCvpvgJ5xtOZSiQxpLnO+3gXPZg61k01JVkKEcDUbu3cpBBkjFgK1SVQmhO3n
v3+/Hyfgfk9MN3hWr+MSv4myOADhawim1TU/g7TiBaqKgj4z6/WCQAuwlf10cTGh2I++9uOC/a1R
0u8XXtiUV5R3sEvWijILul4LwUVFZpRg33G1hQ8Gp+55XV1jPw/8s/rzX17k/gjBKdg7RgJlThtI
4PJQ0QlUYkGAZ+6UtpDuHPqziJWlzXilgoEvVZIYl6j92Yh2UHUgZ0Q68O1JtY3vu4LmvGA5xqXM
F4/M4M/vQh9+BJSpyvCv+9ugKXoMq5EqLVudjjbk/KAV+qDTJ3MAbWrB0taXCp5+eqmhur1y3TSB
KIUdhq+AsO2hq0Og7m06sdysnI7RblI9qmw3KtbZ39rxmcs5pCgJ9ZFMZ/2WjeIKpnNuYy6AVaSG
yr7wjDZzuLy4eld+XZykEbimY5JJdxxRvSVVzuHqcoxasiyz46gxXY8tj0pnqBuDDQGjBM/g7Pr6
emBulXNi1RdW3N1YidOo1TLjT3Vn94n1Qiaby29dadbSOY3jtRLDPjiaisiOA2d2zeAt7+wziZvl
036u77zlg31O/ZLYUh3RTlT1Tnj3rxaxrtvmX9/h/wcAAP//rQ2TxtsHAAA=`
normalizeCss = `
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`
jqueryMinJs = `
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
f8vzG5z8/wAAAP//TQkipcVtAQA=`
)

View File

@ -1,183 +0,0 @@
package main
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"text/template"
"time"
)
const b64Width = 76
var (
webAssetsTmpl = template.Must(template.New("web_assets").Parse("" +
"package conway\n" +
"// WARNING: GENERATED FILE, NERDS! {{.Now}}\n" +
"\n" +
"const (\n" +
" gameOfLifeIndexHtml = `{{.IndexHTML}}`\n" +
" normalizeCss = `{{.NormalizeCSS}}`\n" +
" jqueryMinJs = `{{.JqueryMinJS}}`\n" +
")\n"))
normalizeCssUrl = "http://necolas.github.com/normalize.css/2.0.1/normalize.css"
jqueryMinJsUrl = "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"
)
type webAssetStrings struct {
IndexHTML string
NormalizeCSS string
JqueryMinJS string
Now time.Time
}
func die(err error) {
log.Fatal("CRUD:", err)
}
func toB64(input []byte) string {
var (
err error
out bytes.Buffer
)
encoded := base64.StdEncoding.EncodeToString(input)
lenEncoded := len(encoded)
outbuf := bufio.NewWriter(&out)
inbuf := strings.NewReader(encoded)
outbuf.WriteByte('\n')
if err != nil {
die(err)
}
nWritten := 0
for {
for n := 0; n < b64Width; n++ {
c, err := inbuf.ReadByte()
if err == io.EOF {
break
}
if err != nil {
die(err)
}
err = outbuf.WriteByte(c)
if err != nil {
die(err)
}
nWritten++
}
_, err := inbuf.ReadByte()
if err == io.EOF {
break
}
if err != nil {
die(err)
}
err = inbuf.UnreadByte()
if err != nil {
die(err)
}
err = outbuf.WriteByte('\n')
if err != nil {
die(err)
}
}
if nWritten != lenEncoded {
die(errors.New(fmt.Sprintf("base64-encoded length = %d, but wrote %d",
lenEncoded, nWritten)))
}
outbuf.Flush()
return string(out.Bytes())
}
func toGz(input []byte) []byte {
var buf bytes.Buffer
zwriter, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
die(err)
}
zbuf := bufio.NewWriter(zwriter)
toWrite := len(input)
for written := 0; written < toWrite; {
n, err := zbuf.Write(input[written:])
if err != nil {
die(err)
}
written += n
}
zbuf.Flush()
zwriter.Close()
return buf.Bytes()
}
func fetchUrl(url string) []byte {
resp, err := http.Get(url)
if err != nil {
die(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
die(err)
}
return body
}
func fetchIndexHtml(indexHtmlPath string) []byte {
fd, err := os.Open(indexHtmlPath)
if err != nil {
die(err)
}
body, err := ioutil.ReadAll(fd)
if err != nil {
die(err)
}
return body
}
func toB64Gz(input []byte) string {
return string(toB64(toGz(input)))
}
func main() {
if len(os.Args) < 2 {
die(errors.New(fmt.Sprintf("Usage: %s <index.html-path>", os.Args[0])))
}
assets := &webAssetStrings{
IndexHTML: toB64Gz(fetchIndexHtml(os.Args[1])),
NormalizeCSS: toB64Gz(fetchUrl(normalizeCssUrl)),
JqueryMinJS: toB64Gz(fetchUrl(jqueryMinJsUrl)),
Now: time.Now(),
}
webAssetsTmpl.Execute(os.Stdout, assets)
}

View File

@ -1,75 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Conway's Game of Life</title>
<link type="text/css" rel="stylesheet" href="/static/normalize.css" />
<script type="text/javascript" src="/static/jquery.min.js"></script>
<script type="text/javascript">
curState = {};
curImg = "";
function populateInitialState() {
$.getJSON('/state', {}, onPopulateResponse);
}
function goToNextState(state, loop) {
$.post(
'/state',
JSON.stringify({s: state}, null, 2),
function(data, textStatus, jqXHR) {
var state = onPopulateResponse(data, textStatus, jqXHR);
if (loop) {
setTimeout(function() { goToNextState(state, loop); }, 500);
}
},
'json'
);
}
function onPopulateResponse(data, textStatus, jqXHR) {
$.extend(curState, data.s);
curImg = data.i;
var $stateImg = $('#state_img');
if ($stateImg.length < 1) {
$stateImg = $('<img id="state_img" />').appendTo($('#state_container'));
}
$stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.i));
return data.s;
}
$(function() {
populateInitialState();
$('#step').click(function() { goToNextState(curState); });
$('#play').click(function() { goToNextState(curState, true); });
});
</script>
<style type="text/css">
body {
text-align: center;
}
#container {
margin: 0 auto;
width: 1024px;
text-align: left;
}
#state_container {
margin-top: 21px;
padding-top: 9px;
border-top: 1px solid #999;
}
</style>
</head>
<body>
<div id="container">
<h1>Conway's Game of Life</h1>
<div id="controls">
<button id="play">Play</button>
<button id="step">Step</button>
</div>
<div id="state_container">
</div>
</div>
</body>
</html>