Getting the web implementation mostly working!

cat-town
Dan Buch 12 years ago
parent 6818d0ee01
commit 4c44252a49

@ -520,8 +520,8 @@ func TestHandleWebGameRootReturns405ForNonGET(t *testing.T) {
} }
} }
func TestHandleWebGameStateForGET(t *testing.T) { func TestHandleWebGameStateGETReturnsRandomImage(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:9775/state.png?random=1", nil) req, err := http.NewRequest("GET", "http://localhost:9775/state", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
@ -564,3 +564,30 @@ func TestHandleWebGameStateForGET(t *testing.T) {
return 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
}
}

@ -18,18 +18,18 @@ const (
) )
type GameStateCell struct { type GameStateCell struct {
Value int `json:"value"` Value int `json:"v"`
X int `json:"x"` X int `json:"x"`
Y int `json:"y"` Y int `json:"y"`
} }
type GameStateRow struct { type GameStateRow struct {
Y int `json:"y"` Y int `json:"y"`
Cells []*GameStateCell `json:"cells"` Cells []*GameStateCell `json:"c"`
} }
type GameState struct { type GameState struct {
Rows []*GameStateRow `json:"rows"` Rows []*GameStateRow `json:"r"`
} }
func NewGameState(height, width int) *GameState { 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, 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) draw.Draw(img, square, color, image.ZP, draw.Src)
} }

@ -4,8 +4,10 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"image/png" "image/png"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
@ -15,7 +17,6 @@ import (
) )
type WebGameParams struct { type WebGameParams struct {
Random bool
Height int Height int
Width int Width int
Xmul int Xmul int
@ -23,8 +24,8 @@ type WebGameParams struct {
} }
type WebGameState struct { type WebGameState struct {
State *GameState `json:"state"` State *GameState `json:"s"`
Img string `json:"img"` Img string `json:"i"`
} }
var jQueryMinJs = []byte("") var jQueryMinJs = []byte("")
@ -46,10 +47,6 @@ func NewWebGameParams(q url.Values) *WebGameParams {
Ymul: 10, Ymul: 10,
} }
if len(q.Get("random")) > 0 {
params.Random = true
}
h := q.Get("h") h := q.Get("h")
if len(h) > 0 { if len(h) > 0 {
if i, err := strconv.Atoi(h); err == nil { 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) { 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) handle405(req.Method, req.RequestURI, w)
return return
} }
}
func handleWebGameStateGET(w http.ResponseWriter, req *http.Request) {
q := req.URL.Query() q := req.URL.Query()
params := NewWebGameParams(q) params := NewWebGameParams(q)
if params.Random { game := NewGameOfLife(params.Height, params.Width)
game := NewGameOfLife(params.Height, params.Width) err := game.ImportRandomState()
err := game.ImportRandomState() if err != nil {
if err != nil { handle500(err, w)
handle500(err, w) return
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 { defer func() {
handle500(err, w) r := recover()
return 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 height, width := webGameState.State.Height(), webGameState.State.Width()
err = png.Encode(&pngBuf, img) game := NewGameOfLife(height, width)
if err != nil { game.ImportState(webGameState.State)
handle500(err, w)
}
imgB64 := base64.StdEncoding.EncodeToString(pngBuf.Bytes()) game.EvaluateGeneration()
webGameState := &WebGameState{State: game.State, Img: imgB64}
wgsJson, err := json.Marshal(webGameState)
if err != nil { sendWebGameStateJSONPayload(w, http.StatusCreated, game, params)
handle500(err, w) }
}
w.Header().Set("Content-Type", "application/json") func sendWebGameStateJSONPayload(w http.ResponseWriter, status int, game *GameOfLife, params *WebGameParams) {
w.WriteHeader(http.StatusOK) img, err := game.Image(params.Xmul, params.Ymul)
w.Write(wgsJson)
if err != nil {
handle500(err, w)
return return
} }
rawState := q.Get("s") log.Println("Serving state image.")
if len(rawState) < 1 {
w.Header().Set("Content-Type", "text/plain") var pngBuf bytes.Buffer
w.WriteHeader(http.StatusBadRequest) err = png.Encode(&pngBuf, img)
w.Write([]byte("Missing query param \"s\".\n")) if err != nil {
return handle500(err, w)
} }
//state := &GameState{} imgB64 := base64.StdEncoding.EncodeToString(pngBuf.Bytes())
//err := json.Unmarshal([]byte(rawState), state) webGameState := &WebGameState{State: game.State, Img: imgB64}
//if err != nil { wgsJson, err := json.Marshal(webGameState)
//w.Header().Set("Content-Type", "text/plain")
//w.WriteHeader(http.StatusBadRequest) if err != nil {
//w.Write([]byte(fmt.Sprintf("Invalid query param \"s\": %v\n", err))) handle500(err, w)
//return }
//}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(status)
w.Write([]byte(`{"state":null,"img":""}`)) w.Write(wgsJson)
return
} }
func RunWebGame(address string, height, width, sleepMs int, mutate bool) (int, error) { func RunWebGame(address string, height, width, sleepMs int, mutate bool) (int, error) {

@ -12,29 +12,41 @@ const (
curState = {}; curState = {};
curImg = ""; curImg = "";
function populateState(stateOptions) { function populateInitialState() {
$.getJSON('/state', stateOptions, onPopulateResponse); $.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) { function onPopulateResponse(data, textStatus, jqXHR) {
$.extend(curState, data.state); $.extend(curState, data.s);
curImg = data.img; curImg = data.i;
var $stateImg = $('#state_img'); var $stateImg = $('#state_img');
if ($stateImg.length < 1) { if ($stateImg.length < 1) {
$stateImg = $('<img id="state_img" />').appendTo($('#state_container')); $stateImg = $('<img id="state_img" />').appendTo($('#state_container'));
} }
$stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.img)); $stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.i));
} }
$(function() { $(function() {
populateState({random: '1'}) populateInitialState();
$('#step').click(function() { goToNextState(curState); });
$('#play').click(playGame);
}); });
</script> </script>
</head> </head>
<body> <body>
<h1>Conway's Game of Life</h1> <h1>Conway's Game of Life</h1>
<div id="controls"> <div id="controls">
<button id="next">Next</button> <button id="step">Step</button>
<button id="play">Play</button>
</div> </div>
<hr /> <hr />
<div id="state_container"> <div id="state_container">

Loading…
Cancel
Save