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) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
conway/web.go
133
conway/web.go
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rawState := q.Get("s")
|
sendWebGameStateJSONPayload(w, http.StatusOK, game, params)
|
||||||
if len(rawState) < 1 {
|
}
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
func handleWebGameStatePOST(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Write([]byte("Missing query param \"s\".\n"))
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//state := &GameState{}
|
log.Println("Serving state image.")
|
||||||
//err := json.Unmarshal([]byte(rawState), state)
|
|
||||||
//if err != nil {
|
var pngBuf bytes.Buffer
|
||||||
//w.Header().Set("Content-Type", "text/plain")
|
err = png.Encode(&pngBuf, img)
|
||||||
//w.WriteHeader(http.StatusBadRequest)
|
if err != nil {
|
||||||
//w.Write([]byte(fmt.Sprintf("Invalid query param \"s\": %v\n", err)))
|
handle500(err, w)
|
||||||
//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.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…
Reference in New Issue
Block a user