Files
go-maze/main.go

219 lines
5.0 KiB
Go

package main
import (
"fmt"
"html/template"
"image/color"
"image/png"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"test-maze/maze"
)
const (
defaultWidth = 41
defaultHeight = 41
defaultScale = 8
)
type mazeParams struct {
Width int
Height int
Scale int
Seed uint64
Highlight bool
WallR uint8
WallG uint8
WallB uint8
PathR uint8
PathG uint8
PathB uint8
SolveR uint8
SolveG uint8
SolveB uint8
}
var pageTmpl = template.Must(template.ParseFiles("templates/index.html"))
type pageData struct {
mazeParams
MazeURL string
Query string
}
func main() {
http.HandleFunc("/", handleIndex)
http.HandleFunc("/maze.png", handleMazePNG)
addr := ":8080"
log.Printf("listening on http://localhost%s", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
}
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
params := parseParams(r.URL.Query())
query := params.encode().Encode()
data := pageData{
mazeParams: params,
MazeURL: "/maze.png?" + query,
Query: query,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := pageTmpl.Execute(w, data); err != nil {
http.Error(w, "template rendering failed", http.StatusInternalServerError)
}
}
func handleMazePNG(w http.ResponseWriter, r *http.Request) {
params := parseParams(r.URL.Query())
grid, err := maze.GenerateWithSeed(params.Width, params.Height, params.Seed)
if err != nil {
http.Error(w, fmt.Sprintf("generate maze: %v", err), http.StatusBadRequest)
return
}
options := maze.DefaultRenderOptions()
options.Scale = params.Scale
options.HighlightPath = params.Highlight
options.WallColor = color.RGBA{R: params.WallR, G: params.WallG, B: params.WallB, A: 255}
options.PathColor = color.RGBA{R: params.PathR, G: params.PathG, B: params.PathB, A: 255}
options.SolutionColor = color.RGBA{R: params.SolveR, G: params.SolveG, B: params.SolveB, A: 255}
img, err := maze.RenderImage(grid, options)
if err != nil {
http.Error(w, fmt.Sprintf("render maze: %v", err), http.StatusBadRequest)
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "image/png")
if err := png.Encode(w, img); err != nil {
http.Error(w, "encoding png failed", http.StatusInternalServerError)
}
}
func parseParams(q url.Values) mazeParams {
defaults := maze.DefaultRenderOptions()
defaultSeed := uint64(time.Now().UnixNano())
out := mazeParams{
Width: parseOddInt(q.Get("w"), defaultWidth, 3, 151),
Height: parseOddInt(q.Get("h"), defaultHeight, 3, 151),
Scale: parseInt(q.Get("scale"), defaultScale, 2, 24),
Seed: parseUint64(q.Get("seed"), defaultSeed),
Highlight: parseBool(q.Get("solve")),
WallR: defaults.WallColor.R,
WallG: defaults.WallColor.G,
WallB: defaults.WallColor.B,
PathR: defaults.PathColor.R,
PathG: defaults.PathColor.G,
PathB: defaults.PathColor.B,
SolveR: defaults.SolutionColor.R,
SolveG: defaults.SolutionColor.G,
SolveB: defaults.SolutionColor.B,
}
out.WallR = parseUint8(q.Get("wall_r"), out.WallR)
out.WallG = parseUint8(q.Get("wall_g"), out.WallG)
out.WallB = parseUint8(q.Get("wall_b"), out.WallB)
out.PathR = parseUint8(q.Get("path_r"), out.PathR)
out.PathG = parseUint8(q.Get("path_g"), out.PathG)
out.PathB = parseUint8(q.Get("path_b"), out.PathB)
out.SolveR = parseUint8(q.Get("solve_r"), out.SolveR)
out.SolveG = parseUint8(q.Get("solve_g"), out.SolveG)
out.SolveB = parseUint8(q.Get("solve_b"), out.SolveB)
return out
}
func (p mazeParams) encode() url.Values {
v := url.Values{}
v.Set("w", strconv.Itoa(p.Width))
v.Set("h", strconv.Itoa(p.Height))
v.Set("scale", strconv.Itoa(p.Scale))
v.Set("seed", strconv.FormatUint(p.Seed, 10))
if p.Highlight {
v.Set("solve", "1")
}
v.Set("wall_r", strconv.Itoa(int(p.WallR)))
v.Set("wall_g", strconv.Itoa(int(p.WallG)))
v.Set("wall_b", strconv.Itoa(int(p.WallB)))
v.Set("path_r", strconv.Itoa(int(p.PathR)))
v.Set("path_g", strconv.Itoa(int(p.PathG)))
v.Set("path_b", strconv.Itoa(int(p.PathB)))
v.Set("solve_r", strconv.Itoa(int(p.SolveR)))
v.Set("solve_g", strconv.Itoa(int(p.SolveG)))
v.Set("solve_b", strconv.Itoa(int(p.SolveB)))
return v
}
func parseInt(raw string, fallback, min, max int) int {
n, err := strconv.Atoi(raw)
if err != nil {
return fallback
}
if n < min {
return min
}
if n > max {
return max
}
return n
}
func parseOddInt(raw string, fallback, min, max int) int {
n := parseInt(raw, fallback, min, max)
if n%2 == 0 {
if n == max {
n--
} else {
n++
}
}
return n
}
func parseUint8(raw string, fallback uint8) uint8 {
n, err := strconv.Atoi(raw)
if err != nil {
return fallback
}
if n < 0 {
return 0
}
if n > 255 {
return 255
}
return uint8(n)
}
func parseUint64(raw string, fallback uint64) uint64 {
n, err := strconv.ParseUint(raw, 10, 64)
if err != nil {
return fallback
}
return n
}
func parseBool(raw string) bool {
raw = strings.TrimSpace(strings.ToLower(raw))
switch raw {
case "1", "true", "t", "yes", "y", "on":
return true
default:
return false
}
}