summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-09-14 19:48:36 +0200
committerewy <ewy0@protonmail.com>2026-09-14 19:48:36 +0200
commit899ef7448222f0d087c194b3379b702f8dd2ff8c (patch)
tree8c2b508bb4d01236c7f582e4eee91fd178d09670 /server
parentc69e6c0770359e2febafcf487eb21c4aa608c3e9 (diff)
add server and websocket comms
Diffstat (limited to 'server')
-rw-r--r--server/api/game.go79
-rw-r--r--server/server.go34
-rw-r--r--server/web/index.gohtml24
3 files changed, 135 insertions, 2 deletions
diff --git a/server/api/game.go b/server/api/game.go
new file mode 100644
index 0000000..bccf12a
--- /dev/null
+++ b/server/api/game.go
@@ -0,0 +1,79 @@
+package api
+
+import (
+ "github.com/gorilla/websocket"
+ "net/http"
+ "sakura/engine"
+ "slices"
+ "uuid"
+)
+
+type Conn struct {
+ Socket *websocket.Conn
+}
+
+var upgrader = websocket.Upgrader{}
+
+var games = make(map[uuid.UUID]*engine.Game)
+var connections = make(map[uuid.UUID][]*Conn)
+
+func GameCreate(w http.ResponseWriter, r *http.Request) {
+ id := uuid.NewV7()
+ games[id] = engine.NewGame()
+ w.Write([]byte(id.String()))
+}
+
+func GameConnect(w http.ResponseWriter, r *http.Request) {
+ id := r.PathValue("id")
+ uid, err := uuid.Parse(id)
+ if err != nil {
+ http.Error(w, "unable to parse game uuid "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ game := games[uid]
+ if game == nil {
+ http.Error(w, "game not found", http.StatusNotFound)
+ return
+ }
+ wsConn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if wsConn == nil {
+ http.Error(w, "unable to initialize websocket", http.StatusInternalServerError)
+ return
+ }
+ conn := &Conn{wsConn}
+ connections[uid] = append(connections[uid], conn)
+ wsConn.SetCloseHandler(func(code int, text string) error {
+ connections[uid] = slices.DeleteFunc(connections[uid], func(potential *Conn) bool {
+ return potential == conn
+ })
+ return nil
+ })
+ err = wsConn.WriteJSON(game)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ go PumpRead(uid, game, wsConn)
+}
+
+func Update(id uuid.UUID, g *engine.Game) {
+ for _, c := range connections[id] {
+ _ = c.Socket.WriteJSON(g)
+ }
+}
+
+func PumpRead(id uuid.UUID, g *engine.Game, conn *websocket.Conn) {
+ var interaction engine.InteractionID
+ for {
+ err := conn.ReadJSON(&interaction)
+ if err != nil {
+ break
+ }
+ g.Actions[interaction].Then(g)
+ Update(id, g)
+ }
+}
diff --git a/server/server.go b/server/server.go
index 8230f87..798bab6 100644
--- a/server/server.go
+++ b/server/server.go
@@ -1,11 +1,41 @@
package server
import (
+ "embed"
"fmt"
- "sakura/data"
+ "github.com/spf13/pflag"
+ "html/template"
+ "net"
+ "net/http"
+ "sakura/cfg"
+ "sakura/server/api"
+ "strconv"
)
+//go:embed web
+var web embed.FS
+
+var tmpl *template.Template
+
func Start() {
+ pflag.Parse()
fmt.Println("Server started")
- fmt.Printf("Characters: %v\n", data.Characters)
+ addr := net.JoinHostPort(*cfg.Host, strconv.Itoa(*cfg.Port))
+ fmt.Printf("Listening on %s\n", addr)
+ var err error
+ tmpl, err = template.ParseFS(web, "web/**.gohtml")
+ http.HandleFunc("/{$}", serveHome)
+ http.HandleFunc(cfg.CreateMethod+" "+cfg.CreatePath, api.GameCreate)
+ http.HandleFunc(cfg.ConnectMethod+" "+cfg.ConnectPath, api.GameConnect)
+ err = http.ListenAndServe(addr, nil)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ err := tmpl.ExecuteTemplate(w, "index.gohtml", nil)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
}
diff --git a/server/web/index.gohtml b/server/web/index.gohtml
new file mode 100644
index 0000000..bf60372
--- /dev/null
+++ b/server/web/index.gohtml
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>Document</title>
+</head>
+<body>
+
+<script>
+ const start = async () => {
+ const game = await fetch("/api/game/create")
+ const gameId = await game.text()
+ const connection = new WebSocket(`/api/game/${gameId}/connect`)
+ connection.addEventListener("message", (ev) => {
+ console.debug(JSON.parse(ev.data))
+ })
+ }
+ start()
+</script>
+</body>
+</html> \ No newline at end of file