Compare commits
5 Commits
52aad4008c
...
c18f154328
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c18f154328 | ||
|
|
8d9815ee90 | ||
|
|
1e25ef7a98 | ||
|
|
d1c4738796 | ||
|
|
1c6025e78c |
@@ -143,22 +143,27 @@ type (
|
|||||||
|
|
||||||
EventPlayerJoin struct {
|
EventPlayerJoin struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
EventPlayerLeave struct {
|
EventPlayerLeave struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
EventGameComplete struct {
|
EventGameComplete struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
EventAssignmentRotation struct {
|
EventAssignmentRotation struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
Killer string
|
Killer string
|
||||||
Victim string
|
Victim string
|
||||||
KillWord KillWord
|
KillWord KillWord
|
||||||
KillWords map[string]KillWords
|
AllKillWords AllKillWords
|
||||||
}
|
}
|
||||||
|
AllKillWords map[string]KillWords
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -168,57 +173,109 @@ const (
|
|||||||
AssignmentRotation
|
AssignmentRotation
|
||||||
)
|
)
|
||||||
|
|
||||||
func (games Games) GameState(ctx context.Context, id string) (GameState, error) {
|
type Event interface{ event() }
|
||||||
result := GameState{Players: map[string]PlayerState{}}
|
|
||||||
|
|
||||||
|
func (EventPlayerJoin) event() {}
|
||||||
|
func (EventPlayerLeave) event() {}
|
||||||
|
func (EventGameComplete) event() {}
|
||||||
|
func (EventAssignmentRotation) event() {}
|
||||||
|
|
||||||
|
func EventWithTime(event Event, t time.Time) Event {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case EventPlayerJoin:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
case EventPlayerLeave:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
case EventGameComplete:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
case EventAssignmentRotation:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (games Games) GameEvents(ctx context.Context, id string, since time.Time) ([]Event, error) {
|
||||||
|
var results []Event
|
||||||
err := games.db.Query(ctx, func(rows *sql.Rows) error {
|
err := games.db.Query(ctx, func(rows *sql.Rows) error {
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
var payload []byte
|
var b []byte
|
||||||
if err := rows.Scan(×tamp, &payload); err != nil {
|
if err := rows.Scan(×tamp, &b); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
event, err := parseEvent(b, timestamp)
|
||||||
|
results = append(results, event)
|
||||||
|
return err
|
||||||
|
}, `
|
||||||
|
SELECT
|
||||||
|
timestamp,
|
||||||
|
payload
|
||||||
|
FROM events
|
||||||
|
WHERE game_uuid=? and timestamp>?
|
||||||
|
ORDER BY timestamp ASC
|
||||||
|
`, id, since)
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEvent(b []byte, timestamp time.Time) (Event, error) {
|
||||||
var peek struct {
|
var peek struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(payload, &peek); err != nil {
|
if err := json.Unmarshal(b, &peek); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch peek.Type {
|
switch peek.Type {
|
||||||
case PlayerJoin:
|
case PlayerJoin:
|
||||||
var playerJoin EventPlayerJoin
|
var v EventPlayerJoin
|
||||||
if err := json.Unmarshal(payload, &playerJoin); err != nil {
|
err := json.Unmarshal(b, &v)
|
||||||
return err
|
return EventWithTime(v, timestamp), err
|
||||||
}
|
|
||||||
result.Players[playerJoin.ID] = PlayerState{}
|
|
||||||
return nil
|
|
||||||
case PlayerLeave:
|
case PlayerLeave:
|
||||||
var playerLeave EventPlayerLeave
|
var v EventPlayerLeave
|
||||||
if err := json.Unmarshal(payload, &playerLeave); err != nil {
|
err := json.Unmarshal(b, &v)
|
||||||
return err
|
return EventWithTime(v, timestamp), err
|
||||||
}
|
|
||||||
delete(result.Players, playerLeave.ID)
|
|
||||||
return nil
|
|
||||||
case GameComplete:
|
case GameComplete:
|
||||||
var gameComplete EventGameComplete
|
var v EventGameComplete
|
||||||
if err := json.Unmarshal(payload, &gameComplete); err != nil {
|
err := json.Unmarshal(b, &v)
|
||||||
return err
|
return EventWithTime(v, timestamp), err
|
||||||
}
|
|
||||||
result.Completed = timestamp
|
|
||||||
return nil
|
|
||||||
case AssignmentRotation:
|
case AssignmentRotation:
|
||||||
result.Started = true
|
var v EventAssignmentRotation
|
||||||
var assignmentRotation EventAssignmentRotation
|
err := json.Unmarshal(b, &v)
|
||||||
if err := json.Unmarshal(payload, &assignmentRotation); err != nil {
|
return EventWithTime(v, timestamp), err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("unknown event type %d: %s", peek.Type, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (games Games) GameState(ctx context.Context, id string) (GameState, error) {
|
||||||
|
result := GameState{Players: map[string]PlayerState{}}
|
||||||
|
|
||||||
|
events, err := games.GameEvents(ctx, id, time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.(type) {
|
||||||
|
case EventPlayerJoin:
|
||||||
|
playerJoin := event.(EventPlayerJoin)
|
||||||
|
result.Players[playerJoin.ID] = PlayerState{}
|
||||||
|
case EventPlayerLeave:
|
||||||
|
playerLeave := event.(EventPlayerLeave)
|
||||||
|
delete(result.Players, playerLeave.ID)
|
||||||
|
case EventGameComplete:
|
||||||
|
gameComplete := event.(EventGameComplete)
|
||||||
|
result.Completed = gameComplete.Timestamp
|
||||||
|
case EventAssignmentRotation:
|
||||||
|
result.Started = true
|
||||||
|
assignmentRotation := event.(EventAssignmentRotation)
|
||||||
|
|
||||||
if killer, ok := result.Players[assignmentRotation.Killer]; !ok {
|
if killer, ok := result.Players[assignmentRotation.Killer]; !ok {
|
||||||
} else if _, ok := result.Players[assignmentRotation.Victim]; !ok {
|
} else if _, ok := result.Players[assignmentRotation.Victim]; !ok {
|
||||||
} else {
|
} else {
|
||||||
killer.Kills = append(killer.Kills, Kill{
|
killer.Kills = append(killer.Kills, Kill{
|
||||||
Timestamp: timestamp,
|
Timestamp: assignmentRotation.Timestamp,
|
||||||
Victim: assignmentRotation.Victim,
|
Victim: assignmentRotation.Victim,
|
||||||
KillWord: assignmentRotation.KillWord,
|
KillWord: assignmentRotation.KillWord,
|
||||||
})
|
})
|
||||||
@@ -230,22 +287,15 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
|
|||||||
result.Players[k] = v
|
result.Players[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range assignmentRotation.KillWords {
|
for k, v := range assignmentRotation.AllKillWords {
|
||||||
player := result.Players[k]
|
player := result.Players[k]
|
||||||
player.KillWords = v
|
player.KillWords = v
|
||||||
result.Players[k] = player
|
result.Players[k] = player
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown event type %d: %s", peek.Type, payload)
|
return GameState{}, fmt.Errorf("unknown event type %T", event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}, `
|
|
||||||
SELECT timestamp, payload
|
|
||||||
FROM events
|
|
||||||
WHERE game_uuid=?
|
|
||||||
`, id)
|
|
||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -301,9 +351,6 @@ func (games Games) CreateEventPlayerLeave(ctx context.Context, id string, player
|
|||||||
return games.createEvent(ctx, id, EventPlayerLeave{Type: PlayerLeave, ID: player})
|
return games.createEvent(ctx, id, EventPlayerLeave{Type: PlayerLeave, ID: player})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed holiday.txt
|
|
||||||
var wordsHoliday string
|
|
||||||
|
|
||||||
func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, killer, victim, word string, points int) error {
|
func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, killer, victim, word string, points int) error {
|
||||||
state, err := games.GameState(ctx, id)
|
state, err := games.GameState(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -319,7 +366,7 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
Word: word,
|
Word: word,
|
||||||
Points: points,
|
Points: points,
|
||||||
},
|
},
|
||||||
KillWords: map[string]KillWords{},
|
AllKillWords: make(AllKillWords),
|
||||||
}
|
}
|
||||||
|
|
||||||
toAssign := []string{}
|
toAssign := []string{}
|
||||||
@@ -330,7 +377,7 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
toAssign = append(toAssign, k)
|
toAssign = append(toAssign, k)
|
||||||
doNotAssign[k] = v.Assignee
|
doNotAssign[k] = v.Assignee
|
||||||
|
|
||||||
event.KillWords[k] = KillWords{
|
event.AllKillWords[k] = KillWords{
|
||||||
Global: v.Global,
|
Global: v.Global,
|
||||||
Assigned: now,
|
Assigned: now,
|
||||||
Assignee: "",
|
Assignee: "",
|
||||||
@@ -341,13 +388,13 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
if killerState, ok := state.Players[killer]; !ok {
|
if killerState, ok := state.Players[killer]; !ok {
|
||||||
} else if victimState, ok := state.Players[victim]; !ok {
|
} else if victimState, ok := state.Players[victim]; !ok {
|
||||||
} else {
|
} else {
|
||||||
event.KillWords[killer] = KillWords{
|
event.AllKillWords[killer] = KillWords{
|
||||||
Global: killerState.KillWords.Global,
|
Global: killerState.KillWords.Global,
|
||||||
Assigned: now,
|
Assigned: now,
|
||||||
Assignee: victimState.KillWords.Assignee,
|
Assignee: victimState.KillWords.Assignee,
|
||||||
Assignment: killerState.KillWords.Assignment,
|
Assignment: killerState.KillWords.Assignment,
|
||||||
}
|
}
|
||||||
toAssign = slices.DeleteFunc(toAssign, func(s string) bool { return s == event.KillWords[killer].Assignee })
|
toAssign = slices.DeleteFunc(toAssign, func(s string) bool { return s == event.AllKillWords[killer].Assignee })
|
||||||
|
|
||||||
if killerState.KillWords.Global.Word != word {
|
if killerState.KillWords.Global.Word != word {
|
||||||
victimState.KillWords.Assignment = Assignment{}
|
victimState.KillWords.Assignment = Assignment{}
|
||||||
@@ -358,17 +405,17 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
for !func() bool {
|
for !func() bool {
|
||||||
toAssign := slices.Clone(toAssign)
|
toAssign := slices.Clone(toAssign)
|
||||||
doNotAssign := maps.Clone(doNotAssign)
|
doNotAssign := maps.Clone(doNotAssign)
|
||||||
eventKillWords := maps.Clone(event.KillWords)
|
allKillWords := maps.Clone(event.AllKillWords)
|
||||||
|
|
||||||
for i := range toAssign {
|
for i := range toAssign {
|
||||||
j := rand.Intn(i + 1)
|
j := rand.Intn(i + 1)
|
||||||
toAssign[i], toAssign[j] = toAssign[j], toAssign[i]
|
toAssign[i], toAssign[j] = toAssign[j], toAssign[i]
|
||||||
}
|
}
|
||||||
for k, v := range eventKillWords {
|
for k, v := range allKillWords {
|
||||||
if k == toAssign[0] || doNotAssign[k] == toAssign[0] {
|
if k == toAssign[0] || doNotAssign[k] == toAssign[0] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
eventKillWords[k] = KillWords{
|
allKillWords[k] = KillWords{
|
||||||
Global: v.Global,
|
Global: v.Global,
|
||||||
Assigned: now,
|
Assigned: now,
|
||||||
Assignee: toAssign[0],
|
Assignee: toAssign[0],
|
||||||
@@ -377,66 +424,95 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
toAssign = toAssign[1:]
|
toAssign = toAssign[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
event.KillWords = eventKillWords
|
event.AllKillWords = allKillWords
|
||||||
return true
|
return true
|
||||||
}() {
|
}() {
|
||||||
}
|
}
|
||||||
|
|
||||||
globalsInUse := map[string]any{}
|
for k, v := range event.AllKillWords {
|
||||||
publicsInUse := map[string]any{}
|
|
||||||
privatesInUse := map[string]any{}
|
|
||||||
for _, v := range event.KillWords {
|
|
||||||
globalsInUse[v.Global.Word] = nil
|
|
||||||
for _, public := range v.Assignment.Public {
|
|
||||||
publicsInUse[public.Word] = nil
|
|
||||||
}
|
|
||||||
for _, private := range v.Assignment.Private {
|
|
||||||
privatesInUse[private.Word] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
randWord := func(words string, taken map[string]any) string {
|
|
||||||
wordsList := strings.Fields(words)
|
|
||||||
for {
|
|
||||||
got := wordsList[rand.Intn(len(wordsList))]
|
|
||||||
if _, ok := taken[got]; !ok {
|
|
||||||
taken[got] = nil
|
|
||||||
return got
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
randGlobal := func() string {
|
|
||||||
return randWord(wordsHoliday, globalsInUse)
|
|
||||||
}
|
|
||||||
randPublic := func() string {
|
|
||||||
return randWord(wordsHoliday, publicsInUse)
|
|
||||||
}
|
|
||||||
randPrivate := func() string {
|
|
||||||
return randWord(wordsHoliday, privatesInUse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO generate .Global=...us major cities?, .Assignments.Public=...?, .Assignments.Private=holiday
|
|
||||||
for k, v := range event.KillWords {
|
|
||||||
if v.Global.Word == "" {
|
if v.Global.Word == "" {
|
||||||
v.Global = KillWord{Word: randGlobal(), Points: 1}
|
v.Global = KillWord{Word: event.AllKillWords.unusedGlobal(), Points: -1}
|
||||||
|
event.AllKillWords[k] = v
|
||||||
}
|
}
|
||||||
if len(v.Assignment.Public) == 0 {
|
if len(v.Assignment.Public) == 0 {
|
||||||
v.Assignment.Public = []KillWord{
|
v.Assignment.Public = []KillWord{}
|
||||||
KillWord{Word: randPublic(), Points: 50},
|
for i := 0; i < 2; i++ {
|
||||||
KillWord{Word: randPublic(), Points: 50},
|
v.Assignment.Public = append(v.Assignment.Public, KillWord{Word: event.AllKillWords.unusedPublic(), Points: 50})
|
||||||
|
event.AllKillWords[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(v.Assignment.Private) == 0 {
|
if len(v.Assignment.Private) == 0 {
|
||||||
v.Assignment.Private = []KillWord{
|
v.Assignment.Private = []KillWord{}
|
||||||
KillWord{Word: randPrivate(), Points: 100},
|
for i := 0; i < 2; i++ {
|
||||||
|
v.Assignment.Private = append(v.Assignment.Private, KillWord{Word: event.AllKillWords.unusedPrivate(), Points: 100})
|
||||||
|
event.AllKillWords[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.KillWords[k] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return games.createEvent(ctx, id, event)
|
return games.createEvent(ctx, id, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed holiday.txt
|
||||||
|
var wordsHoliday string
|
||||||
|
|
||||||
|
func (m AllKillWords) unusedGlobal() string {
|
||||||
|
pool := strings.Fields(wordsHoliday)
|
||||||
|
inUse := func() []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, killWords := range m {
|
||||||
|
result = append(result, killWords.Global.Word)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
picked := pool[rand.Intn(len(pool))]
|
||||||
|
if !slices.Contains(inUse(), picked) {
|
||||||
|
return picked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO hard difficulty
|
||||||
|
func (m AllKillWords) unusedPrivate() string {
|
||||||
|
pool := strings.Fields(wordsHoliday)
|
||||||
|
inUse := func() []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, killWords := range m {
|
||||||
|
for _, killWord := range killWords.Assignment.Private {
|
||||||
|
result = append(result, killWord.Word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
picked := pool[rand.Intn(len(pool))]
|
||||||
|
if !slices.Contains(inUse(), picked) {
|
||||||
|
return picked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO medium difficulty
|
||||||
|
func (m AllKillWords) unusedPublic() string {
|
||||||
|
pool := strings.Fields(wordsHoliday)
|
||||||
|
inUse := func() []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, killWords := range m {
|
||||||
|
for _, killWord := range killWords.Assignment.Public {
|
||||||
|
result = append(result, killWord.Word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
picked := pool[rand.Intn(len(pool))]
|
||||||
|
if !slices.Contains(inUse(), picked) {
|
||||||
|
return picked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (games Games) CreateEventGameComplete(ctx context.Context, id string) error {
|
func (games Games) CreateEventGameComplete(ctx context.Context, id string) error {
|
||||||
if err := games.db.Exec(ctx, `
|
if err := games.db.Exec(ctx, `
|
||||||
UPDATE games
|
UPDATE games
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestGames(t *testing.T) Games {
|
func newTestGames(t *testing.T) Games {
|
||||||
@@ -66,15 +68,41 @@ func TestGames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if name, err := games.UserName(ctx, p); err != nil {
|
if name, err := games.UserName(ctx, p); err != nil {
|
||||||
t.Fatal(p, "err getting user name", err)
|
t.Fatal(p, "err getting user name", err)
|
||||||
} else if name == "" {
|
} else if name != "player "+p {
|
||||||
t.Fatal("name empty")
|
t.Fatal("name wrong", name)
|
||||||
|
}
|
||||||
|
if err := games.UpdateUserName(ctx, p, "player! "+p); err != nil {
|
||||||
|
t.Fatal(p, "failed to rename:", err)
|
||||||
|
} else if name, err := games.UserName(ctx, p); err != nil {
|
||||||
|
t.Fatal(p, "err getting user name", err)
|
||||||
|
} else if name != "player! "+p {
|
||||||
|
t.Fatal("updated name wrong", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if events, err := games.GameEvents(ctx, id, time.Time{}); err != nil {
|
||||||
|
t.Fatal("failed to get player join, leave events:", err)
|
||||||
|
} else if len(events) != 6 {
|
||||||
|
t.Error("wrong number of events:", len(events))
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
if err := games.CreateEventAssignmentRotation(ctx, id, "", "", "", 1); err != nil {
|
if err := games.CreateEventAssignmentRotation(ctx, id, "", "", "", 1); err != nil {
|
||||||
t.Fatal("err creating rotation:", err)
|
t.Fatal("err creating rotation:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if events, err := games.GameEvents(ctx, id, time.Time{}); err != nil {
|
||||||
|
t.Fatal("failed to get player join, leave events:", err)
|
||||||
|
} else if len(events) != 7 {
|
||||||
|
t.Error("wrong number of events:", len(events))
|
||||||
|
} else if events, err = games.GameEvents(ctx, id, now); err != nil {
|
||||||
|
t.Fatal("failed to get assignment rotation event:", err)
|
||||||
|
} else if len(events) != 1 {
|
||||||
|
t.Error("wrong number of events:", len(events))
|
||||||
|
} else if _, ok := events[0].(EventAssignmentRotation); !ok {
|
||||||
|
t.Errorf("not an assignment rotation event: %T", events[0])
|
||||||
|
}
|
||||||
|
|
||||||
if v, err := games.GamesForUser(ctx, "p1"); err != nil {
|
if v, err := games.GamesForUser(ctx, "p1"); err != nil {
|
||||||
t.Error("err getting games for user:", err)
|
t.Error("err getting games for user:", err)
|
||||||
} else if len(v) < 1 {
|
} else if len(v) < 1 {
|
||||||
@@ -96,6 +124,9 @@ func TestGames(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
p := fmt.Sprintf("p%d", i+1)
|
p := fmt.Sprintf("p%d", i+1)
|
||||||
|
if v.Players[p].Points() != 0 {
|
||||||
|
t.Error("nonzero points after zero kills:", v.Players[p].Points())
|
||||||
|
}
|
||||||
if v.Players[p].KillWords.Global.Word == "" {
|
if v.Players[p].KillWords.Global.Word == "" {
|
||||||
t.Error(p, "no killwords.global")
|
t.Error(p, "no killwords.global")
|
||||||
} else if v.Players[p].KillWords.Assigned.IsZero() {
|
} else if v.Players[p].KillWords.Assigned.IsZero() {
|
||||||
@@ -119,3 +150,66 @@ func TestGames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEvent(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
cases := map[string]Event{
|
||||||
|
"player join": EventPlayerJoin{
|
||||||
|
Type: PlayerJoin,
|
||||||
|
ID: "x",
|
||||||
|
},
|
||||||
|
"player leave": EventPlayerLeave{
|
||||||
|
Type: PlayerLeave,
|
||||||
|
ID: "x",
|
||||||
|
},
|
||||||
|
"game complete": EventGameComplete{
|
||||||
|
Type: GameComplete,
|
||||||
|
},
|
||||||
|
"assignment rotation": EventAssignmentRotation{
|
||||||
|
Type: AssignmentRotation,
|
||||||
|
Killer: "x",
|
||||||
|
Victim: "y",
|
||||||
|
KillWord: KillWord{
|
||||||
|
Word: "word",
|
||||||
|
Points: 1,
|
||||||
|
},
|
||||||
|
AllKillWords: map[string]KillWords{
|
||||||
|
"x": KillWords{
|
||||||
|
Global: KillWord{
|
||||||
|
Word: "a",
|
||||||
|
Points: -1,
|
||||||
|
},
|
||||||
|
Assignee: "z",
|
||||||
|
Assigned: now,
|
||||||
|
Assignment: Assignment{
|
||||||
|
Public: []KillWord{{
|
||||||
|
Word: "word2",
|
||||||
|
Points: 2,
|
||||||
|
}},
|
||||||
|
Private: []KillWord{{
|
||||||
|
Word: "word3",
|
||||||
|
Points: 3,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
c := EventWithTime(c, now)
|
||||||
|
b, _ := json.Marshal(c)
|
||||||
|
got, err := parseEvent(b, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotb, _ := json.Marshal(got)
|
||||||
|
if string(b) != string(gotb) {
|
||||||
|
t.Errorf("expected (%T) %+v, but got (%T) %+v", c, c, got, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var last time.Time
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -97,6 +98,13 @@ func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error {
|
|||||||
case <-time.After(time.Second * 1):
|
case <-time.After(time.Second * 1):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if events, err := s.games.GameEvents(ctx, game, last); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(events) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
last = time.Now()
|
||||||
|
|
||||||
gameState, err := s.games.GameState(ctx, game)
|
gameState, err := s.games.GameState(ctx, game)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user