package api import ( "net/http" "sakura/data/cirno" "sakura/engine" "slices" "uuid" "github.com/gorilla/websocket" ) 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() g := engine.NewGame() games[id] = g g.Yin.SetCharacter(cirno.Cirno) g.Yang.SetCharacter(cirno.Cirno) 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 } p, c := Bind(game, w, r) wsConn, err := upgrader.Upgrade(w, r, http.Header{ "Set-Cookie": []string{c.String()}, }) 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 }) go PumpRead(uid, game, p, wsConn) } func Bind(g *engine.Game, w http.ResponseWriter, r *http.Request) (*engine.Player, *http.Cookie) { cookie, err := r.Cookie("User") newUser := false var id string if err != nil { newUser = true uid := uuid.NewV7() id = uid.String() cookie = &http.Cookie{ Name: "User", Value: id, Quoted: true, Secure: true, HttpOnly: false, Path: "/", SameSite: http.SameSiteStrictMode, } http.SetCookie(w, cookie) } else { id = cookie.Value } switch { case newUser && g.Yin.Cookie == "": g.Yin.Cookie = id return g.Yin, cookie case newUser && g.Yang.Cookie == "": g.Yang.Cookie = id return g.Yang, cookie case g.Yang.Cookie == id: return g.Yang, cookie case g.Yin.Cookie == id: return g.Yin, cookie default: return nil, cookie } } 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, p *engine.Player, conn *websocket.Conn) { var interaction engine.InteractionID for { strippedGame := g.Present(p) Update(id, &strippedGame) err := conn.ReadJSON(&interaction) if err != nil { break } a := g.Actions[interaction] if a == nil { continue } a.Then(g) g.ActionToHistory(a) } }