diff --git a/conway/conway_test.go b/conway/conway_test.go index e723daa..3394799 100644 --- a/conway/conway_test.go +++ b/conway/conway_test.go @@ -520,8 +520,8 @@ func TestHandleWebGameRootReturns405ForNonGET(t *testing.T) { } } -func TestHandleWebGameStateForGET(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:9775/state.png?random=1", nil) +func TestHandleWebGameStateGETReturnsRandomImage(t *testing.T) { + req, err := http.NewRequest("GET", "http://localhost:9775/state", nil) if err != nil { t.Error(err) return @@ -564,3 +564,30 @@ func TestHandleWebGameStateForGET(t *testing.T) { 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 + } +} diff --git a/conway/game_state.go b/conway/game_state.go index f34667f..0fa65c4 100644 --- a/conway/game_state.go +++ b/conway/game_state.go @@ -18,18 +18,18 @@ const ( ) type GameStateCell struct { - Value int `json:"value"` + Value int `json:"v"` X int `json:"x"` Y int `json:"y"` } type GameStateRow struct { Y int `json:"y"` - Cells []*GameStateCell `json:"cells"` + Cells []*GameStateCell `json:"c"` } type GameState struct { - Rows []*GameStateRow `json:"rows"` + Rows []*GameStateRow `json:"r"` } func NewGameState(height, width int) *GameState { @@ -197,7 +197,7 @@ func (state *GameState) Image(xMult, yMult int) (*image.Gray, error) { } square := image.Rect(cell.X*xMult, cell.Y*yMult, - (cell.X*xMult)+xMult, (cell.Y*yMult)+yMult) + (cell.X*xMult)+xMult-1, (cell.Y*yMult)+yMult-1) draw.Draw(img, square, color, image.ZP, draw.Src) } diff --git a/conway/web.go b/conway/web.go index c97e730..eee1349 100644 --- a/conway/web.go +++ b/conway/web.go @@ -4,8 +4,10 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "fmt" "image/png" + "io/ioutil" "log" "math/rand" "net/http" @@ -15,7 +17,6 @@ import ( ) type WebGameParams struct { - Random bool Height int Width int Xmul int @@ -23,8 +24,8 @@ type WebGameParams struct { } type WebGameState struct { - State *GameState `json:"state"` - Img string `json:"img"` + State *GameState `json:"s"` + Img string `json:"i"` } var jQueryMinJs = []byte("") @@ -46,10 +47,6 @@ func NewWebGameParams(q url.Values) *WebGameParams { Ymul: 10, } - if len(q.Get("random")) > 0 { - params.Random = true - } - h := q.Get("h") if len(h) > 0 { if i, err := strconv.Atoi(h); err == nil { @@ -132,73 +129,91 @@ func HandleWebGameStatic(w http.ResponseWriter, req *http.Request) { } func HandleWebGameState(w http.ResponseWriter, req *http.Request) { - if req.Method != "GET" { + 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) - if params.Random { - game := NewGameOfLife(params.Height, params.Width) - err := game.ImportRandomState() - if err != nil { - handle500(err, w) - return - } + game := NewGameOfLife(params.Height, params.Width) + err := game.ImportRandomState() + if err != nil { + handle500(err, w) + return + } + + sendWebGameStateJSONPayload(w, http.StatusOK, game, params) +} - img, err := game.Image(params.Xmul, params.Ymul) +func handleWebGameStatePOST(w http.ResponseWriter, req *http.Request) { + q := req.URL.Query() + params := NewWebGameParams(q) - if err != nil { - handle500(err, w) - return + 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) + } - log.Println("Serving random state image.") + webGameState := &WebGameState{} + err = json.Unmarshal(body, webGameState) + if err != nil { + handle500(err, w) + } - var pngBuf bytes.Buffer - err = png.Encode(&pngBuf, img) - if err != nil { - handle500(err, w) - } + height, width := webGameState.State.Height(), webGameState.State.Width() + game := NewGameOfLife(height, width) + game.ImportState(webGameState.State) - imgB64 := base64.StdEncoding.EncodeToString(pngBuf.Bytes()) - webGameState := &WebGameState{State: game.State, Img: imgB64} - wgsJson, err := json.Marshal(webGameState) + game.EvaluateGeneration() - if err != nil { - handle500(err, w) - } + sendWebGameStateJSONPayload(w, http.StatusCreated, game, params) +} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(wgsJson) +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 } - rawState := q.Get("s") - if len(rawState) < 1 { - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Missing query param \"s\".\n")) - return + log.Println("Serving state image.") + + var pngBuf bytes.Buffer + err = png.Encode(&pngBuf, img) + if err != nil { + handle500(err, w) } - //state := &GameState{} - //err := json.Unmarshal([]byte(rawState), state) - //if err != nil { - //w.Header().Set("Content-Type", "text/plain") - //w.WriteHeader(http.StatusBadRequest) - //w.Write([]byte(fmt.Sprintf("Invalid query param \"s\": %v\n", err))) - //return - //} + 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(http.StatusOK) - w.Write([]byte(`{"state":null,"img":""}`)) - return + w.WriteHeader(status) + w.Write(wgsJson) } func RunWebGame(address string, height, width, sleepMs int, mutate bool) (int, error) { diff --git a/conway/web_assets.go b/conway/web_assets.go index b43e6d3..91f14cb 100644 --- a/conway/web_assets.go +++ b/conway/web_assets.go @@ -12,29 +12,41 @@ const ( curState = {}; curImg = ""; - function populateState(stateOptions) { - $.getJSON('/state', stateOptions, onPopulateResponse); + function populateInitialState() { + $.getJSON('/state', {}, onPopulateResponse); } + function goToNextState(state) { + $.post('/state', JSON.stringify({s: state}, null, 2), onPopulateResponse, 'json'); + } + + function playGame() { + goToNextState(curState); + setTimeout(playGame, 500); + } + function onPopulateResponse(data, textStatus, jqXHR) { - $.extend(curState, data.state); - curImg = data.img; + $.extend(curState, data.s); + curImg = data.i; var $stateImg = $('#state_img'); if ($stateImg.length < 1) { $stateImg = $('').appendTo($('#state_container')); } - $stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.img)); + $stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.i)); } $(function() { - populateState({random: '1'}) + populateInitialState(); + $('#step').click(function() { goToNextState(curState); }); + $('#play').click(playGame); });

Conway's Game of Life

- + +