273 lines
5.5 KiB
Go
273 lines
5.5 KiB
Go
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
|
|
}
|