Update via to v0.4.0 and decouple tick rate from snake speed
Use via.OnKeyDownMap for snake keybindings, replacing the manual dataExpr/rawDataAttr workaround. Window-scoped key handling removes the need for tabindex/focus hacks, and WithPreventDefault on arrow keys prevents page scrolling during gameplay. Introduce a 60 FPS tick loop with a separate snake movement speed (7 cells/s) so direction input is polled every frame but game state only advances at the configured rate.
This commit is contained in:
74
main.go
74
main.go
@@ -1,19 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
g "maragu.dev/gomponents"
|
||||
|
||||
"github.com/ryanhamamura/c4/auth"
|
||||
"github.com/ryanhamamura/c4/db"
|
||||
@@ -33,37 +27,6 @@ var queries *gen.Queries
|
||||
//go:embed assets/css/output.css
|
||||
var daisyUICSS []byte
|
||||
|
||||
// dataExpr renders an h.H attribute node and extracts the raw Datastar expression.
|
||||
// h.Data/h.Attr HTML-escape values, so we unescape to get the original expression.
|
||||
func dataExpr(node h.H) string {
|
||||
var buf bytes.Buffer
|
||||
node.Render(&buf)
|
||||
s := buf.String()
|
||||
start := strings.Index(s, `="`) + 2
|
||||
end := strings.LastIndex(s, `"`)
|
||||
if start < 2 || end <= start {
|
||||
return ""
|
||||
}
|
||||
return html.UnescapeString(s[start:end])
|
||||
}
|
||||
|
||||
// rawDataAttr outputs an unescaped data attribute. Needed because gomponents
|
||||
// HTML-escapes attribute values, which double-escapes expressions extracted
|
||||
// from rendered nodes. Implements gomponents' attribute interface so it
|
||||
// renders in the element's opening tag rather than as a child.
|
||||
type rawDataAttr struct {
|
||||
name, value string
|
||||
}
|
||||
|
||||
func (a rawDataAttr) Render(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, ` %s="%s"`, a.name, a.value)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a rawDataAttr) Type() g.NodeType {
|
||||
return g.AttributeType
|
||||
}
|
||||
|
||||
func DaisyUIPlugin(v *via.V) {
|
||||
v.HTTPServeMux().HandleFunc("GET /_plugins/daisyui/style.css", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
@@ -627,35 +590,18 @@ func main() {
|
||||
content = append(content, ui.SnakeInviteLink(sg.ID))
|
||||
}
|
||||
|
||||
// Build keydown attributes with unique __suffix names so the
|
||||
// browser doesn't deduplicate them (all share data-on:keydown).
|
||||
type keyBinding struct {
|
||||
suffix string
|
||||
key string
|
||||
dir snake.Direction
|
||||
}
|
||||
bindings := []keyBinding{
|
||||
{"arrowup", "ArrowUp", snake.DirUp},
|
||||
{"arrowdown", "ArrowDown", snake.DirDown},
|
||||
{"arrowleft", "ArrowLeft", snake.DirLeft},
|
||||
{"arrowright", "ArrowRight", snake.DirRight},
|
||||
{"w", "w", snake.DirUp},
|
||||
{"s", "s", snake.DirDown},
|
||||
{"a", "a", snake.DirLeft},
|
||||
{"d", "d", snake.DirRight},
|
||||
}
|
||||
|
||||
wrapperAttrs := []h.H{
|
||||
h.Class("snake-wrapper flex flex-col items-center gap-4 p-4"),
|
||||
h.Attr("tabindex", "0"),
|
||||
h.Data("on:load", "this.focus()"),
|
||||
}
|
||||
for _, kb := range bindings {
|
||||
expr := dataExpr(handleDir.OnKeyDown(kb.key, via.WithSignalInt(dirSignal, int(kb.dir))))
|
||||
wrapperAttrs = append(wrapperAttrs, h.H(rawDataAttr{
|
||||
name: "data-on:keydown__" + kb.suffix,
|
||||
value: expr,
|
||||
}))
|
||||
via.OnKeyDownMap(
|
||||
via.KeyBind("w", handleDir, via.WithSignalInt(dirSignal, int(snake.DirUp))),
|
||||
via.KeyBind("a", handleDir, via.WithSignalInt(dirSignal, int(snake.DirLeft))),
|
||||
via.KeyBind("s", handleDir, via.WithSignalInt(dirSignal, int(snake.DirDown))),
|
||||
via.KeyBind("d", handleDir, via.WithSignalInt(dirSignal, int(snake.DirRight))),
|
||||
via.KeyBind("ArrowUp", handleDir, via.WithSignalInt(dirSignal, int(snake.DirUp)), via.WithPreventDefault()),
|
||||
via.KeyBind("ArrowLeft", handleDir, via.WithSignalInt(dirSignal, int(snake.DirLeft)), via.WithPreventDefault()),
|
||||
via.KeyBind("ArrowDown", handleDir, via.WithSignalInt(dirSignal, int(snake.DirDown)), via.WithPreventDefault()),
|
||||
via.KeyBind("ArrowRight", handleDir, via.WithSignalInt(dirSignal, int(snake.DirRight)), via.WithPreventDefault()),
|
||||
),
|
||||
}
|
||||
|
||||
wrapperAttrs = append(wrapperAttrs, content...)
|
||||
|
||||
Reference in New Issue
Block a user