From 1068060e94b463265bd8ecf67db36b6958c46437 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Sat, 3 Feb 2024 11:44:11 -0600 Subject: [PATCH] transmit game actions and winner in dto --- game/action.go | 35 +++++++++++++++++++++++++++++++++++ game/action_string.go | 35 +++++++++++++++++++++++++++++++++++ game/game.go | 19 ++++++++++++++++++- game/game_test.go | 3 +++ game/item.go | 15 ++++++++++----- serve/dto.go | 23 +++++++++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 game/action.go create mode 100644 game/action_string.go diff --git a/game/action.go b/game/action.go new file mode 100644 index 0000000..a5f199c --- /dev/null +++ b/game/action.go @@ -0,0 +1,35 @@ +package game + +type action uint8 + +const ( + actStart action = iota // start of match + + actShoot // fired, note prev indicates whether the shell was live + actGameEnd // end of game but not round + actBeerGameEnd // used a beer to end the game + actChallengerWins // challenger wins the round + actDealerWins // dealer wins the match + + // current player uses an item + actLens + actCig + actBeer // note prev indicates whether the shell was live + actCuff + actKnife + + actDealerConcedes // dealer concedes + actChallengerConcedes // challenger concedes +) + +//go:generate go run golang.org/x/tools/cmd/stringer@v0.17.0 -type=action -trimprefix=act + +// gameStart returns whether the action indicates the start of a game. +func (a action) gameStart() bool { + switch a { + case actStart, actGameEnd, actBeerGameEnd, actChallengerWins: + return true + default: + return false + } +} diff --git a/game/action_string.go b/game/action_string.go new file mode 100644 index 0000000..87b3f68 --- /dev/null +++ b/game/action_string.go @@ -0,0 +1,35 @@ +// Code generated by "stringer -type=action -trimprefix=act"; DO NOT EDIT. + +package game + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[actStart-0] + _ = x[actShoot-1] + _ = x[actGameEnd-2] + _ = x[actBeerGameEnd-3] + _ = x[actChallengerWins-4] + _ = x[actDealerWins-5] + _ = x[actLens-6] + _ = x[actCig-7] + _ = x[actBeer-8] + _ = x[actCuff-9] + _ = x[actKnife-10] + _ = x[actDealerConcedes-11] + _ = x[actChallengerConcedes-12] +} + +const _action_name = "StartShootGameEndBeerGameEndChallengerWinsDealerWinsLensCigBeerCuffKnifeDealerConcedesChallengerConcedes" + +var _action_index = [...]uint8{0, 5, 10, 17, 28, 42, 52, 56, 59, 63, 67, 72, 86, 104} + +func (i action) String() string { + if i >= action(len(_action_index)-1) { + return "action(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _action_name[_action_index[i]:_action_index[i+1]] +} diff --git a/game/game.go b/game/game.go index 0b958e2..4c9c2fe 100644 --- a/game/game.go +++ b/game/game.go @@ -39,6 +39,8 @@ type Match struct { // prev is a pointer to the element of shellArray which was fired last this // game, or nil if none has been. prev *bool + // action is a description of the previous action. + action action // shellArray is the backing storage for shells and prev. shellArray [8]bool @@ -188,18 +190,22 @@ func (g *Match) Shoot(id player.ID, self bool) error { target = g.CurrentPlayer() } live := g.popShell() + g.action = actShoot if live { target.hp -= g.damage if target.hp <= 0 { target.hp = 0 // in case it goes negative + g.action = actChallengerWins // If the target is the challenger, the match is over as well. if target == &g.players[1] { + g.action = actDealerWins g.round = 3 } return ErrRoundEnded } } if g.Empty() { + g.action = actGameEnd return ErrGameEnded } if !self || live { @@ -214,8 +220,10 @@ func (g *Match) Shoot(id player.ID, self bool) error { func (g *Match) Concede(id player.ID) error { switch id { case g.players[0].id: + g.action = actDealerConcedes g.players[0].hp = 0 case g.players[1].id: + g.action = actChallengerConcedes g.players[1].hp = 0 default: return ErrWrongTurn @@ -227,15 +235,24 @@ func (g *Match) Concede(id player.ID) error { // DTO returns the current match state as viewed by the given player. func (g *Match) DTO(id player.ID) serve.Game { var live, blank int - if g.turn == 1 { + if g.action.gameStart() { live = len(g.shells) / 2 blank = len(g.shells) - live } + w := serve.NoWinner + switch g.RoundWinner() { + case &g.players[0]: + w = serve.DealerWins + case &g.players[1]: + w = serve.ChallengerWins + } return serve.Game{ Players: [2]serve.Player{ g.players[0].DTO(), g.players[1].DTO(), }, + Action: g.action.String(), + Winner: w, Round: g.round, Dealer: g.CurrentPlayer() == &g.players[0], Damage: g.damage, diff --git a/game/game_test.go b/game/game_test.go index 73333c7..2b75dad 100644 --- a/game/game_test.go +++ b/game/game_test.go @@ -446,6 +446,7 @@ func TestGameDTO(t *testing.T) { g.players[0].DTO(), g.players[1].DTO(), }, + Action: "Start", Round: g.round, Dealer: false, Damage: g.damage, @@ -471,6 +472,7 @@ func TestGameDTO(t *testing.T) { g.players[0].DTO(), g.players[1].DTO(), }, + Action: "Shoot", Round: g.round, Dealer: true, Damage: g.damage, @@ -489,6 +491,7 @@ func TestGameDTO(t *testing.T) { t.Errorf("observer sees the wrong thing:\nwant %+v\ngot %+v", want, got) } } + // TODO(zeph): many more tests about actions } func deref[T any, P ~*T](p P) (x T) { diff --git a/game/item.go b/game/item.go index 77b65e5..becd103 100644 --- a/game/item.go +++ b/game/item.go @@ -20,9 +20,8 @@ func (i Item) Apply(g *Match) bool { case ItemNone: return false case ItemLens: - if g.reveal { - return false - } + // Lenses are always used, even if they have no effect. + g.action = actLens g.reveal = true return true case ItemCig: @@ -31,10 +30,13 @@ func (i Item) Apply(g *Match) bool { cur.hp++ // Cigs are always used, even if they have no effect. } + g.action = actCig return true case ItemBeer: g.popShell() + g.action = actBeer if g.Empty() { + g.action = actBeerGameEnd g.NextGame() } return true @@ -43,12 +45,14 @@ func (i Item) Apply(g *Match) bool { if opp.cuffs != Uncuffed { return false } + g.action = actCuff opp.cuffs = Cuffed return true case ItemKnife: if g.damage != 1 { return false } + g.action = actKnife g.damage = 2 return true default: @@ -56,7 +60,8 @@ func (i Item) Apply(g *Match) bool { } } +var itemNames = [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"} + func (i Item) String() string { - s := [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"} - return s[i] + return itemNames[i] } diff --git a/serve/dto.go b/serve/dto.go index 09e8c14..f544461 100644 --- a/serve/dto.go +++ b/serve/dto.go @@ -7,6 +7,10 @@ type Game struct { // Players is the players in the game. // The dealer is always the first player. Players [2]Player `json:"players"` + // Action indicates the previous action taken in the game. + Action string `json:"action"` + // Winner indicates the winner of the current round, if any. + Winner Winner `json:"winner,omitempty"` // Round is the current round. Round int8 `json:"round"` // Dealer indicates whether the current player is the dealer. @@ -45,3 +49,22 @@ type GameStart struct { ID GameID `json:"id"` Dealer bool `json:"dealer"` } + +// Winner is a round winner. +type Winner int8 + +const ( + NoWinner Winner = iota + DealerWins + ChallengerWins +) + +var winners = [...][]byte{ + NoWinner: []byte("null"), + DealerWins: []byte("0"), + ChallengerWins: []byte("1"), +} + +func (w *Winner) MarshalJSON() ([]byte, error) { + return winners[*w], nil +}