Compare commits
12 Commits
d36ef7b629
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
617785ad51 | ||
|
|
d2fa707628 | ||
|
|
421331eb71 | ||
|
|
c6ffa12354 | ||
|
|
58fae19522 | ||
|
|
4abac89472 | ||
|
|
15078a626d | ||
|
|
02dc21c124 | ||
|
|
4d9abef04c | ||
|
|
557e1ec6d4 | ||
|
|
3095d54f99 | ||
|
|
d6db4a8a58 |
@@ -11,6 +11,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -172,7 +173,10 @@ type (
|
|||||||
ID string
|
ID string
|
||||||
Started bool
|
Started bool
|
||||||
Completed time.Time
|
Completed time.Time
|
||||||
|
|
||||||
Players map[string]PlayerState
|
Players map[string]PlayerState
|
||||||
|
|
||||||
|
Trial Trial
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerState struct {
|
PlayerState struct {
|
||||||
@@ -187,7 +191,7 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
KillWords struct {
|
KillWords struct {
|
||||||
Global KillWord
|
Codename Codename
|
||||||
|
|
||||||
Assigned time.Time
|
Assigned time.Time
|
||||||
Assignee string
|
Assignee string
|
||||||
@@ -195,6 +199,11 @@ type (
|
|||||||
Assignment Assignment
|
Assignment Assignment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Codename struct {
|
||||||
|
KillWord KillWord
|
||||||
|
Consumed bool
|
||||||
|
}
|
||||||
|
|
||||||
Assignment struct {
|
Assignment struct {
|
||||||
Public []KillWord
|
Public []KillWord
|
||||||
Private []KillWord
|
Private []KillWord
|
||||||
@@ -205,6 +214,12 @@ type (
|
|||||||
Points int
|
Points int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Trial struct {
|
||||||
|
Prosecutor string
|
||||||
|
Defendant string
|
||||||
|
Word string
|
||||||
|
}
|
||||||
|
|
||||||
EventType int
|
EventType int
|
||||||
|
|
||||||
EventPlayerJoin struct {
|
EventPlayerJoin struct {
|
||||||
@@ -234,6 +249,24 @@ type (
|
|||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
|
EventCodenameAccusal struct {
|
||||||
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
|
Prosecutor string
|
||||||
|
Defendant string
|
||||||
|
Word string
|
||||||
|
}
|
||||||
|
EventCodenameTrial struct {
|
||||||
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
|
Guilty bool
|
||||||
|
}
|
||||||
|
EventNotification struct {
|
||||||
|
Type EventType
|
||||||
|
Timestamp time.Time
|
||||||
|
Recipient string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
AllKillWords map[string]KillWords
|
AllKillWords map[string]KillWords
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -243,6 +276,9 @@ const (
|
|||||||
GameComplete
|
GameComplete
|
||||||
AssignmentRotation
|
AssignmentRotation
|
||||||
GameReset
|
GameReset
|
||||||
|
CodenameAccusal
|
||||||
|
CodenameTrial
|
||||||
|
Notification
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event interface{ event() }
|
type Event interface{ event() }
|
||||||
@@ -252,6 +288,9 @@ func (EventPlayerLeave) event() {}
|
|||||||
func (EventGameComplete) event() {}
|
func (EventGameComplete) event() {}
|
||||||
func (EventAssignmentRotation) event() {}
|
func (EventAssignmentRotation) event() {}
|
||||||
func (EventGameReset) event() {}
|
func (EventGameReset) event() {}
|
||||||
|
func (EventCodenameAccusal) event() {}
|
||||||
|
func (EventCodenameTrial) event() {}
|
||||||
|
func (EventNotification) event() {}
|
||||||
|
|
||||||
func EventWithTime(event Event, t time.Time) Event {
|
func EventWithTime(event Event, t time.Time) Event {
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
@@ -270,6 +309,15 @@ func EventWithTime(event Event, t time.Time) Event {
|
|||||||
case EventGameReset:
|
case EventGameReset:
|
||||||
e.Timestamp = t
|
e.Timestamp = t
|
||||||
event = e
|
event = e
|
||||||
|
case EventCodenameAccusal:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
case EventCodenameTrial:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
|
case EventNotification:
|
||||||
|
e.Timestamp = t
|
||||||
|
event = e
|
||||||
}
|
}
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
@@ -324,6 +372,18 @@ func parseEvent(b []byte, timestamp time.Time) (Event, error) {
|
|||||||
var v EventGameReset
|
var v EventGameReset
|
||||||
err := json.Unmarshal(b, &v)
|
err := json.Unmarshal(b, &v)
|
||||||
return EventWithTime(v, timestamp), err
|
return EventWithTime(v, timestamp), err
|
||||||
|
case CodenameAccusal:
|
||||||
|
var v EventCodenameAccusal
|
||||||
|
err := json.Unmarshal(b, &v)
|
||||||
|
return EventWithTime(v, timestamp), err
|
||||||
|
case CodenameTrial:
|
||||||
|
var v EventCodenameTrial
|
||||||
|
err := json.Unmarshal(b, &v)
|
||||||
|
return EventWithTime(v, timestamp), err
|
||||||
|
case Notification:
|
||||||
|
var v EventNotification
|
||||||
|
err := json.Unmarshal(b, &v)
|
||||||
|
return EventWithTime(v, timestamp), err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown event type %d: %s", peek.Type, b)
|
return nil, fmt.Errorf("unknown event type %d: %s", peek.Type, b)
|
||||||
}
|
}
|
||||||
@@ -369,6 +429,46 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
|
|||||||
player.KillWords = v
|
player.KillWords = v
|
||||||
result.Players[k] = player
|
result.Players[k] = player
|
||||||
}
|
}
|
||||||
|
case EventCodenameAccusal:
|
||||||
|
if actual := result.Players[e.Defendant].KillWords.Codename; !actual.Consumed {
|
||||||
|
result.Trial.Prosecutor = e.Prosecutor
|
||||||
|
result.Trial.Defendant = e.Defendant
|
||||||
|
result.Trial.Word = e.Word
|
||||||
|
|
||||||
|
if !basicallyTheSame(actual.KillWord.Word, e.Word) {
|
||||||
|
} else if err := games.CreateEventCodenameTrial(ctx, id, true); err != nil { // TODO cannot be in State loop
|
||||||
|
return GameState{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EventCodenameTrial:
|
||||||
|
if result.Trial == (Trial{}) {
|
||||||
|
} else if e.Guilty {
|
||||||
|
if err := games.CreateEventNotification(ctx, id, fmt.Sprintf(`%s revealed %s is %s and collected %s's bounty.`, result.Trial.Prosecutor, result.Trial.Defendant, result.Trial.Word, result.Trial.Defendant)); err != nil { // TODO not in this loop
|
||||||
|
return GameState{}, err
|
||||||
|
}
|
||||||
|
return GameState{}, fmt.Errorf("not impl: trial: guilty: %+v", e)
|
||||||
|
} else {
|
||||||
|
v := result.Players[result.Trial.Prosecutor]
|
||||||
|
v.KillWords.Codename.Consumed = true
|
||||||
|
v.Kills = append(v.Kills, Kill{
|
||||||
|
Timestamp: e.Timestamp,
|
||||||
|
Victim: result.Trial.Defendant,
|
||||||
|
KillWord: KillWord{
|
||||||
|
Word: result.Trial.Word,
|
||||||
|
Points: -200,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
result.Players[result.Trial.Prosecutor] = v
|
||||||
|
|
||||||
|
v = result.Players[result.Trial.Defendant]
|
||||||
|
v.KillWords.Codename.KillWord.Word = "" // TODO
|
||||||
|
|
||||||
|
return GameState{}, fmt.Errorf("creating state CANNOT create events because it will eval every loop")
|
||||||
|
if err := games.CreateEventNotification(ctx, id, fmt.Sprintf(`%s accused the innocent %s of being %s. %s will get a new codename.`, result.Trial.Prosecutor, result.Trial.Defendant, result.Trial.Word, result.Trial.Defendant)); err != nil {
|
||||||
|
return GameState{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Trial = Trial{}
|
||||||
case EventGameReset:
|
case EventGameReset:
|
||||||
return games.GameState(ctx, e.ID)
|
return games.GameState(ctx, e.ID)
|
||||||
default:
|
default:
|
||||||
@@ -379,6 +479,20 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func basicallyTheSame(a, b string) bool {
|
||||||
|
simplify := func(s string) string {
|
||||||
|
s = strings.TrimSpace(strings.ToLower(s))
|
||||||
|
s2 := ""
|
||||||
|
for _, c := range s {
|
||||||
|
if unicode.IsLetter(c) {
|
||||||
|
s2 = fmt.Sprintf("%s%c", s2, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
return simplify(a) == simplify(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (games Games) CreateGame(ctx context.Context, name string) (string, error) {
|
func (games Games) CreateGame(ctx context.Context, name string) (string, error) {
|
||||||
var exists string
|
var exists string
|
||||||
if err := games.db.Query(ctx,
|
if err := games.db.Query(ctx,
|
||||||
@@ -440,10 +554,7 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
prevAllKillWords := make(AllKillWords)
|
prevAllKillWords := state.AllKillWords()
|
||||||
for k, v := range state.Players {
|
|
||||||
prevAllKillWords[k] = v.KillWords
|
|
||||||
}
|
|
||||||
|
|
||||||
event.AllKillWords = prevAllKillWords.ShuffleAssignees(killer, victim, word)
|
event.AllKillWords = prevAllKillWords.ShuffleAssignees(killer, victim, word)
|
||||||
event.AllKillWords = event.AllKillWords.FillKillWords()
|
event.AllKillWords = event.AllKillWords.FillKillWords()
|
||||||
@@ -451,6 +562,14 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
|
|||||||
return games.createEvent(ctx, id, event)
|
return games.createEvent(ctx, id, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (state GameState) AllKillWords() AllKillWords {
|
||||||
|
m := make(AllKillWords)
|
||||||
|
for k, v := range state.Players {
|
||||||
|
m[k] = v.KillWords
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func (games Games) CreateEventGameReset(ctx context.Context, gid string) error {
|
func (games Games) CreateEventGameReset(ctx context.Context, gid string) error {
|
||||||
state, err := games.GameState(ctx, gid)
|
state, err := games.GameState(ctx, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -478,7 +597,7 @@ func (games Games) CreateEventGameReset(ctx context.Context, gid string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (words KillWords) Empty() bool {
|
func (words KillWords) Empty() bool {
|
||||||
return words.Global == (KillWord{}) && words.Assigned.IsZero() && words.Assignee == "" && words.Assignment.Empty()
|
return words.Codename == (Codename{}) && words.Assigned.IsZero() && words.Assignee == "" && words.Assignment.Empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (words KillWords) Privates() []KillWord {
|
func (words KillWords) Privates() []KillWord {
|
||||||
@@ -549,7 +668,7 @@ func (m AllKillWords) withoutAssignees() AllKillWords {
|
|||||||
result := make(AllKillWords)
|
result := make(AllKillWords)
|
||||||
for k := range m {
|
for k := range m {
|
||||||
result[k] = KillWords{
|
result[k] = KillWords{
|
||||||
Global: m[k].Global,
|
Codename: m[k].Codename,
|
||||||
Assigned: now,
|
Assigned: now,
|
||||||
Assignee: "",
|
Assignee: "",
|
||||||
Assignment: m[k].Assignment,
|
Assignment: m[k].Assignment,
|
||||||
@@ -598,7 +717,7 @@ func (m AllKillWords) FillKillWords() AllKillWords {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m AllKillWords) fillKillWords(
|
func (m AllKillWords) fillKillWords(
|
||||||
poolGlobal []string,
|
poolCodename []string,
|
||||||
nPublic int,
|
nPublic int,
|
||||||
poolPublic []string,
|
poolPublic []string,
|
||||||
nPrivate int,
|
nPrivate int,
|
||||||
@@ -607,8 +726,8 @@ func (m AllKillWords) fillKillWords(
|
|||||||
result := maps.Clone(m)
|
result := maps.Clone(m)
|
||||||
m = result
|
m = result
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
if v.Global.Word == "" {
|
if v.Codename.KillWord.Word == "" {
|
||||||
v.Global = KillWord{Word: m.unusedGlobal(poolGlobal), Points: -1}
|
v.Codename = Codename{KillWord: KillWord{Word: m.unusedCodename(poolCodename), Points: 200}}
|
||||||
m[k] = v
|
m[k] = v
|
||||||
}
|
}
|
||||||
if len(v.Assignment.Public) == 0 {
|
if len(v.Assignment.Public) == 0 {
|
||||||
@@ -629,11 +748,11 @@ func (m AllKillWords) fillKillWords(
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m AllKillWords) unusedGlobal(pool []string) string {
|
func (m AllKillWords) unusedCodename(pool []string) string {
|
||||||
inUse := func() []string {
|
inUse := func() []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for _, killWords := range m {
|
for _, killWords := range m {
|
||||||
result = append(result, killWords.Global.Word)
|
result = append(result, killWords.Codename.KillWord.Word)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -692,6 +811,23 @@ func (games Games) CreateEventGameComplete(ctx context.Context, id string) error
|
|||||||
return games.createEvent(ctx, id, EventGameComplete{Type: GameComplete})
|
return games.createEvent(ctx, id, EventGameComplete{Type: GameComplete})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (games Games) CreateEventCodenameAccusal(ctx context.Context, gid, prosecutor, defendant, codename string) error {
|
||||||
|
return fmt.Errorf("not impl: x accused y")
|
||||||
|
return fmt.Errorf("not impl: x caught by y")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (games Games) CreateEventCodenameTrial(ctx context.Context, gid string, guilty bool) error {
|
||||||
|
return fmt.Errorf("not impl: x found guilty/notguilty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (games Games) CreateEventNotification(ctx context.Context, gid, msg string) error {
|
||||||
|
return games.CreateEventNotificationTo(ctx, gid, "", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (games Games) CreateEventNotificationTo(ctx context.Context, gid, uid, msg string) error {
|
||||||
|
return fmt.Errorf("not impl: simple")
|
||||||
|
}
|
||||||
|
|
||||||
func (games Games) createEvent(ctx context.Context, id string, v any) error {
|
func (games Games) createEvent(ctx context.Context, id string, v any) error {
|
||||||
payload, err := json.Marshal(v)
|
payload, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -706,7 +842,3 @@ func (games Games) createEvent(ctx context.Context, id string, v any) error {
|
|||||||
) VALUES (?, ?, ?)
|
) VALUES (?, ?, ?)
|
||||||
`, id, time.Now(), payload)
|
`, id, time.Now(), payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (games *Games) Reset(ctx context.Context, gid string) error {
|
|
||||||
return games.CreateEventGameReset(ctx, gid)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ func TestGames(t *testing.T) {
|
|||||||
if v.Players[p].Points() != 0 {
|
if v.Players[p].Points() != 0 {
|
||||||
t.Error("nonzero points after zero kills:", v.Players[p].Points())
|
t.Error("nonzero points after zero kills:", v.Players[p].Points())
|
||||||
}
|
}
|
||||||
if v.Players[p].KillWords.Global.Word == "" {
|
if v.Players[p].KillWords.Codename.KillWord.Word == "" {
|
||||||
t.Error(p, "no killwords.global")
|
t.Error(p, "no killwords.Codename")
|
||||||
} else if v.Players[p].KillWords.Assigned.IsZero() {
|
} else if v.Players[p].KillWords.Assigned.IsZero() {
|
||||||
t.Error(p, "no killwords.assigned")
|
t.Error(p, "no killwords.assigned")
|
||||||
} else if v.Players[p].KillWords.Assignee == "" {
|
} else if v.Players[p].KillWords.Assignee == "" {
|
||||||
@@ -150,7 +150,7 @@ func TestGames(t *testing.T) {
|
|||||||
t.Fatal("state.Completed is zero")
|
t.Fatal("state.Completed is zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := games.Reset(ctx, id); err != nil {
|
if err := games.CreateEventGameReset(ctx, id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if state, err := games.GameState(ctx, id); err != nil {
|
} else if state, err := games.GameState(ctx, id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -199,10 +199,10 @@ func TestParseEvent(t *testing.T) {
|
|||||||
},
|
},
|
||||||
AllKillWords: map[string]KillWords{
|
AllKillWords: map[string]KillWords{
|
||||||
"x": KillWords{
|
"x": KillWords{
|
||||||
Global: KillWord{
|
Codename: Codename{KillWord: KillWord{
|
||||||
Word: "a",
|
Word: "a",
|
||||||
Points: -1,
|
Points: 200,
|
||||||
},
|
}},
|
||||||
Assignee: "z",
|
Assignee: "z",
|
||||||
Assigned: now,
|
Assigned: now,
|
||||||
Assignment: Assignment{
|
Assignment: Assignment{
|
||||||
@@ -260,48 +260,48 @@ func TestAllKillWordsFill(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"full": {
|
"full": {
|
||||||
given: KillWords{
|
given: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("pub", "pri"),
|
Assignment: ass("pub", "pri"),
|
||||||
},
|
},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("pub", "pri"),
|
Assignment: ass("pub", "pri"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"no ass": {
|
"no ass": {
|
||||||
given: KillWords{
|
given: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: Assignment{},
|
Assignment: Assignment{},
|
||||||
},
|
},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("filled-public", "filled-private"),
|
Assignment: ass("filled-public", "filled-private"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"no pub": {
|
"no pub": {
|
||||||
given: KillWords{
|
given: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("", "pri"),
|
Assignment: ass("", "pri"),
|
||||||
},
|
},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("filled-public", "pri"),
|
Assignment: ass("filled-public", "pri"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"no pri": {
|
"no pri": {
|
||||||
given: KillWords{
|
given: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("pub", ""),
|
Assignment: ass("pub", ""),
|
||||||
},
|
},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "global"),
|
Codename: Codename{KillWord: kw(200, "global")},
|
||||||
Assignment: ass("pub", "filled-private"),
|
Assignment: ass("pub", "filled-private"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
given: KillWords{},
|
given: KillWords{},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "filled-global"),
|
Codename: Codename{KillWord: kw(200, "filled-global")},
|
||||||
Assignment: ass("filled-public", "filled-private"),
|
Assignment: ass("filled-public", "filled-private"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -310,7 +310,7 @@ func TestAllKillWordsFill(t *testing.T) {
|
|||||||
Assignment: ass("pub", "pri"),
|
Assignment: ass("pub", "pri"),
|
||||||
},
|
},
|
||||||
expect: KillWords{
|
expect: KillWords{
|
||||||
Global: kw(-1, "filled-global"),
|
Codename: Codename{KillWord: kw(200, "filled-global")},
|
||||||
Assignment: ass("pub", "pri"),
|
Assignment: ass("pub", "pri"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -354,7 +354,7 @@ func TestAllKillWordsUnused(t *testing.T) {
|
|||||||
t.Error("empty playerbase didnt think only option was unused")
|
t.Error("empty playerbase didnt think only option was unused")
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := akw.unusedGlobal([]string{"x"}); got != "x" {
|
if got := akw.unusedCodename([]string{"x"}); got != "x" {
|
||||||
t.Error("empty playerbase didnt think only option was unused")
|
t.Error("empty playerbase didnt think only option was unused")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -363,7 +363,7 @@ func TestAllKillWordsUnused(t *testing.T) {
|
|||||||
t.Run("private", func(t *testing.T) {
|
t.Run("private", func(t *testing.T) {
|
||||||
akw := make(AllKillWords)
|
akw := make(AllKillWords)
|
||||||
akw["k"] = KillWords{
|
akw["k"] = KillWords{
|
||||||
Global: KillWord{Word: "x"},
|
Codename: Codename{KillWord: KillWord{Word: "x"}},
|
||||||
Assignment: Assignment{
|
Assignment: Assignment{
|
||||||
Private: []KillWord{{}, {Word: "y"}},
|
Private: []KillWord{{}, {Word: "y"}},
|
||||||
Public: []KillWord{{}, {Word: "x"}},
|
Public: []KillWord{{}, {Word: "x"}},
|
||||||
@@ -377,13 +377,13 @@ func TestAllKillWordsUnused(t *testing.T) {
|
|||||||
t.Run("global", func(t *testing.T) {
|
t.Run("global", func(t *testing.T) {
|
||||||
akw := make(AllKillWords)
|
akw := make(AllKillWords)
|
||||||
akw["k"] = KillWords{
|
akw["k"] = KillWords{
|
||||||
Global: KillWord{Word: "y"},
|
Codename: Codename{KillWord: KillWord{Word: "y"}},
|
||||||
Assignment: Assignment{
|
Assignment: Assignment{
|
||||||
Private: []KillWord{{}, {Word: "x"}},
|
Private: []KillWord{{}, {Word: "x"}},
|
||||||
Public: []KillWord{{}, {Word: "x"}},
|
Public: []KillWord{{}, {Word: "x"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := akw.unusedGlobal([]string{"x", "y"})
|
got := akw.unusedCodename([]string{"x", "y"})
|
||||||
if got != "x" {
|
if got != "x" {
|
||||||
t.Error("didnt return only unused option")
|
t.Error("didnt return only unused option")
|
||||||
}
|
}
|
||||||
@@ -391,7 +391,7 @@ func TestAllKillWordsUnused(t *testing.T) {
|
|||||||
t.Run("public", func(t *testing.T) {
|
t.Run("public", func(t *testing.T) {
|
||||||
akw := make(AllKillWords)
|
akw := make(AllKillWords)
|
||||||
akw["k"] = KillWords{
|
akw["k"] = KillWords{
|
||||||
Global: KillWord{Word: "x"},
|
Codename: Codename{KillWord: KillWord{Word: "x"}},
|
||||||
Assignment: Assignment{
|
Assignment: Assignment{
|
||||||
Private: []KillWord{{}, {Word: "x"}},
|
Private: []KillWord{{}, {Word: "x"}},
|
||||||
Public: []KillWord{{}, {Word: "y"}},
|
Public: []KillWord{{}, {Word: "y"}},
|
||||||
|
|||||||
@@ -59,13 +59,19 @@ type Session struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
||||||
id, err := r.Cookie("uuid")
|
id := r.Header.Get("uuid")
|
||||||
if err != nil || id.Value == "" {
|
if id == "" {
|
||||||
|
c, _ := r.Cookie("uuid")
|
||||||
|
if c != nil {
|
||||||
|
id = c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if id == "" {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
ctx = context.WithValue(ctx, "session", Session{
|
ctx = context.WithValue(ctx, "session", Session{
|
||||||
ID: id.Value,
|
ID: id,
|
||||||
})
|
})
|
||||||
*r = *r.WithContext(ctx)
|
*r = *r.WithContext(ctx)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
|
|||||||
var points int
|
var points int
|
||||||
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
|
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if global := gameState.Players[killer].KillWords.Global; global.Word == word {
|
} else if codename := gameState.Players[killer].KillWords.Codename.KillWord; codename.Word == word {
|
||||||
points = global.Points
|
points = codename.Points
|
||||||
} else if matches := slices.DeleteFunc(gameState.Players[victim].KillWords.Publics(), func(kw KillWord) bool { return kw.Word != word }); len(matches) > 0 {
|
} else if matches := slices.DeleteFunc(gameState.Players[victim].KillWords.Publics(), func(kw KillWord) bool { return kw.Word != word }); len(matches) > 0 {
|
||||||
points = matches[0].Points
|
points = matches[0].Points
|
||||||
} else if matches := slices.DeleteFunc(gameState.Players[victim].KillWords.Privates(), func(kw KillWord) bool { return kw.Word != word }); len(matches) > 0 {
|
} else if matches := slices.DeleteFunc(gameState.Players[victim].KillWords.Privates(), func(kw KillWord) bool { return kw.Word != word }); len(matches) > 0 {
|
||||||
@@ -116,7 +116,7 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
|
|||||||
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
|
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if gameState.Completed.IsZero() {
|
} else if gameState.Completed.IsZero() {
|
||||||
} else if err := ugs.games.Reset(ctx, ugs.ID); err != nil {
|
} else if err := ugs.games.CreateEventGameReset(ctx, ugs.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -151,7 +151,7 @@ func (ugs *UserGameServer) State(ctx context.Context) (UserGameState, error) {
|
|||||||
if isSelf := k == ugs.Session.ID; isSelf {
|
if isSelf := k == ugs.Session.ID; isSelf {
|
||||||
v.KillWords.Assignment = Assignment{}
|
v.KillWords.Assignment = Assignment{}
|
||||||
} else {
|
} else {
|
||||||
v.KillWords.Global = KillWord{}
|
v.KillWords.Codename = Codename{}
|
||||||
v.KillWords.Assignee = ""
|
v.KillWords.Assignee = ""
|
||||||
for i := range v.Kills {
|
for i := range v.Kills {
|
||||||
v.Kills[i].Victim = ""
|
v.Kills[i].Victim = ""
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isSelf := pid == ugs.Session.ID; isSelf {
|
if isSelf := pid == ugs.Session.ID; isSelf {
|
||||||
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
|
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
|
||||||
t.Error("self global missing field")
|
t.Error("self codename missing field")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee == "" {
|
if p.KillWords.Assignee == "" {
|
||||||
t.Error("assignee is empty")
|
t.Error("assignee is empty")
|
||||||
@@ -96,8 +96,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
t.Error("self knows its own private")
|
t.Error("self knows its own private")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !p.KillWords.Global.Empty() {
|
if !p.KillWords.Codename.KillWord.Empty() {
|
||||||
t.Error("can see not self global")
|
t.Error("can see not self Codename")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee != "" {
|
if p.KillWords.Assignee != "" {
|
||||||
t.Error("can see other player's assignee")
|
t.Error("can see other player's assignee")
|
||||||
@@ -167,8 +167,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
} else if kill.KillWord.Word == "" {
|
} else if kill.KillWord.Word == "" {
|
||||||
t.Errorf("dont know own kill word")
|
t.Errorf("dont know own kill word")
|
||||||
}
|
}
|
||||||
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
|
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
|
||||||
t.Error("self global missing field")
|
t.Error("self Codename missing field")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee == "" {
|
if p.KillWords.Assignee == "" {
|
||||||
t.Error("assignee is empty")
|
t.Error("assignee is empty")
|
||||||
@@ -190,8 +190,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
} else if kill.KillWord.Word != "" {
|
} else if kill.KillWord.Word != "" {
|
||||||
t.Errorf("know other's kill word")
|
t.Errorf("know other's kill word")
|
||||||
}
|
}
|
||||||
if !p.KillWords.Global.Empty() {
|
if !p.KillWords.Codename.KillWord.Empty() {
|
||||||
t.Error("can see not self global")
|
t.Error("can see not self Codename")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee != "" {
|
if p.KillWords.Assignee != "" {
|
||||||
t.Error("can see other player's assignee")
|
t.Error("can see other player's assignee")
|
||||||
@@ -248,8 +248,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
} else if kill.KillWord.Word == "" {
|
} else if kill.KillWord.Word == "" {
|
||||||
t.Errorf("dont know own kill word")
|
t.Errorf("dont know own kill word")
|
||||||
}
|
}
|
||||||
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
|
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
|
||||||
t.Error("self global missing field")
|
t.Error("self Codename missing field")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee == "" {
|
if p.KillWords.Assignee == "" {
|
||||||
t.Error("assignee is empty")
|
t.Error("assignee is empty")
|
||||||
@@ -271,8 +271,8 @@ func TestUserGameServer(t *testing.T) {
|
|||||||
} else if kill.KillWord.Word == "" {
|
} else if kill.KillWord.Word == "" {
|
||||||
t.Errorf("dont know other's kill word")
|
t.Errorf("dont know other's kill word")
|
||||||
}
|
}
|
||||||
if p.KillWords.Global.Empty() {
|
if p.KillWords.Codename.KillWord.Empty() {
|
||||||
t.Error("cannot see not self global")
|
t.Error("cannot see not self Codename")
|
||||||
}
|
}
|
||||||
if p.KillWords.Assignee == "" {
|
if p.KillWords.Assignee == "" {
|
||||||
t.Error("cannot see other player's assignee")
|
t.Error("cannot see other player's assignee")
|
||||||
|
|||||||
@@ -191,10 +191,8 @@ func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameSta
|
|||||||
|
|
||||||
tags := []inProgressMsgItemTag{}
|
tags := []inProgressMsgItemTag{}
|
||||||
|
|
||||||
if hasBeenKilledWithGlobal := slices.ContainsFunc(self.Kills, func(a Kill) bool {
|
if canKillWithCodename := !self.KillWords.Codename.Consumed; canKillWithCodename {
|
||||||
return a.Victim == uid && a.KillWord.Word == self.KillWords.Global.Word
|
tags = append(tags, newInProgressMsgItemTag(self.KillWords.Codename.KillWord))
|
||||||
}); !hasBeenKilledWithGlobal {
|
|
||||||
tags = append(tags, newInProgressMsgItemTag(self.KillWords.Global))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, killWord := range append(
|
for _, killWord := range append(
|
||||||
|
|||||||
@@ -122,13 +122,17 @@ type Session struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
||||||
id, err := r.Cookie("uuid")
|
id := r.URL.Query().Get("uuid")
|
||||||
if err != nil || id.Value == "" {
|
if id == "" {
|
||||||
|
c, err := r.Cookie("uuid")
|
||||||
|
if err != nil || c.Value == "" {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
id = c.Value
|
||||||
|
}
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
ctx = context.WithValue(ctx, "session", Session{
|
ctx = context.WithValue(ctx, "session", Session{
|
||||||
ID: id.Value,
|
ID: id,
|
||||||
})
|
})
|
||||||
*r = *r.WithContext(ctx)
|
*r = *r.WithContext(ctx)
|
||||||
return nil
|
return nil
|
||||||
@@ -142,7 +146,9 @@ func (s *S) Session(ctx context.Context) Session {
|
|||||||
func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error {
|
func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
c, err := websocket.Accept(w, r, nil)
|
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
16
cmd/testws/index.html
Normal file
16
cmd/testws/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<html>
|
||||||
|
<header>
|
||||||
|
<script>
|
||||||
|
function log(msg) {
|
||||||
|
console.log(msg)
|
||||||
|
}
|
||||||
|
const ws = new Websocket("wss://out-test.breel.dev/ws")
|
||||||
|
ws.onmessage = () => { console.log("got a message") }
|
||||||
|
ws.onerror = () => { console.log("got an error") }
|
||||||
|
ws.onclose = () => { console.log("closed") }
|
||||||
|
</script>
|
||||||
|
</header>
|
||||||
|
<body>
|
||||||
|
<div id="msg"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
todo.yaml
12
todo.yaml
@@ -1,4 +1,14 @@
|
|||||||
todo:
|
todo:
|
||||||
|
- global+public+private to public+private+CODENAME
|
||||||
|
- accuse event which either ends in a successful codename call worth 100% points +
|
||||||
|
disables codename OR ends in a failed codename call costing 50% codename points
|
||||||
|
- report system
|
||||||
|
- how to generate word lists??
|
||||||
|
- '"handler" system; there are both assassinations AND tenet-friendship-codeword systems
|
||||||
|
in-flight'
|
||||||
|
- dont like other players points; just order by points so winner is at top of list
|
||||||
|
- comeback/rebound system
|
||||||
|
- kingkiller system, increased bounty for those who havent died recently + high scorers
|
||||||
- test ws flow
|
- test ws flow
|
||||||
- notifications system with dismissal server-side so users see X got a kill
|
- notifications system with dismissal server-side so users see X got a kill
|
||||||
- play mp3 on kill + shuffle
|
- play mp3 on kill + shuffle
|
||||||
@@ -14,3 +24,5 @@ done:
|
|||||||
ts: Sun Dec 15 14:28:29 MST 2024
|
ts: Sun Dec 15 14:28:29 MST 2024
|
||||||
- todo: quit
|
- todo: quit
|
||||||
ts: Sun Dec 15 14:28:34 MST 2024
|
ts: Sun Dec 15 14:28:34 MST 2024
|
||||||
|
- todo: '"handler" system??'
|
||||||
|
ts: Sun Dec 15 16:43:34 MST 2024
|
||||||
|
|||||||
Reference in New Issue
Block a user