diff --git a/cmd/server/games.go b/cmd/server/games.go index 978f1d6..ab84a67 100644 --- a/cmd/server/games.go +++ b/cmd/server/games.go @@ -156,13 +156,14 @@ type ( Timestamp time.Time } EventAssignmentRotation struct { - Type EventType - Timestamp time.Time - Killer string - Victim string - KillWord KillWord - KillWords map[string]KillWords + Type EventType + Timestamp time.Time + Killer string + Victim string + KillWord KillWord + AllKillWords AllKillWords } + AllKillWords map[string]KillWords ) const ( @@ -286,7 +287,7 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error) result.Players[k] = v } - for k, v := range assignmentRotation.KillWords { + for k, v := range assignmentRotation.AllKillWords { player := result.Players[k] player.KillWords = v result.Players[k] = player @@ -350,9 +351,6 @@ func (games Games) CreateEventPlayerLeave(ctx context.Context, id string, 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 { state, err := games.GameState(ctx, id) if err != nil { @@ -368,7 +366,7 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, Word: word, Points: points, }, - KillWords: map[string]KillWords{}, + AllKillWords: make(AllKillWords), } toAssign := []string{} @@ -379,7 +377,7 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, toAssign = append(toAssign, k) doNotAssign[k] = v.Assignee - event.KillWords[k] = KillWords{ + event.AllKillWords[k] = KillWords{ Global: v.Global, Assigned: now, Assignee: "", @@ -390,13 +388,13 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, if killerState, ok := state.Players[killer]; !ok { } else if victimState, ok := state.Players[victim]; !ok { } else { - event.KillWords[killer] = KillWords{ + event.AllKillWords[killer] = KillWords{ Global: killerState.KillWords.Global, Assigned: now, Assignee: victimState.KillWords.Assignee, 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 { victimState.KillWords.Assignment = Assignment{} @@ -407,17 +405,17 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, for !func() bool { toAssign := slices.Clone(toAssign) doNotAssign := maps.Clone(doNotAssign) - eventKillWords := maps.Clone(event.KillWords) + allKillWords := maps.Clone(event.AllKillWords) for i := range toAssign { j := rand.Intn(i + 1) 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] { return false } - eventKillWords[k] = KillWords{ + allKillWords[k] = KillWords{ Global: v.Global, Assigned: now, Assignee: toAssign[0], @@ -426,66 +424,95 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, toAssign = toAssign[1:] } - event.KillWords = eventKillWords + event.AllKillWords = allKillWords return true }() { } - globalsInUse := map[string]any{} - 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 { + for k, v := range event.AllKillWords { 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 { - v.Assignment.Public = []KillWord{ - KillWord{Word: randPublic(), Points: 50}, - KillWord{Word: randPublic(), Points: 50}, + v.Assignment.Public = []KillWord{} + for i := 0; i < 2; i++ { + v.Assignment.Public = append(v.Assignment.Public, KillWord{Word: event.AllKillWords.unusedPublic(), Points: 50}) + event.AllKillWords[k] = v } } if len(v.Assignment.Private) == 0 { - v.Assignment.Private = []KillWord{ - KillWord{Word: randPrivate(), Points: 100}, + v.Assignment.Private = []KillWord{} + 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) } +//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 { if err := games.db.Exec(ctx, ` UPDATE games diff --git a/cmd/server/games_test.go b/cmd/server/games_test.go index 9510c8f..b39f4fa 100644 --- a/cmd/server/games_test.go +++ b/cmd/server/games_test.go @@ -174,7 +174,7 @@ func TestParseEvent(t *testing.T) { Word: "word", Points: 1, }, - KillWords: map[string]KillWords{ + AllKillWords: map[string]KillWords{ "x": KillWords{ Global: KillWord{ Word: "a",