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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
event := EventAssignmentRotation{
|
event := EventAssignmentRotation{
|
||||||
Type: AssignmentRotation,
|
Type: AssignmentRotation,
|
||||||
@@ -366,98 +365,152 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
Word: word,
|
Word: word,
|
||||||
Points: points,
|
Points: points,
|
||||||
},
|
},
|
||||||
AllKillWords: make(AllKillWords),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toAssign := []string{}
|
prevAllKillWords := make(AllKillWords)
|
||||||
doNotAssign := map[string]string{}
|
|
||||||
|
|
||||||
for k, v := range state.Players {
|
for k, v := range state.Players {
|
||||||
v := v.KillWords
|
prevAllKillWords[k] = v.KillWords
|
||||||
toAssign = append(toAssign, k)
|
|
||||||
doNotAssign[k] = v.Assignee
|
|
||||||
|
|
||||||
event.AllKillWords[k] = KillWords{
|
|
||||||
Global: v.Global,
|
|
||||||
Assigned: now,
|
|
||||||
Assignee: "",
|
|
||||||
Assignment: v.Assignment,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if killerState, ok := state.Players[killer]; !ok {
|
event.AllKillWords = prevAllKillWords.ShuffleAssignees(killer, victim, word)
|
||||||
} else if victimState, ok := state.Players[victim]; !ok {
|
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 {
|
} else {
|
||||||
event.AllKillWords[killer] = KillWords{
|
m.assign(killer, victimState.Assignee)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for !func() bool {
|
for !func() bool {
|
||||||
toAssign := slices.Clone(toAssign)
|
allKillWords := maps.Clone(m)
|
||||||
doNotAssign := maps.Clone(doNotAssign)
|
unassigned := allKillWords.unassigned()
|
||||||
allKillWords := maps.Clone(event.AllKillWords)
|
pop := func() string {
|
||||||
|
result := unassigned[0]
|
||||||
for i := range toAssign {
|
unassigned = unassigned[1:]
|
||||||
j := rand.Intn(i + 1)
|
return result
|
||||||
toAssign[i], toAssign[j] = toAssign[j], toAssign[i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range allKillWords {
|
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
|
return false
|
||||||
}
|
}
|
||||||
allKillWords[k] = KillWords{
|
allKillWords[k] = v
|
||||||
Global: v.Global,
|
|
||||||
Assigned: now,
|
|
||||||
Assignee: toAssign[0],
|
|
||||||
Assignment: v.Assignment,
|
|
||||||
}
|
|
||||||
toAssign = toAssign[1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.AllKillWords = allKillWords
|
if len(unassigned) > 0 {
|
||||||
|
panic(unassigned)
|
||||||
|
}
|
||||||
|
|
||||||
|
m = allKillWords
|
||||||
return true
|
return true
|
||||||
}() {
|
}() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range event.AllKillWords {
|
return m
|
||||||
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 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
|
//go:embed holiday.txt
|
||||||
var wordsHoliday string
|
var wordsHoliday string
|
||||||
|
|
||||||
func (m AllKillWords) unusedGlobal() string {
|
func (m AllKillWords) FillKillWords() AllKillWords {
|
||||||
pool := strings.Fields(wordsHoliday)
|
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 {
|
inUse := func() []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for _, killWords := range m {
|
for _, killWords := range m {
|
||||||
@@ -473,9 +526,7 @@ func (m AllKillWords) unusedGlobal() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO hard difficulty
|
func (m AllKillWords) unusedPrivate(pool []string) string {
|
||||||
func (m AllKillWords) unusedPrivate() string {
|
|
||||||
pool := strings.Fields(wordsHoliday)
|
|
||||||
inUse := func() []string {
|
inUse := func() []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for _, killWords := range m {
|
for _, killWords := range m {
|
||||||
@@ -493,9 +544,7 @@ func (m AllKillWords) unusedPrivate() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO medium difficulty
|
func (m AllKillWords) unusedPublic(pool []string) string {
|
||||||
func (m AllKillWords) unusedPublic() string {
|
|
||||||
pool := strings.Fields(wordsHoliday)
|
|
||||||
inUse := func() []string {
|
inUse := func() []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for _, killWords := range m {
|
for _, killWords := range m {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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