Compare commits
11 Commits
c1933dc180
...
0b22ba4bd2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b22ba4bd2 | ||
|
|
f7a303168a | ||
|
|
37291e68aa | ||
|
|
5109dc9fdb | ||
|
|
fc899056e0 | ||
|
|
8d73f97c3a | ||
|
|
598cb0684c | ||
|
|
aba5225ed2 | ||
|
|
f3f70e10f4 | ||
|
|
1e4198b291 | ||
|
|
c760dac44b |
249
cmd/server/adjectives.txt
Normal file
249
cmd/server/adjectives.txt
Normal 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
100
cmd/server/animals.txt
Normal 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
|
||||
@@ -62,8 +62,23 @@ func (games Games) GamesForUser(ctx context.Context, id string) ([]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, `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) {
|
||||
result := ""
|
||||
@@ -74,9 +89,31 @@ func (games Games) UserName(ctx context.Context, id string) (string, error) {
|
||||
FROM users
|
||||
WHERE users.uuid=?
|
||||
`, 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
|
||||
}
|
||||
|
||||
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 {
|
||||
points := 0
|
||||
for _, kill := range s.Kills {
|
||||
@@ -85,20 +122,35 @@ func (s PlayerState) Points() int {
|
||||
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
|
||||
err := games.db.Query(ctx, func(rows *sql.Rows) error {
|
||||
return rows.Scan(&result)
|
||||
}, `
|
||||
SELECT
|
||||
players.game_uuid
|
||||
games.name
|
||||
FROM
|
||||
players
|
||||
JOIN games ON players.game_uuid=games.uuid
|
||||
WHERE players.user_uuid=? AND games.name=?
|
||||
games
|
||||
WHERE games.uuid=?
|
||||
ORDER BY games.updated DESC
|
||||
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
|
||||
}
|
||||
|
||||
@@ -378,6 +430,10 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
||||
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 {
|
||||
a := slices.Clone(words.Assignment.Private)
|
||||
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 {
|
||||
} 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestGames(t *testing.T) {
|
||||
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)
|
||||
} else if len(v) > 0 {
|
||||
t.Error(v)
|
||||
@@ -112,7 +112,7 @@ func TestGames(t *testing.T) {
|
||||
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)
|
||||
} else if v != id {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -71,20 +71,20 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
|
||||
return err
|
||||
}
|
||||
|
||||
if m["party"] == "start" {
|
||||
if startGame := m["party"] == "start"; startGame {
|
||||
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
|
||||
return err
|
||||
} else if gameState.Started {
|
||||
} else if err := ugs.games.CreateEventAssignmentRotation(ctx, ugs.ID, "", "", "", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if m["k"] != "" {
|
||||
} else if killOccurred := m["k"] != ""; killOccurred {
|
||||
return fmt.Errorf("not impl: a kill occurred: %+v", m)
|
||||
} else if name := m["name"]; name != "" {
|
||||
if err := ugs.games.UpdateUserName(ctx, ugs.Session.ID, name); err != nil {
|
||||
} else if isRename := m["name"] != ""; isRename {
|
||||
if err := ugs.games.UpdateUserName(ctx, ugs.Session.ID, m["name"]); err != nil {
|
||||
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 {
|
||||
return err
|
||||
} else if gameState.Completed.IsZero() {
|
||||
@@ -98,6 +98,44 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (ugs *UserGameServer) State(ctx context.Context) (GameState, error) {
|
||||
return ugs.games.GameState(ctx, ugs.ID)
|
||||
type UserGameState GameState
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
291
cmd/server/usergameserver_test.go
Normal file
291
cmd/server/usergameserver_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -12,8 +15,29 @@ func isV1(r *http.Request) bool {
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
default:
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -68,7 +67,7 @@ func (ws WS) Push(ctx context.Context, ugs *UserGameServer) error {
|
||||
|
||||
var msg map[string]any
|
||||
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 {
|
||||
msg, err = ws.completeMsg(ctx, gameState)
|
||||
} else {
|
||||
@@ -92,23 +91,27 @@ func (ws WS) Push(ctx context.Context, ugs *UserGameServer) error {
|
||||
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"
|
||||
|
||||
items := []map[string]any{}
|
||||
for k := range gameState.Players {
|
||||
if k == ugs.Session.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
name, err := ws.games.UserName(ctx, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, map[string]any{"name": name})
|
||||
items = append(items, map[string]any{"id": k, "name": name})
|
||||
}
|
||||
msg["items"] = items
|
||||
|
||||
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["event"] = "B"
|
||||
|
||||
@@ -135,7 +138,7 @@ func (ws WS) completeMsg(ctx context.Context, gameState GameState) (msg map[stri
|
||||
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["event"] = "A"
|
||||
|
||||
@@ -145,7 +148,7 @@ func (ws WS) inProgressMsg(ctx context.Context, ugs *UserGameServer, gameState G
|
||||
}
|
||||
msg["items"] = items
|
||||
|
||||
return nil, io.EOF
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type inProgressMsgItem struct {
|
||||
@@ -159,7 +162,7 @@ type inProgressMsgItemTag struct {
|
||||
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{}
|
||||
for k := range gameState.Players {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
v := gameState.Players[uid]
|
||||
self := gameState.Players[ugs.Session.ID]
|
||||
v := gameState.Players[uid]
|
||||
|
||||
tags := []inProgressMsgItemTag{}
|
||||
|
||||
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 {
|
||||
tags = append(tags, inProgressMsgItemTag{
|
||||
K: self.KillWords.Global.Word,
|
||||
V: self.KillWords.Global.Points,
|
||||
})
|
||||
tags = append(tags, newInProgressMsgItemTag(self.KillWords.Global))
|
||||
}
|
||||
|
||||
for _, public := range v.KillWords.Publics() {
|
||||
tags = append(tags, inProgressMsgItemTag{
|
||||
K: public.Word,
|
||||
V: public.Points,
|
||||
})
|
||||
}
|
||||
|
||||
if isAssigned := self.KillWords.Assignee == uid; isAssigned {
|
||||
for _, private := range v.KillWords.Privates() {
|
||||
tags = append(tags, inProgressMsgItemTag{
|
||||
K: private.Word,
|
||||
V: private.Points,
|
||||
})
|
||||
}
|
||||
for _, killWord := range append(
|
||||
v.KillWords.Publics(),
|
||||
v.KillWords.Privates()...,
|
||||
) {
|
||||
tags = append(tags, newInProgressMsgItemTag(killWord))
|
||||
}
|
||||
|
||||
name, err := ws.games.UserName(ctx, uid)
|
||||
@@ -221,3 +211,10 @@ func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameSta
|
||||
Tags: tags,
|
||||
}, err
|
||||
}
|
||||
|
||||
func newInProgressMsgItemTag(kw KillWord) inProgressMsgItemTag {
|
||||
return inProgressMsgItemTag{
|
||||
K: kw.Word,
|
||||
V: kw.Points,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user