Compare commits

...

11 Commits

Author SHA1 Message Date
Bel LaPointe
0b22ba4bd2 random name generation done 2024-12-15 13:41:57 -07:00
Bel LaPointe
f7a303168a GameByName doesnt take uid 2024-12-15 13:26:28 -07:00
Bel LaPointe
37291e68aa ugs listen needs love 2024-12-15 13:17:36 -07:00
Bel LaPointe
5109dc9fdb test usergameserver.State after game completes WOO 2024-12-15 13:16:04 -07:00
Bel LaPointe
fc899056e0 test game state after some kills 2024-12-15 13:13:03 -07:00
Bel LaPointe
8d73f97c3a bugfix if victim was targeting killer then killer cant target itself 2024-12-15 13:03:37 -07:00
Bel LaPointe
598cb0684c test UserGameState of freshly started game 2024-12-15 12:52:10 -07:00
Bel LaPointe
aba5225ed2 unittest not impls 2024-12-15 12:42:53 -07:00
Bel LaPointe
f3f70e10f4 test UserGameServer unstarted state has all empty players 2024-12-15 12:39:36 -07:00
Bel LaPointe
1e4198b291 drop temp fail 2024-12-15 12:27:17 -07:00
Bel LaPointe
c760dac44b ws blindly pushes public, private and lets UserGameServer limit visibility of user 2024-12-15 12:26:30 -07:00
8 changed files with 854 additions and 50 deletions

249
cmd/server/adjectives.txt Normal file
View File

@@ -0,0 +1,249 @@
amazing
astounding
adorable
astonishing
awesome
agreeable
admirable
affectionate
artistic
animated
beautiful
brave
brilliant
big
bouncy
bright
busy
bold
blissful
bubbly
caring
clever
courageous
creative
cute
cheerful
charming
confident
curious
content
daring
delightful
determined
diligent
dynamic
decisive
dazzling
dependable
dramatic
dreamy
eager
earnest
effervescent
elegant
energetic
enchanting
enthusiastic
excellent
exciting
expressive
fair
faithful
fantastic
fearless
festive
friendly
funny
feisty
forgiving
focused
gentle
giving
glad
gleeful
glorious
good
graceful
grateful
great
gregarious
happy
helpful
honest
hopeful
hungry
hilarious
honorable
huggable
humble
hardworking
important
impressive
incredible
independent
inquisitive
intelligent
interesting
imaginative
inventive
inspiring
joyful
jolly
jovial
judicious
just
jaunty
jubilant
jazzy
jumpy
joking
kind
knowledgeable
keen
kooky
kindhearted
karate-chopping
krazy
kicking
kissable
kidding
loving
laughing
likable
lucky
lovely
light
little
loud
lazy
lanky
merry
magical
marvellous
mysterious
mischievous
masterful
mindful
melodic
mighty
motivated
nice
nimble
nifty
noisy
nutty
nautical
noteworthy
nosey
neat
nourished
odd
old
obedient
outstanding
opinionated
optimistic
orderly
outgoing
overjoyed
organized
perfect
playful
pleasant
polite
powerful
peaceful
patient
proud
puzzled
perky
quirky
quick
queenly
quaint
qualified
quizzical
quaint
quiet
quirky
quacking
rainy
rambunctious
respectful
right
responsible
ripe
rustic
rotten
rhythmic
righteous
silly
sweet
smart
smiling
strong
super
skillful
sleepy
scented
spotless
thankful
tired
tasty
talented
thoughtful
tremendous
terrific
truthful
tough
trustworthy
unique
understanding
unusual
upbeat
useful
uplifting
unafraid
universal
unlimited
unselfish
victorious
vivacious
valuable
vibrant
versatile
virtuous
visionary
vocal
vivacious
valiant
wonderful
whimsical
welcoming
witty
wise
wild
warm
wacky
willing
watchful
xenial
xeric
yummy
yellow
yippee
yappy
young
yucky
yummy
youthful
yakky
zany
zesty
zen
zealous
zingy
zippy
zonal
zonked

100
cmd/server/animals.txt Normal file
View File

@@ -0,0 +1,100 @@
lion
tiger
goat
horse
donkey
dog
cat
pig
cow
elephant
giraffe
kangaroo
koala
deer
moose
sheep
zebra
bear
wolf
fox
otter
raccoon
squirrel
bat
chimpanzee
gorilla
orangutan
lemur
panda
red panda
hippopotamus
rhinoceros
camel
llama
alpaca
ferret
hedgehog
skunk
beaver
walrus
seal
dolphin
whale
bat
eagle
hawk
falcon
owl
parrot
crow
raven
pigeon
dove
swan
goose
duck
chicken
turkey
peacock
ostrich
emu
snake
lizard
turtle
crocodile
alligator
chameleon
gecko
shark
dolphin
whale
octopus
lobster
crab
shrimp
clam
oyster
ant
bee
butterfly
caterpillar
cricket
grasshopper
ladybug
mosquito
scorpion
spider
worm
snail
jellyfish
starfish
sponge
platypus
koala
tasmanian devil
kangaroo
wombat
wallaby
meerkat
lemming

View File

@@ -62,9 +62,24 @@ func (games Games) GamesForUser(ctx context.Context, id string) ([]string, error
} }
func (games Games) UpdateUserName(ctx context.Context, id, name string) error { func (games Games) UpdateUserName(ctx context.Context, id, name string) error {
var n int
if err := games.db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&n)
}, `SELECT COUNT(uuid) FROM users WHERE uuid=?`, id); err != nil {
return err
}
if n > 0 {
return games.db.Exec(ctx, `UPDATE users SET name=? WHERE uuid=?`, name, id) return games.db.Exec(ctx, `UPDATE users SET name=? WHERE uuid=?`, name, id)
}
return games.db.Exec(ctx, `INSERT INTO users (uuid, name) VALUES (?, ?)`, id, name)
} }
//go:embed adjectives.txt
var namesAdjectives string
//go:embed animals.txt
var namesAnimals string
func (games Games) UserName(ctx context.Context, id string) (string, error) { func (games Games) UserName(ctx context.Context, id string) (string, error) {
result := "" result := ""
err := games.db.Query(ctx, func(rows *sql.Rows) error { err := games.db.Query(ctx, func(rows *sql.Rows) error {
@@ -74,9 +89,31 @@ func (games Games) UserName(ctx context.Context, id string) (string, error) {
FROM users FROM users
WHERE users.uuid=? WHERE users.uuid=?
`, id) `, id)
if result == "" {
adjectives := strings.Fields(namesAdjectives)
animals := strings.Split(namesAnimals, "\n")
animals = slices.DeleteFunc(animals, func(s string) bool { return s == "" })
name := strings.Title(fmt.Sprintf("%s %s", adjectives[rand.Intn(len(adjectives))], animals[rand.Intn(len(animals))]))
if err := games.UpdateUserName(ctx, id, name); err != nil {
return "", err
}
return games.UserName(ctx, id)
}
return result, err return result, err
} }
func (a KillWord) Empty() bool {
return a == (KillWord{})
}
func (a Assignment) Empty() bool {
return len(a.Public) == 0 && len(a.Private) == 0
}
func (s PlayerState) Empty() bool {
return len(s.Kills) == 0 && s.KillWords.Empty()
}
func (s PlayerState) Points() int { func (s PlayerState) Points() int {
points := 0 points := 0
for _, kill := range s.Kills { for _, kill := range s.Kills {
@@ -85,20 +122,35 @@ func (s PlayerState) Points() int {
return points return points
} }
func (games Games) GameByName(ctx context.Context, uid, name string) (string, error) { func (games Games) GameName(ctx context.Context, id string) (string, error) {
var result string var result string
err := games.db.Query(ctx, func(rows *sql.Rows) error { err := games.db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&result) return rows.Scan(&result)
}, ` }, `
SELECT SELECT
players.game_uuid games.name
FROM FROM
players games
JOIN games ON players.game_uuid=games.uuid WHERE games.uuid=?
WHERE players.user_uuid=? AND games.name=?
ORDER BY games.updated DESC ORDER BY games.updated DESC
LIMIT 1 LIMIT 1
`, uid, name) `, id)
return result, err
}
func (games Games) GameByName(ctx context.Context, name string) (string, error) {
var result string
err := games.db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&result)
}, `
SELECT
games.uuid
FROM
games
WHERE games.name=?
ORDER BY games.updated DESC
LIMIT 1
`, name)
return result, err return result, err
} }
@@ -378,6 +430,10 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
return games.createEvent(ctx, id, event) return games.createEvent(ctx, id, event)
} }
func (words KillWords) Empty() bool {
return words.Global == (KillWord{}) && words.Assigned.IsZero() && words.Assignee == "" && words.Assignment.Empty()
}
func (words KillWords) Privates() []KillWord { func (words KillWords) Privates() []KillWord {
a := slices.Clone(words.Assignment.Private) a := slices.Clone(words.Assignment.Private)
slices.SortFunc(a, func(a, b KillWord) int { slices.SortFunc(a, func(a, b KillWord) int {
@@ -399,7 +455,7 @@ func (prev AllKillWords) ShuffleAssignees(killer, victim, word string) AllKillWo
if _, ok := prev[killer]; !ok { if _, ok := prev[killer]; !ok {
} else if victimState, ok := prev[victim]; !ok { } else if victimState, ok := prev[victim]; !ok {
} else { } else if killer != victimState.Assignee { // if victim was targeting killer, just randomize
m.assign(killer, victimState.Assignee) m.assign(killer, victimState.Assignee)
} }

View File

@@ -31,7 +31,7 @@ func TestGames(t *testing.T) {
t.Error(v) t.Error(v)
} }
if v, err := games.GameByName(ctx, "", ""); err != nil { if v, err := games.GameByName(ctx, ""); err != nil {
t.Error("err getting game by empty name for empty user:", err) t.Error("err getting game by empty name for empty user:", err)
} else if len(v) > 0 { } else if len(v) > 0 {
t.Error(v) t.Error(v)
@@ -112,7 +112,7 @@ func TestGames(t *testing.T) {
t.Error("wrong game found for user:", v) t.Error("wrong game found for user:", v)
} }
if v, err := games.GameByName(ctx, "p1", "g1"); err != nil { if v, err := games.GameByName(ctx, "g1"); err != nil {
t.Error("err getting game by name for user:", err) t.Error("err getting game by name for user:", err)
} else if v != id { } else if v != id {
t.Error("wrong game by name for user:", v) t.Error("wrong game by name for user:", v)
@@ -382,3 +382,25 @@ func TestAllKillWordsUnused(t *testing.T) {
}) })
}) })
} }
func TestGenerateUserName(t *testing.T) {
games := newTestGames(t)
name, err := games.UserName(context.Background(), "id")
if err != nil {
t.Fatal(err)
}
if name == "" {
t.Fatal(name)
}
name2, err := games.UserName(context.Background(), "id")
if err != nil {
t.Fatal(err)
}
if name2 != name {
t.Fatal(name2)
}
t.Log(name)
}

View File

@@ -71,20 +71,20 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
return err return err
} }
if m["party"] == "start" { if startGame := m["party"] == "start"; startGame {
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil { if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
return err return err
} else if gameState.Started { } else if gameState.Started {
} else if err := ugs.games.CreateEventAssignmentRotation(ctx, ugs.ID, "", "", "", 0); err != nil { } else if err := ugs.games.CreateEventAssignmentRotation(ctx, ugs.ID, "", "", "", 0); err != nil {
return err return err
} }
} else if m["k"] != "" { } else if killOccurred := m["k"] != ""; killOccurred {
return fmt.Errorf("not impl: a kill occurred: %+v", m) return fmt.Errorf("not impl: a kill occurred: %+v", m)
} else if name := m["name"]; name != "" { } else if isRename := m["name"] != ""; isRename {
if err := ugs.games.UpdateUserName(ctx, ugs.Session.ID, name); err != nil { if err := ugs.games.UpdateUserName(ctx, ugs.Session.ID, m["name"]); err != nil {
return err return err
} }
} else if m["again"] == "true" { } else if isRestart := m["again"] == "true"; isRestart {
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil { if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
return err return err
} else if gameState.Completed.IsZero() { } else if gameState.Completed.IsZero() {
@@ -98,6 +98,44 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
return ctx.Err() return ctx.Err()
} }
func (ugs *UserGameServer) State(ctx context.Context) (GameState, error) { type UserGameState GameState
return ugs.games.GameState(ctx, ugs.ID)
func (ugs *UserGameServer) State(ctx context.Context) (UserGameState, error) {
gameState, err := ugs.games.GameState(ctx, ugs.ID)
if err != nil {
return UserGameState{}, err
}
if complete := !gameState.Completed.IsZero(); complete {
return UserGameState(gameState), nil
}
self := gameState.Players[ugs.Session.ID]
for i := range self.Kills {
self.Kills[i].KillWord.Points = 0
}
self.KillWords.Assignment.Public = nil
self.KillWords.Assignment.Private = nil
gameState.Players[ugs.Session.ID] = self
for k, v := range gameState.Players {
if isSelf := k == ugs.Session.ID; isSelf {
v.KillWords.Assignment = Assignment{}
} else {
v.KillWords.Global = KillWord{}
v.KillWords.Assignee = ""
for i := range v.Kills {
v.Kills[i].Victim = ""
v.Kills[i].KillWord.Word = ""
}
}
if assignedToSomeoneElse := self.KillWords.Assignee != k; assignedToSomeoneElse {
v.KillWords.Assignment.Private = v.KillWords.Assignment.Private[:0]
}
gameState.Players[k] = v
}
return UserGameState(gameState), nil
} }

View File

@@ -0,0 +1,291 @@
package main
import (
"context"
"fmt"
"testing"
)
func TestUserGameServer(t *testing.T) {
ctx := context.Background()
games := newTestGames(t)
gid, err := games.CreateGame(ctx, "g1")
if err != nil {
t.Fatal(err)
}
pids := []string{}
for i := 0; i < 4; i++ {
pid := fmt.Sprintf("p%d", i+1)
if err := games.CreateEventPlayerJoin(ctx, gid, pid, "player "+pid); err != nil {
t.Fatal(err)
}
pids = append(pids, pid)
}
ugs, err := NewUserGameServer(ctx, Session{ID: pids[0]}, games)
if err != nil {
t.Fatal(err)
}
t.Run("unstarted", func(t *testing.T) {
state, err := ugs.State(ctx)
if err != nil {
t.Fatal(err)
}
if state.Started {
t.Error("started after player joins only")
}
if !state.Completed.IsZero() {
t.Error("completed after player joins only")
}
for _, pid := range pids {
if p, ok := state.Players[pid]; !ok {
t.Error(pid, "not in players")
} else if !p.Empty() {
t.Error(pid, p)
}
}
})
if err := games.CreateEventAssignmentRotation(ctx, gid, "", "", "", 0); err != nil {
t.Fatal(err)
}
t.Run("just started", func(t *testing.T) {
state, err := ugs.State(ctx)
if err != nil {
t.Fatal(err)
}
if !state.Started {
t.Error("not started after assignment rotation")
}
if !state.Completed.IsZero() {
t.Error("completed after assignment rotation")
}
for _, pid := range pids {
p, ok := state.Players[pid]
if !ok {
t.Error(pid, "not in players")
} else if p.Empty() {
t.Error(pid, p)
} else if len(p.Kills) > 0 {
t.Error(pid, "has a kill")
} else if p.KillWords.Assigned.IsZero() {
t.Error("assigned is zero")
}
if isSelf := pid == ugs.Session.ID; isSelf {
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
}
if len(p.KillWords.Assignment.Public) > 0 {
t.Error("self knows its own public")
}
if len(p.KillWords.Assignment.Private) > 0 {
t.Error("self knows its own private")
}
} else {
if !p.KillWords.Global.Empty() {
t.Error("can see not self global")
}
if p.KillWords.Assignee != "" {
t.Error("can see other player's assignee")
}
if len(p.KillWords.Assignment.Public) == 0 {
t.Error("cannot see other player's public")
}
if state.Players[ugs.Session.ID].KillWords.Assignee == pid && len(p.KillWords.Assignment.Private) == 0 {
t.Error("cannot see assignee's private")
} else if state.Players[ugs.Session.ID].KillWords.Assignee != pid && len(p.KillWords.Assignment.Private) > 0 {
t.Error("can see not assignee's private")
}
}
}
})
for i := 0; i < 3; i++ {
state, err := games.GameState(ctx, ugs.ID)
if err != nil {
t.Fatal(err)
}
killer := ugs.Session.ID
if i > 0 {
killer = state.Players[killer].KillWords.Assignee
}
victim := state.Players[killer].KillWords.Assignee
word := state.Players[victim].KillWords.Assignment.Public[0].Word
points := state.Players[victim].KillWords.Assignment.Public[0].Points
if err := games.CreateEventAssignmentRotation(ctx, gid, killer, victim, word, points); err != nil {
t.Fatal(err)
}
}
t.Run("after kills", func(t *testing.T) {
state, err := ugs.State(ctx)
if err != nil {
t.Fatal(err)
}
if !state.Started {
t.Error("not started after kills")
}
if !state.Completed.IsZero() {
t.Error("completed after kills")
}
for _, pid := range pids {
p, ok := state.Players[pid]
if !ok {
t.Error(pid, "not in players")
} else if p.Empty() {
t.Error(pid, p)
} else if p.KillWords.Assigned.IsZero() {
t.Error("assigned is zero")
}
if isSelf := pid == ugs.Session.ID; isSelf {
if len(p.Kills) == 0 {
t.Error("self never got a kill")
} else if kill := p.Kills[0]; kill.Timestamp.IsZero() {
t.Errorf("kill has no timestamp")
} else if kill.Victim == "" {
t.Errorf("kill has no victim")
} else if kill.KillWord.Points != 0 {
t.Errorf("know points of own kill")
} else if kill.KillWord.Word == "" {
t.Errorf("dont know own kill word")
}
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
}
if len(p.KillWords.Assignment.Public) > 0 {
t.Error("self knows its own public")
}
if len(p.KillWords.Assignment.Private) > 0 {
t.Error("self knows its own private")
}
} else {
if len(p.Kills) == 0 {
} else if kill := p.Kills[0]; kill.Timestamp.IsZero() {
t.Errorf("kill has no timestamp")
} else if kill.Victim != "" {
t.Errorf("know other's victim")
} else if kill.KillWord.Points == 0 {
t.Errorf("other's kill has no points")
} else if kill.KillWord.Word != "" {
t.Errorf("know other's kill word")
}
if !p.KillWords.Global.Empty() {
t.Error("can see not self global")
}
if p.KillWords.Assignee != "" {
t.Error("can see other player's assignee")
}
if len(p.KillWords.Assignment.Public) == 0 {
t.Error("cannot see other player's public")
}
if state.Players[ugs.Session.ID].KillWords.Assignee == pid && len(p.KillWords.Assignment.Private) == 0 {
t.Error("cannot see assignee's private")
} else if state.Players[ugs.Session.ID].KillWords.Assignee != pid && len(p.KillWords.Assignment.Private) > 0 {
t.Error("can see not assignee's private")
}
}
}
})
if err := games.CreateEventGameComplete(ctx, gid); err != nil {
t.Fatal(err)
}
t.Run("completed", func(t *testing.T) {
state, err := ugs.State(ctx)
if err != nil {
t.Fatal(err)
}
if !state.Started {
t.Error("not started after complete")
}
if state.Completed.IsZero() {
t.Error("not complete after complete")
}
for _, pid := range pids {
p, ok := state.Players[pid]
if !ok {
t.Error(pid, "not in players")
} else if p.Empty() {
t.Error(pid, p)
} else if p.KillWords.Assigned.IsZero() {
t.Error("assigned is zero")
}
if isSelf := pid == ugs.Session.ID; isSelf {
if len(p.Kills) == 0 {
t.Error("self never got a kill")
} else if kill := p.Kills[0]; kill.Timestamp.IsZero() {
t.Errorf("kill has no timestamp")
} else if kill.Victim == "" {
t.Errorf("kill has no victim")
} else if kill.KillWord.Points == 0 {
t.Errorf("dont know points of own kill at game review")
} else if kill.KillWord.Word == "" {
t.Errorf("dont know own kill word")
}
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
}
if len(p.KillWords.Assignment.Public) == 0 {
t.Error("self doesnt know its own public after game")
}
if len(p.KillWords.Assignment.Private) == 0 {
t.Error("self doesnt know its own private after game")
}
} else {
if len(p.Kills) == 0 {
} else if kill := p.Kills[0]; kill.Timestamp.IsZero() {
t.Errorf("kill has no timestamp")
} else if kill.Victim == "" {
t.Errorf("cannot know other's victim")
} else if kill.KillWord.Points == 0 {
t.Errorf("other's kill has no points")
} else if kill.KillWord.Word == "" {
t.Errorf("dont know other's kill word")
}
if p.KillWords.Global.Empty() {
t.Error("cannot see not self global")
}
if p.KillWords.Assignee == "" {
t.Error("cannot see other player's assignee")
}
if len(p.KillWords.Assignment.Public) == 0 {
t.Error("cannot see other player's public")
}
if state.Players[ugs.Session.ID].KillWords.Assignee == pid && len(p.KillWords.Assignment.Private) == 0 {
t.Error("cannot see assignee's private")
} else if state.Players[ugs.Session.ID].KillWords.Assignee != pid && len(p.KillWords.Assignment.Private) == 0 {
t.Error("cannot see not assignee's private after game")
}
}
}
})
}

View File

@@ -1,9 +1,12 @@
package main package main
import ( import (
"fmt" "bytes"
"encoding/json"
"io"
"net/http" "net/http"
"path" "path"
"slices"
"strings" "strings"
) )
@@ -12,8 +15,29 @@ func isV1(r *http.Request) bool {
} }
func (s *S) serveV1(w http.ResponseWriter, r *http.Request) error { func (s *S) serveV1(w http.ResponseWriter, r *http.Request) error {
uid := s.Session(r.Context()).ID
switch path.Join(r.Method, r.URL.Path) { switch path.Join(r.Method, r.URL.Path) {
case "PUT/v1/state/" + s.Session(r.Context()).ID + "/party": case "GET/v1/state/" + uid:
name, err := s.games.UserName(r.Context(), uid)
if err != nil {
return err
}
gids, err := s.games.GamesForUser(r.Context(), uid)
if err != nil {
return err
}
msg := map[string]any{
"name": name,
}
if len(gids) > 0 {
party, err := s.games.GameName(r.Context(), gids[0])
if err != nil {
return err
}
msg["party"] = party
}
return json.NewEncoder(w).Encode(msg)
case "PUT/v1/state/" + uid + "/party":
return s.serveV1PutParty(w, r) return s.serveV1PutParty(w, r)
default: default:
http.NotFound(w, r) http.NotFound(w, r)
@@ -22,5 +46,32 @@ func (s *S) serveV1(w http.ResponseWriter, r *http.Request) error {
} }
func (s *S) serveV1PutParty(w http.ResponseWriter, r *http.Request) error { func (s *S) serveV1PutParty(w http.ResponseWriter, r *http.Request) error {
return fmt.Errorf("not impl") userName, err := s.games.UserName(r.Context(), s.Session(r.Context()).ID)
if err != nil {
return err
}
party, err := io.ReadAll(r.Body)
if err != nil {
return err
}
party = bytes.TrimSpace(party)
if len(party) == 0 {
return nil
}
gid, err := s.games.GameByName(r.Context(), string(party))
if err != nil {
return err
}
games, err := s.games.GamesForUser(r.Context(), gid)
if err != nil {
return err
}
if slices.Contains(games, gid) {
return nil
}
return s.games.CreateEventPlayerJoin(r.Context(), gid, s.Session(r.Context()).ID, userName)
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"slices" "slices"
"strconv" "strconv"
@@ -68,7 +67,7 @@ func (ws WS) Push(ctx context.Context, ugs *UserGameServer) error {
var msg map[string]any var msg map[string]any
if unstarted := !gameState.Started; unstarted { if unstarted := !gameState.Started; unstarted {
msg, err = ws.unstartedMsg(ctx, gameState) msg, err = ws.unstartedMsg(ctx, ugs, gameState)
} else if complete := !gameState.Completed.IsZero(); complete { } else if complete := !gameState.Completed.IsZero(); complete {
msg, err = ws.completeMsg(ctx, gameState) msg, err = ws.completeMsg(ctx, gameState)
} else { } else {
@@ -92,23 +91,27 @@ func (ws WS) Push(ctx context.Context, ugs *UserGameServer) error {
return ws.c.Write(ctx, 1, msgB) return ws.c.Write(ctx, 1, msgB)
} }
func (ws WS) unstartedMsg(ctx context.Context, gameState GameState) (msg map[string]any, _ error) { func (ws WS) unstartedMsg(ctx context.Context, ugs *UserGameServer, gameState UserGameState) (msg map[string]any, _ error) {
msg["page"] = "A" msg["page"] = "A"
items := []map[string]any{} items := []map[string]any{}
for k := range gameState.Players { for k := range gameState.Players {
if k == ugs.Session.ID {
continue
}
name, err := ws.games.UserName(ctx, k) name, err := ws.games.UserName(ctx, k)
if err != nil { if err != nil {
return nil, err return nil, err
} }
items = append(items, map[string]any{"name": name}) items = append(items, map[string]any{"id": k, "name": name})
} }
msg["items"] = items msg["items"] = items
return msg, nil return msg, nil
} }
func (ws WS) completeMsg(ctx context.Context, gameState GameState) (msg map[string]any, _ error) { func (ws WS) completeMsg(ctx context.Context, gameState UserGameState) (msg map[string]any, _ error) {
msg["page"] = "B" msg["page"] = "B"
msg["event"] = "B" msg["event"] = "B"
@@ -135,7 +138,7 @@ func (ws WS) completeMsg(ctx context.Context, gameState GameState) (msg map[stri
return msg, nil return msg, nil
} }
func (ws WS) inProgressMsg(ctx context.Context, ugs *UserGameServer, gameState GameState) (msg map[string]any, _ error) { func (ws WS) inProgressMsg(ctx context.Context, ugs *UserGameServer, gameState UserGameState) (msg map[string]any, _ error) {
msg["page"] = "B" msg["page"] = "B"
msg["event"] = "A" msg["event"] = "A"
@@ -145,7 +148,7 @@ func (ws WS) inProgressMsg(ctx context.Context, ugs *UserGameServer, gameState G
} }
msg["items"] = items msg["items"] = items
return nil, io.EOF return msg, nil
} }
type inProgressMsgItem struct { type inProgressMsgItem struct {
@@ -159,7 +162,7 @@ type inProgressMsgItemTag struct {
V int `json:"v,string"` V int `json:"v,string"`
} }
func (ws WS) inProgressMsgItems(ctx context.Context, ugs *UserGameServer, gameState GameState) ([]inProgressMsgItem, error) { func (ws WS) inProgressMsgItems(ctx context.Context, ugs *UserGameServer, gameState UserGameState) ([]inProgressMsgItem, error) {
items := []inProgressMsgItem{} items := []inProgressMsgItem{}
for k := range gameState.Players { for k := range gameState.Players {
item, err := ws.inProgressMsgItem(ctx, ugs, gameState, k) item, err := ws.inProgressMsgItem(ctx, ugs, gameState, k)
@@ -179,39 +182,26 @@ func (ws WS) inProgressMsgItems(ctx context.Context, ugs *UserGameServer, gameSt
return items, nil return items, nil
} }
func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameState GameState, uid string) (*inProgressMsgItem, error) { func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameState UserGameState, uid string) (*inProgressMsgItem, error) {
if isSelf := uid == ugs.Session.ID; isSelf { if isSelf := uid == ugs.Session.ID; isSelf {
return nil, nil return nil, nil
} }
v := gameState.Players[uid]
self := gameState.Players[ugs.Session.ID] self := gameState.Players[ugs.Session.ID]
v := gameState.Players[uid]
tags := []inProgressMsgItemTag{} tags := []inProgressMsgItemTag{}
if hasBeenKilledWithGlobal := slices.ContainsFunc(self.Kills, func(a Kill) bool { if hasBeenKilledWithGlobal := slices.ContainsFunc(self.Kills, func(a Kill) bool {
return a.KillWord.Word == self.KillWords.Global.Word && a.Victim == uid return a.Victim == uid && a.KillWord.Word == self.KillWords.Global.Word
}); !hasBeenKilledWithGlobal { }); !hasBeenKilledWithGlobal {
tags = append(tags, inProgressMsgItemTag{ tags = append(tags, newInProgressMsgItemTag(self.KillWords.Global))
K: self.KillWords.Global.Word,
V: self.KillWords.Global.Points,
})
} }
for _, public := range v.KillWords.Publics() { for _, killWord := range append(
tags = append(tags, inProgressMsgItemTag{ v.KillWords.Publics(),
K: public.Word, v.KillWords.Privates()...,
V: public.Points, ) {
}) tags = append(tags, newInProgressMsgItemTag(killWord))
}
if isAssigned := self.KillWords.Assignee == uid; isAssigned {
for _, private := range v.KillWords.Privates() {
tags = append(tags, inProgressMsgItemTag{
K: private.Word,
V: private.Points,
})
}
} }
name, err := ws.games.UserName(ctx, uid) name, err := ws.games.UserName(ctx, uid)
@@ -221,3 +211,10 @@ func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameSta
Tags: tags, Tags: tags,
}, err }, err
} }
func newInProgressMsgItemTag(kw KillWord) inProgressMsgItemTag {
return inProgressMsgItemTag{
K: kw.Word,
V: kw.Points,
}
}