Getting the web implementation mostly working!
This commit is contained in:
parent
6818d0ee01
commit
4c44252a49
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
133
conway/web.go
133
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
|
||||
}
|
||||
|
||||
img, err := game.Image(params.Xmul, params.Ymul)
|
||||
|
||||
if err != nil {
|
||||
handle500(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Serving random 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(http.StatusOK)
|
||||
w.Write(wgsJson)
|
||||
|
||||
game := NewGameOfLife(params.Height, params.Width)
|
||||
err := game.ImportRandomState()
|
||||
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"))
|
||||
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
|
||||
}
|
||||
|
||||
//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
|
||||
//}
|
||||
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(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) {
|
||||
|
@ -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 = $('<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() {
|
||||
populateState({random: '1'})
|
||||
populateInitialState();
|
||||
$('#step').click(function() { goToNextState(curState); });
|
||||
$('#play').click(playGame);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Conway's Game of Life</h1>
|
||||
<div id="controls">
|
||||
<button id="next">Next</button>
|
||||
<button id="step">Step</button>
|
||||
<button id="play">Play</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="state_container">
|
||||
|
Loading…
Reference in New Issue
Block a user