Compare commits
9 Commits
c18f154328
...
94d3d97645
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94d3d97645 | ||
|
|
eb3a30ec8a | ||
|
|
6340469d53 | ||
|
|
d05ab6d0ec | ||
|
|
ae593bc092 | ||
|
|
ff5071215a | ||
|
|
b9d06f81ba | ||
|
|
51c7cc4496 | ||
|
|
d7e4196f61 |
@@ -356,7 +356,6 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
event := EventAssignmentRotation{
|
||||
Type: AssignmentRotation,
|
||||
@@ -366,98 +365,152 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
||||
Word: word,
|
||||
Points: points,
|
||||
},
|
||||
AllKillWords: make(AllKillWords),
|
||||
}
|
||||
|
||||
toAssign := []string{}
|
||||
doNotAssign := map[string]string{}
|
||||
|
||||
prevAllKillWords := make(AllKillWords)
|
||||
for k, v := range state.Players {
|
||||
v := v.KillWords
|
||||
toAssign = append(toAssign, k)
|
||||
doNotAssign[k] = v.Assignee
|
||||
|
||||
event.AllKillWords[k] = KillWords{
|
||||
Global: v.Global,
|
||||
Assigned: now,
|
||||
Assignee: "",
|
||||
Assignment: v.Assignment,
|
||||
}
|
||||
prevAllKillWords[k] = v.KillWords
|
||||
}
|
||||
|
||||
if killerState, ok := state.Players[killer]; !ok {
|
||||
} else if victimState, ok := state.Players[victim]; !ok {
|
||||
event.AllKillWords = prevAllKillWords.ShuffleAssignees(killer, victim, word)
|
||||
event.AllKillWords = event.AllKillWords.FillKillWords()
|
||||
|
||||
return games.createEvent(ctx, id, event)
|
||||
}
|
||||
|
||||
func (prev AllKillWords) ShuffleAssignees(killer, victim, word string) AllKillWords {
|
||||
m := prev.withoutAssignees()
|
||||
|
||||
if _, ok := prev[killer]; !ok {
|
||||
} else if victimState, ok := prev[victim]; !ok {
|
||||
} else {
|
||||
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.AllKillWords[killer].Assignee })
|
||||
|
||||
if killerState.KillWords.Global.Word != word {
|
||||
victimState.KillWords.Assignment = Assignment{}
|
||||
state.Players[victim] = victimState
|
||||
}
|
||||
m.assign(killer, victimState.Assignee)
|
||||
}
|
||||
|
||||
for !func() bool {
|
||||
toAssign := slices.Clone(toAssign)
|
||||
doNotAssign := maps.Clone(doNotAssign)
|
||||
allKillWords := maps.Clone(event.AllKillWords)
|
||||
|
||||
for i := range toAssign {
|
||||
j := rand.Intn(i + 1)
|
||||
toAssign[i], toAssign[j] = toAssign[j], toAssign[i]
|
||||
allKillWords := maps.Clone(m)
|
||||
unassigned := allKillWords.unassigned()
|
||||
pop := func() string {
|
||||
result := unassigned[0]
|
||||
unassigned = unassigned[1:]
|
||||
return result
|
||||
}
|
||||
|
||||
for k, v := range allKillWords {
|
||||
if k == toAssign[0] || doNotAssign[k] == toAssign[0] {
|
||||
if v.Assignee != "" {
|
||||
continue
|
||||
}
|
||||
v.Assignee = pop()
|
||||
if k == v.Assignee || prev[k].Assignee == v.Assignee {
|
||||
return false
|
||||
}
|
||||
allKillWords[k] = KillWords{
|
||||
Global: v.Global,
|
||||
Assigned: now,
|
||||
Assignee: toAssign[0],
|
||||
Assignment: v.Assignment,
|
||||
}
|
||||
toAssign = toAssign[1:]
|
||||
allKillWords[k] = v
|
||||
}
|
||||
|
||||
event.AllKillWords = allKillWords
|
||||
if len(unassigned) > 0 {
|
||||
panic(unassigned)
|
||||
}
|
||||
|
||||
m = allKillWords
|
||||
return true
|
||||
}() {
|
||||
}
|
||||
|
||||
for k, v := range event.AllKillWords {
|
||||
if v.Global.Word == "" {
|
||||
v.Global = KillWord{Word: event.AllKillWords.unusedGlobal(), Points: -1}
|
||||
event.AllKillWords[k] = v
|
||||
}
|
||||
if len(v.Assignment.Public) == 0 {
|
||||
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{}
|
||||
for i := 0; i < 2; i++ {
|
||||
v.Assignment.Private = append(v.Assignment.Private, KillWord{Word: event.AllKillWords.unusedPrivate(), Points: 100})
|
||||
event.AllKillWords[k] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return games.createEvent(ctx, id, event)
|
||||
func (m AllKillWords) assign(killer, victim string) {
|
||||
v := m[killer]
|
||||
v.Assignee = victim
|
||||
m[killer] = v
|
||||
}
|
||||
|
||||
func (m AllKillWords) withoutAssignees() AllKillWords {
|
||||
now := time.Now()
|
||||
result := make(AllKillWords)
|
||||
for k := range m {
|
||||
result[k] = KillWords{
|
||||
Global: m[k].Global,
|
||||
Assigned: now,
|
||||
Assignee: "",
|
||||
Assignment: m[k].Assignment,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m AllKillWords) unassigned() []string {
|
||||
var result []string
|
||||
for k := range m {
|
||||
if !slices.Contains(m.assigned(), k) {
|
||||
result = append(result, k)
|
||||
}
|
||||
}
|
||||
for i := range result {
|
||||
j := rand.Intn(i + 1)
|
||||
result[i], result[j] = result[j], result[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m AllKillWords) assigned() []string {
|
||||
var result []string
|
||||
for k := range m {
|
||||
v := m[k].Assignee
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//go:embed holiday.txt
|
||||
var wordsHoliday string
|
||||
|
||||
func (m AllKillWords) unusedGlobal() string {
|
||||
pool := strings.Fields(wordsHoliday)
|
||||
func (m AllKillWords) FillKillWords() AllKillWords {
|
||||
return m.fillKillWords(
|
||||
strings.Fields(wordsHoliday),
|
||||
1,
|
||||
strings.Fields(wordsHoliday), // TODO medium difficulty
|
||||
2,
|
||||
strings.Fields(wordsHoliday), // TODO hard difficulty
|
||||
)
|
||||
}
|
||||
|
||||
func (m AllKillWords) fillKillWords(
|
||||
poolGlobal []string,
|
||||
nPublic int,
|
||||
poolPublic []string,
|
||||
nPrivate int,
|
||||
poolPrivate []string,
|
||||
) AllKillWords {
|
||||
result := maps.Clone(m)
|
||||
m = result
|
||||
for k, v := range m {
|
||||
if v.Global.Word == "" {
|
||||
v.Global = KillWord{Word: m.unusedGlobal(poolGlobal), Points: -1}
|
||||
m[k] = v
|
||||
}
|
||||
if len(v.Assignment.Public) == 0 {
|
||||
v.Assignment.Public = []KillWord{}
|
||||
for i := 0; i < nPublic; i++ {
|
||||
v.Assignment.Public = append(v.Assignment.Public, KillWord{Word: m.unusedPublic(poolPublic), Points: 50})
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
if len(v.Assignment.Private) == 0 {
|
||||
v.Assignment.Private = []KillWord{}
|
||||
for i := 0; i < nPrivate; i++ {
|
||||
v.Assignment.Private = append(v.Assignment.Private, KillWord{Word: m.unusedPrivate(poolPrivate), Points: 100})
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m AllKillWords) unusedGlobal(pool []string) string {
|
||||
inUse := func() []string {
|
||||
result := []string{}
|
||||
for _, killWords := range m {
|
||||
@@ -473,9 +526,7 @@ func (m AllKillWords) unusedGlobal() string {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO hard difficulty
|
||||
func (m AllKillWords) unusedPrivate() string {
|
||||
pool := strings.Fields(wordsHoliday)
|
||||
func (m AllKillWords) unusedPrivate(pool []string) string {
|
||||
inUse := func() []string {
|
||||
result := []string{}
|
||||
for _, killWords := range m {
|
||||
@@ -493,9 +544,7 @@ func (m AllKillWords) unusedPrivate() string {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO medium difficulty
|
||||
func (m AllKillWords) unusedPublic() string {
|
||||
pool := strings.Fields(wordsHoliday)
|
||||
func (m AllKillWords) unusedPublic(pool []string) string {
|
||||
inUse := func() []string {
|
||||
result := []string{}
|
||||
for _, killWords := range m {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -213,3 +214,171 @@ func TestParseEvent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllKillWordsFill(t *testing.T) {
|
||||
kw := func(p int, w string) KillWord {
|
||||
return KillWord{Word: w, Points: p}
|
||||
}
|
||||
kws := func(points int, w string) []KillWord {
|
||||
if w == "" {
|
||||
return nil
|
||||
}
|
||||
return []KillWord{kw(points, w)}
|
||||
}
|
||||
ass := func(pub, pri string) Assignment {
|
||||
return Assignment{
|
||||
Public: kws(50, pub),
|
||||
Private: kws(100, pri),
|
||||
}
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
given KillWords
|
||||
expect KillWords
|
||||
}{
|
||||
"full": {
|
||||
given: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("pub", "pri"),
|
||||
},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("pub", "pri"),
|
||||
},
|
||||
},
|
||||
"no ass": {
|
||||
given: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: Assignment{},
|
||||
},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("filled-public", "filled-private"),
|
||||
},
|
||||
},
|
||||
"no pub": {
|
||||
given: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("", "pri"),
|
||||
},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("filled-public", "pri"),
|
||||
},
|
||||
},
|
||||
"no pri": {
|
||||
given: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("pub", ""),
|
||||
},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "global"),
|
||||
Assignment: ass("pub", "filled-private"),
|
||||
},
|
||||
},
|
||||
"empty": {
|
||||
given: KillWords{},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "filled-global"),
|
||||
Assignment: ass("filled-public", "filled-private"),
|
||||
},
|
||||
},
|
||||
"no global": {
|
||||
given: KillWords{
|
||||
Assignment: ass("pub", "pri"),
|
||||
},
|
||||
expect: KillWords{
|
||||
Global: kw(-1, "filled-global"),
|
||||
Assignment: ass("pub", "pri"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
equal := func(a, b KillWords) bool {
|
||||
ba, _ := json.Marshal(a)
|
||||
bb, _ := json.Marshal(b)
|
||||
return bytes.Equal(ba, bb)
|
||||
}
|
||||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
t.Run(name, func(t *testing.T) {
|
||||
akw := make(AllKillWords)
|
||||
akw[name] = c.given
|
||||
akw = akw.fillKillWords(
|
||||
[]string{"filled-global"},
|
||||
1,
|
||||
[]string{"filled-public"},
|
||||
1,
|
||||
[]string{"filled-private"},
|
||||
)
|
||||
got := akw[name]
|
||||
|
||||
if !equal(c.expect, got) {
|
||||
t.Errorf("expected \n\t%+v but got \n\t%+v", c.expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllKillWordsUnused(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
akw := make(AllKillWords)
|
||||
if got := akw.unusedPublic([]string{"x"}); got != "x" {
|
||||
t.Error("empty playerbase didnt think only option was unused")
|
||||
}
|
||||
|
||||
if got := akw.unusedPrivate([]string{"x"}); got != "x" {
|
||||
t.Error("empty playerbase didnt think only option was unused")
|
||||
}
|
||||
|
||||
if got := akw.unusedGlobal([]string{"x"}); got != "x" {
|
||||
t.Error("empty playerbase didnt think only option was unused")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dont return used", func(t *testing.T) {
|
||||
t.Run("private", func(t *testing.T) {
|
||||
akw := make(AllKillWords)
|
||||
akw["k"] = KillWords{
|
||||
Global: KillWord{Word: "x"},
|
||||
Assignment: Assignment{
|
||||
Private: []KillWord{{}, {Word: "y"}},
|
||||
Public: []KillWord{{}, {Word: "x"}},
|
||||
},
|
||||
}
|
||||
got := akw.unusedPrivate([]string{"x", "y"})
|
||||
if got != "x" {
|
||||
t.Error("didnt return only unused option")
|
||||
}
|
||||
})
|
||||
t.Run("global", func(t *testing.T) {
|
||||
akw := make(AllKillWords)
|
||||
akw["k"] = KillWords{
|
||||
Global: KillWord{Word: "y"},
|
||||
Assignment: Assignment{
|
||||
Private: []KillWord{{}, {Word: "x"}},
|
||||
Public: []KillWord{{}, {Word: "x"}},
|
||||
},
|
||||
}
|
||||
got := akw.unusedGlobal([]string{"x", "y"})
|
||||
if got != "x" {
|
||||
t.Error("didnt return only unused option")
|
||||
}
|
||||
})
|
||||
t.Run("public", func(t *testing.T) {
|
||||
akw := make(AllKillWords)
|
||||
akw["k"] = KillWords{
|
||||
Global: KillWord{Word: "x"},
|
||||
Assignment: Assignment{
|
||||
Private: []KillWord{{}, {Word: "x"}},
|
||||
Public: []KillWord{{}, {Word: "y"}},
|
||||
},
|
||||
}
|
||||
got := akw.unusedPublic([]string{"x", "y"})
|
||||
if got != "x" {
|
||||
t.Error("didnt return only unused option")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user