wgoodall01/shikaku

View on GitHub
www/solve.go

Summary

Maintainability
B
6 hrs
Test Coverage
package main

import (
    "bytes"
    "fmt"
    "html/template"
    "net/http"
    "strconv"
    "time"

    raven "github.com/getsentry/raven-go"
    "github.com/wgoodall01/shikaku"
)

type solveHandler struct {
    http.Handler

    tmpl *template.Template
}

func Solve() http.Handler {
    tmpl := LoadTemplateFuncs("solve", map[string]interface{}{
        "add": func(a, b int) int {
            return a + b
        },
    })

    return &solveHandler{
        tmpl: tmpl,
    }
}

func (h *solveHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    if err := r.ParseForm(); err != nil {
        WriteError(w, 400, "Couldn't parse form", err)
        return
    }

    if r.Form["rows"] == nil || r.Form["cols"] == nil {
        WriteError(w, 400, "Invalid request", nil)
        return
    }

    rows, err := strconv.Atoi(r.Form["rows"][0])
    if err != nil {
        WriteError(w, 400, "Invalid number of rows", err)
        return
    }

    cols, err := strconv.Atoi(r.Form["cols"][0])
    if err != nil {
        WriteError(w, 400, "Invalid num. cols", err)
        return
    }

    if cols > 40 || rows > 40 {
        WriteError(w, 400, fmt.Sprintf("%d by %d board is way too large.", rows, cols), nil)
        return
    }

    // Allocate board
    bo := &shikaku.Board{}
    for r := 0; r < rows; r++ {
        bo.Grid = append(bo.Grid, make([]shikaku.Square, cols))
    }

    // Error handling: send bad boards to Sentry with panics
    defer func(bo *shikaku.Board) {
        if thrown := recover(); thrown != nil {
            err, ok := thrown.(error)
            if !ok {
                panic(err)
            }
            raven.CaptureErrorAndWait(err, map[string]string{
                "board": bo.DebugString(),
            })
            WriteError(w, 500, "Internal solve error", err)
            return
        }
    }(bo)

    for i, valStr := range r.Form["e"] {
        r := int(i / cols)
        c := i % cols
        if len(valStr) == 0 {
            bo.Grid[r][c] = shikaku.NewBlank()
        } else {
            val, err := strconv.Atoi(valStr)
            if err != nil {
                WriteError(w, 400, fmt.Sprintf("Bad square at [%d,%d]", c, r), err)
                return
            }

            if val > rows*cols {
                WriteError(w, 400, fmt.Sprintf("Square [%d,%d] is too large", c, r), nil)
                return
            }

            bo.Grid[r][c] = shikaku.NewGiven(val)
        }
    }

    // Solve the puzzle
    tStart := time.Now()
    solveErr := bo.Solve()
    duration := time.Since(tStart).Seconds() / 1000 // in ms

    // Build the table.
    buf := bytes.Buffer{}
    fmt.Fprint(&buf, "<thead>")
    fmt.Fprint(&buf, "<td></td>")
    for i := 0; i < bo.Width(); i++ {
        fmt.Fprintf(&buf, `<td class="solve_label">%d</td>`, i+1)
    }
    fmt.Fprint(&buf, "</thead>")

    var pos shikaku.Vec2
    fmt.Fprint(&buf, "<tbody>")
    for pos[1] = 0; pos[1] < bo.Height(); pos[1]++ {
        fmt.Fprint(&buf, "<tr>")
        fmt.Fprintf(&buf, `<td class="solve_label">%d</td>`, pos[1]+1)
        for pos[0] = 0; pos[0] < bo.Width(); pos[0]++ {
            sq := bo.Get(pos)
            if shikaku.IsNotFinal(*sq) {
                // Write empty square
                fmt.Fprint(&buf, `<td class="solve_empty"></td>`)
            } else if sq.Final.A == pos {
                // It's the top-left, write a cell w/ colspan and rowspan.
                colspan := sq.Final.Width()
                rowspan := sq.Final.Height()
                fmt.Fprintf(
                    &buf,
                    `<td class="solve_rect" colspan="%d" rowspan="%d">%d</td>`,
                    colspan,
                    rowspan,
                    bo.Get(sq.Final.Given).Area,
                )
            }
        }
        fmt.Fprint(&buf, "</tr>")
    }
    fmt.Fprintf(&buf, "</tbody>")

    viewState := struct {
        Err      error
        Soln     template.HTML
        Small    bool
        Duration float64
    }{
        Err:      solveErr,
        Soln:     template.HTML(buf.String()),
        Small:    rows > 15 || cols > 15,
        Duration: duration,
    }

    if err := h.tmpl.Execute(w, viewState); err != nil {
        panic(err)
    }
}