Compare commits

..

4 Commits

Author SHA1 Message Date
Bel LaPointe
c6ffa12354 grr how do trial 2024-12-15 19:03:35 -07:00
Bel LaPointe
58fae19522 stub codename trial, accuse events 2024-12-15 18:43:15 -07:00
Bel LaPointe
4abac89472 use codename.Consumed instead of kills to know whether codename is available for murdering 2024-12-15 18:36:19 -07:00
Bel LaPointe
15078a626d Global-1 to Codename+200 2024-12-15 18:25:47 -07:00
5 changed files with 97 additions and 53 deletions

View File

@@ -187,7 +187,7 @@ type (
}
KillWords struct {
Global KillWord
Codename Codename
Assigned time.Time
Assignee string
@@ -195,6 +195,11 @@ type (
Assignment Assignment
}
Codename struct {
KillWord KillWord
Consumed bool
}
Assignment struct {
Public []KillWord
Private []KillWord
@@ -234,6 +239,17 @@ type (
Timestamp time.Time
ID string
}
EventCodenameAccusal struct {
Type EventType
Timestamp time.Time
Prosecutor string
Killer string
Word string
}
EventCodenameTrial struct {
Type EventType
Timestamp time.Time
}
AllKillWords map[string]KillWords
)
@@ -243,6 +259,8 @@ const (
GameComplete
AssignmentRotation
GameReset
CodenameAccusal
CodenameTrial
)
type Event interface{ event() }
@@ -252,6 +270,8 @@ func (EventPlayerLeave) event() {}
func (EventGameComplete) event() {}
func (EventAssignmentRotation) event() {}
func (EventGameReset) event() {}
func (EventCodenameAccusal) event() {}
func (EventCodenameTrial) event() {}
func EventWithTime(event Event, t time.Time) Event {
switch e := event.(type) {
@@ -270,6 +290,12 @@ func EventWithTime(event Event, t time.Time) Event {
case EventGameReset:
e.Timestamp = t
event = e
case EventCodenameAccusal:
e.Timestamp = t
event = e
case EventCodenameTrial:
e.Timestamp = t
event = e
}
return event
}
@@ -324,6 +350,14 @@ func parseEvent(b []byte, timestamp time.Time) (Event, error) {
var v EventGameReset
err := json.Unmarshal(b, &v)
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
}
return nil, fmt.Errorf("unknown event type %d: %s", peek.Type, b)
}
@@ -336,6 +370,8 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
return result, err
}
var stateBeforeAccusal *GameState
for _, event := range events {
switch e := event.(type) {
case EventPlayerJoin:
@@ -369,6 +405,20 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
player.KillWords = v
result.Players[k] = player
}
case EventCodenameAccusal:
if stateBeforeAccusal == nil {
stateBeforeAccusal = &result
result = *stateBeforeAccusal
return GameState{}, fmt.Errorf("not impl: accusal: %+v", e)
}
case EventCodenameTrial:
if stateBeforeAccusal != nil {
result = *stateBeforeAccusal
stateBeforeAccusal = nil
return GameState{}, fmt.Errorf("not impl: trial: %+v", e)
}
case EventGameReset:
return games.GameState(ctx, e.ID)
default:
@@ -478,7 +528,7 @@ func (games Games) CreateEventGameReset(ctx context.Context, gid string) error {
}
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 {
@@ -549,7 +599,7 @@ func (m AllKillWords) withoutAssignees() AllKillWords {
result := make(AllKillWords)
for k := range m {
result[k] = KillWords{
Global: m[k].Global,
Codename: m[k].Codename,
Assigned: now,
Assignee: "",
Assignment: m[k].Assignment,
@@ -598,7 +648,7 @@ func (m AllKillWords) FillKillWords() AllKillWords {
}
func (m AllKillWords) fillKillWords(
poolGlobal []string,
poolCodename []string,
nPublic int,
poolPublic []string,
nPrivate int,
@@ -607,8 +657,8 @@ func (m AllKillWords) fillKillWords(
result := maps.Clone(m)
m = result
for k, v := range m {
if v.Global.Word == "" {
v.Global = KillWord{Word: m.unusedGlobal(poolGlobal), Points: -1}
if v.Codename.KillWord.Word == "" {
v.Codename = Codename{KillWord: KillWord{Word: m.unusedCodename(poolCodename), Points: 200}}
m[k] = v
}
if len(v.Assignment.Public) == 0 {
@@ -629,11 +679,11 @@ func (m AllKillWords) fillKillWords(
return m
}
func (m AllKillWords) unusedGlobal(pool []string) string {
func (m AllKillWords) unusedCodename(pool []string) string {
inUse := func() []string {
result := []string{}
for _, killWords := range m {
result = append(result, killWords.Global.Word)
result = append(result, killWords.Codename.KillWord.Word)
}
return result
}
@@ -706,7 +756,3 @@ func (games Games) createEvent(ctx context.Context, id string, v any) error {
) VALUES (?, ?, ?)
`, id, time.Now(), payload)
}
func (games *Games) Reset(ctx context.Context, gid string) error {
return games.CreateEventGameReset(ctx, gid)
}

View File

@@ -128,8 +128,8 @@ func TestGames(t *testing.T) {
if v.Players[p].Points() != 0 {
t.Error("nonzero points after zero kills:", v.Players[p].Points())
}
if v.Players[p].KillWords.Global.Word == "" {
t.Error(p, "no killwords.global")
if v.Players[p].KillWords.Codename.KillWord.Word == "" {
t.Error(p, "no killwords.Codename")
} else if v.Players[p].KillWords.Assigned.IsZero() {
t.Error(p, "no killwords.assigned")
} else if v.Players[p].KillWords.Assignee == "" {
@@ -150,7 +150,7 @@ func TestGames(t *testing.T) {
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)
} else if state, err := games.GameState(ctx, id); err != nil {
t.Fatal(err)
@@ -199,10 +199,10 @@ func TestParseEvent(t *testing.T) {
},
AllKillWords: map[string]KillWords{
"x": KillWords{
Global: KillWord{
Codename: Codename{KillWord: KillWord{
Word: "a",
Points: -1,
},
Points: 200,
}},
Assignee: "z",
Assigned: now,
Assignment: Assignment{
@@ -260,48 +260,48 @@ func TestAllKillWordsFill(t *testing.T) {
}{
"full": {
given: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("pub", "pri"),
},
expect: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("pub", "pri"),
},
},
"no ass": {
given: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: Assignment{},
},
expect: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("filled-public", "filled-private"),
},
},
"no pub": {
given: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("", "pri"),
},
expect: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("filled-public", "pri"),
},
},
"no pri": {
given: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("pub", ""),
},
expect: KillWords{
Global: kw(-1, "global"),
Codename: Codename{KillWord: kw(200, "global")},
Assignment: ass("pub", "filled-private"),
},
},
"empty": {
given: KillWords{},
expect: KillWords{
Global: kw(-1, "filled-global"),
Codename: Codename{KillWord: kw(200, "filled-global")},
Assignment: ass("filled-public", "filled-private"),
},
},
@@ -310,7 +310,7 @@ func TestAllKillWordsFill(t *testing.T) {
Assignment: ass("pub", "pri"),
},
expect: KillWords{
Global: kw(-1, "filled-global"),
Codename: Codename{KillWord: kw(200, "filled-global")},
Assignment: ass("pub", "pri"),
},
},
@@ -354,7 +354,7 @@ func TestAllKillWordsUnused(t *testing.T) {
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")
}
})
@@ -363,7 +363,7 @@ func TestAllKillWordsUnused(t *testing.T) {
t.Run("private", func(t *testing.T) {
akw := make(AllKillWords)
akw["k"] = KillWords{
Global: KillWord{Word: "x"},
Codename: Codename{KillWord: KillWord{Word: "x"}},
Assignment: Assignment{
Private: []KillWord{{}, {Word: "y"}},
Public: []KillWord{{}, {Word: "x"}},
@@ -377,13 +377,13 @@ func TestAllKillWordsUnused(t *testing.T) {
t.Run("global", func(t *testing.T) {
akw := make(AllKillWords)
akw["k"] = KillWords{
Global: KillWord{Word: "y"},
Codename: Codename{KillWord: KillWord{Word: "y"}},
Assignment: Assignment{
Private: []KillWord{{}, {Word: "x"}},
Public: []KillWord{{}, {Word: "x"}},
},
}
got := akw.unusedGlobal([]string{"x", "y"})
got := akw.unusedCodename([]string{"x", "y"})
if got != "x" {
t.Error("didnt return only unused option")
}
@@ -391,7 +391,7 @@ func TestAllKillWordsUnused(t *testing.T) {
t.Run("public", func(t *testing.T) {
akw := make(AllKillWords)
akw["k"] = KillWords{
Global: KillWord{Word: "x"},
Codename: Codename{KillWord: KillWord{Word: "x"}},
Assignment: Assignment{
Private: []KillWord{{}, {Word: "x"}},
Public: []KillWord{{}, {Word: "y"}},

View File

@@ -95,8 +95,8 @@ func (ugs *UserGameServer) listen(ctx context.Context, reader func(context.Conte
var points int
if gameState, err := ugs.games.GameState(ctx, ugs.ID); err != nil {
return err
} else if global := gameState.Players[killer].KillWords.Global; global.Word == word {
points = global.Points
} else if codename := gameState.Players[killer].KillWords.Codename.KillWord; codename.Word == word {
points = codename.Points
} 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
} 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 {
return err
} 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
}
} else {
@@ -151,7 +151,7 @@ func (ugs *UserGameServer) State(ctx context.Context) (UserGameState, error) {
if isSelf := k == ugs.Session.ID; isSelf {
v.KillWords.Assignment = Assignment{}
} else {
v.KillWords.Global = KillWord{}
v.KillWords.Codename = Codename{}
v.KillWords.Assignee = ""
for i := range v.Kills {
v.Kills[i].Victim = ""

View File

@@ -83,8 +83,8 @@ func TestUserGameServer(t *testing.T) {
}
if isSelf := pid == ugs.Session.ID; isSelf {
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
t.Error("self codename missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
@@ -96,8 +96,8 @@ func TestUserGameServer(t *testing.T) {
t.Error("self knows its own private")
}
} else {
if !p.KillWords.Global.Empty() {
t.Error("can see not self global")
if !p.KillWords.Codename.KillWord.Empty() {
t.Error("can see not self Codename")
}
if p.KillWords.Assignee != "" {
t.Error("can see other player's assignee")
@@ -167,8 +167,8 @@ func TestUserGameServer(t *testing.T) {
} else if kill.KillWord.Word == "" {
t.Errorf("dont know own kill word")
}
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
t.Error("self Codename missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
@@ -190,8 +190,8 @@ func TestUserGameServer(t *testing.T) {
} else if kill.KillWord.Word != "" {
t.Errorf("know other's kill word")
}
if !p.KillWords.Global.Empty() {
t.Error("can see not self global")
if !p.KillWords.Codename.KillWord.Empty() {
t.Error("can see not self Codename")
}
if p.KillWords.Assignee != "" {
t.Error("can see other player's assignee")
@@ -248,8 +248,8 @@ func TestUserGameServer(t *testing.T) {
} else if kill.KillWord.Word == "" {
t.Errorf("dont know own kill word")
}
if p.KillWords.Global.Word == "" || p.KillWords.Global.Points == 0 {
t.Error("self global missing field")
if p.KillWords.Codename.KillWord.Word == "" || p.KillWords.Codename.KillWord.Points == 0 {
t.Error("self Codename missing field")
}
if p.KillWords.Assignee == "" {
t.Error("assignee is empty")
@@ -271,8 +271,8 @@ func TestUserGameServer(t *testing.T) {
} else if kill.KillWord.Word == "" {
t.Errorf("dont know other's kill word")
}
if p.KillWords.Global.Empty() {
t.Error("cannot see not self global")
if p.KillWords.Codename.KillWord.Empty() {
t.Error("cannot see not self Codename")
}
if p.KillWords.Assignee == "" {
t.Error("cannot see other player's assignee")

View File

@@ -191,10 +191,8 @@ func (ws WS) inProgressMsgItem(ctx context.Context, ugs *UserGameServer, gameSta
tags := []inProgressMsgItemTag{}
if hasBeenKilledWithGlobal := slices.ContainsFunc(self.Kills, func(a Kill) bool {
return a.Victim == uid && a.KillWord.Word == self.KillWords.Global.Word
}); !hasBeenKilledWithGlobal {
tags = append(tags, newInProgressMsgItemTag(self.KillWords.Global))
if canKillWithCodename := !self.KillWords.Codename.Consumed; canKillWithCodename {
tags = append(tags, newInProgressMsgItemTag(self.KillWords.Codename.KillWord))
}
for _, killWord := range append(