diff --git a/cmd/server/ws.go b/cmd/server/ws.go index cce5c60..91cc414 100644 --- a/cmd/server/ws.go +++ b/cmd/server/ws.go @@ -17,136 +17,154 @@ func isWS(r *http.Request) bool { return r.URL.Path == "/ws" || strings.HasPrefix(r.URL.Path, "/ws/") } -func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error { - ctx, can := context.WithCancel(r.Context()) - defer can() +type WS struct { + *S + c *websocket.Conn +} +func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error { c, err := websocket.Accept(w, r, nil) if err != nil { return err } defer c.CloseNow() + ws := WS{S: s, c: c} + return ws.serve(w, r) +} - ugs, err := NewUserGameServer(ctx, s.Session(ctx), s.games) +func (ws WS) serve(w http.ResponseWriter, r *http.Request) error { + ctx, can := context.WithCancel(r.Context()) + defer can() + r = r.WithContext(ctx) + + ugs, err := ws.newUserGameServer(ctx) if err != nil { return err } go ugs.Listen(ctx, can, func(ctx context.Context) ([]byte, error) { - _, b, err := c.Read(ctx) + _, b, err := ws.c.Read(ctx) return b, err }) for ugs.More(ctx) == nil { - gameState, err := ugs.State(ctx) - if err != nil { - return err - } - - msg := map[string]any{ - "help": strings.Join([]string{ - "CARD ASSASSINS (Mobile Ed.)", - "", - "1. Get any target to say any of his or her kill words.", - "2. Click the word to collect points.", - "3. Review new kill words.", - "", - "The game ends when everyone has been assassinated.", - }, "
"), - } - - if gameState.Started { - msg["page"] = "B" - if gameState.Completed.IsZero() { - msg["event"] = "A" - items := []map[string]any{} - for k, v := range gameState.Players { - if k == s.Session(ctx).ID { - continue - } - - name, err := s.games.UserName(ctx, k) - if err != nil { - return err - } - - tags := []map[string]any{} - if self := gameState.Players[s.Session(ctx).ID]; self.KillWords.Assignee == k { - for _, private := range v.KillWords.Assignment.Private { - tags = append(tags, map[string]any{ - "k": private.Word, - "v": private.Points, - }) - } - } - for _, public := range v.KillWords.Assignment.Public { - tags = append(tags, map[string]any{ - "k": public.Word, - "v": public.Points, - }) - } - if self := gameState.Players[s.Session(ctx).ID]; !slices.ContainsFunc(self.Kills, func(a Kill) bool { - return a.Victim == k - }) { - tags = append(tags, map[string]any{ - "k": self.KillWords.Global.Word, - "v": self.KillWords.Global.Points, - }) - } - - items = append(items, map[string]any{ - "name": name, - "title": strconv.Itoa(v.Points()), - "tags": tags, - }) - } - slices.SortFunc(items, func(a, b map[string]any) int { - an, _ := strconv.Atoi(fmt.Sprint(a["title"])) - bn, _ := strconv.Atoi(fmt.Sprint(b["title"])) - return an - bn - }) - return io.EOF - } else { - msg["event"] = "B" - items := []map[string]any{} - for k, v := range gameState.Players { - name, err := s.games.UserName(ctx, k) - if err != nil { - return err - } - tags := []map[string]any{} - for _, kill := range v.Kills { - tags = append(tags, map[string]any{ - "k": kill.KillWord.Word, - "v": kill.Victim, - }) - } - items = append(items, map[string]any{ - "name": name, - "title": fmt.Sprint(v.Points()), - "tags": tags, - }) - } - msg["items"] = items - } - } else { - msg["page"] = "A" - items := []map[string]any{} - for k := range gameState.Players { - name, err := s.games.UserName(ctx, k) - if err != nil { - return err - } - items = append(items, map[string]any{"name": name}) - } - msg["items"] = items - } - - msgB, _ := json.Marshal(msg) - if err := c.Write(ctx, 1, msgB); err != nil { + if err := ws.Push(ctx, ugs); err != nil { return err } } return ctx.Err() } + +func (ws WS) newUserGameServer(ctx context.Context) (*UserGameServer, error) { + return NewUserGameServer(ctx, ws.Session(ctx), ws.games) +} + +func (ws WS) Push(ctx context.Context, ugs *UserGameServer) error { + gameState, err := ugs.State(ctx) + if err != nil { + return err + } + + msg := map[string]any{ + "help": strings.Join([]string{ + "CARD ASSASSINS (Mobile Ed.)", + "", + "1. Get any target to say any of his or her kill words.", + "2. Click the word to collect points.", + "3. Review new kill words.", + "", + "The game ends when everyone has been assassinated.", + }, "
"), + } + + if gameState.Started { + msg["page"] = "B" + if gameState.Completed.IsZero() { + msg["event"] = "A" + items := []map[string]any{} + for k, v := range gameState.Players { + if k == ws.Session(ctx).ID { + continue + } + + name, err := ws.games.UserName(ctx, k) + if err != nil { + return err + } + + tags := []map[string]any{} + if self := gameState.Players[ws.Session(ctx).ID]; self.KillWords.Assignee == k { + for _, private := range v.KillWords.Assignment.Private { + tags = append(tags, map[string]any{ + "k": private.Word, + "v": private.Points, + }) + } + } + for _, public := range v.KillWords.Assignment.Public { + tags = append(tags, map[string]any{ + "k": public.Word, + "v": public.Points, + }) + } + if self := gameState.Players[ws.Session(ctx).ID]; !slices.ContainsFunc(self.Kills, func(a Kill) bool { + return a.Victim == k + }) { + tags = append(tags, map[string]any{ + "k": self.KillWords.Global.Word, + "v": self.KillWords.Global.Points, + }) + } + + items = append(items, map[string]any{ + "name": name, + "title": strconv.Itoa(v.Points()), + "tags": tags, + }) + } + slices.SortFunc(items, func(a, b map[string]any) int { + an, _ := strconv.Atoi(fmt.Sprint(a["title"])) + bn, _ := strconv.Atoi(fmt.Sprint(b["title"])) + return an - bn + }) + return io.EOF + } else { + msg["event"] = "B" + items := []map[string]any{} + for k, v := range gameState.Players { + name, err := ws.games.UserName(ctx, k) + if err != nil { + return err + } + tags := []map[string]any{} + for _, kill := range v.Kills { + tags = append(tags, map[string]any{ + "k": kill.KillWord.Word, + "v": kill.Victim, + }) + } + items = append(items, map[string]any{ + "name": name, + "title": fmt.Sprint(v.Points()), + "tags": tags, + }) + } + msg["items"] = items + } + } else { + msg["page"] = "A" + items := []map[string]any{} + for k := range gameState.Players { + name, err := ws.games.UserName(ctx, k) + if err != nil { + return err + } + items = append(items, map[string]any{"name": name}) + } + msg["items"] = items + } + + msgB, _ := json.Marshal(msg) + return ws.c.Write(ctx, 1, msgB) +}