transmit game actions and winner in dto

This commit is contained in:
Branden J Brown 2024-02-03 11:44:11 -06:00
parent 805fdb13c1
commit 1068060e94
6 changed files with 124 additions and 6 deletions

35
game/action.go Normal file
View File

@ -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
}
}

35
game/action_string.go Normal file
View File

@ -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]]
}

View File

@ -39,6 +39,8 @@ type Match struct {
// prev is a pointer to the element of shellArray which was fired last this // prev is a pointer to the element of shellArray which was fired last this
// game, or nil if none has been. // game, or nil if none has been.
prev *bool prev *bool
// action is a description of the previous action.
action action
// shellArray is the backing storage for shells and prev. // shellArray is the backing storage for shells and prev.
shellArray [8]bool shellArray [8]bool
@ -188,18 +190,22 @@ func (g *Match) Shoot(id player.ID, self bool) error {
target = g.CurrentPlayer() target = g.CurrentPlayer()
} }
live := g.popShell() live := g.popShell()
g.action = actShoot
if live { if live {
target.hp -= g.damage target.hp -= g.damage
if target.hp <= 0 { if target.hp <= 0 {
target.hp = 0 // in case it goes negative target.hp = 0 // in case it goes negative
g.action = actChallengerWins
// If the target is the challenger, the match is over as well. // If the target is the challenger, the match is over as well.
if target == &g.players[1] { if target == &g.players[1] {
g.action = actDealerWins
g.round = 3 g.round = 3
} }
return ErrRoundEnded return ErrRoundEnded
} }
} }
if g.Empty() { if g.Empty() {
g.action = actGameEnd
return ErrGameEnded return ErrGameEnded
} }
if !self || live { 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 { func (g *Match) Concede(id player.ID) error {
switch id { switch id {
case g.players[0].id: case g.players[0].id:
g.action = actDealerConcedes
g.players[0].hp = 0 g.players[0].hp = 0
case g.players[1].id: case g.players[1].id:
g.action = actChallengerConcedes
g.players[1].hp = 0 g.players[1].hp = 0
default: default:
return ErrWrongTurn 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. // DTO returns the current match state as viewed by the given player.
func (g *Match) DTO(id player.ID) serve.Game { func (g *Match) DTO(id player.ID) serve.Game {
var live, blank int var live, blank int
if g.turn == 1 { if g.action.gameStart() {
live = len(g.shells) / 2 live = len(g.shells) / 2
blank = len(g.shells) - live 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{ return serve.Game{
Players: [2]serve.Player{ Players: [2]serve.Player{
g.players[0].DTO(), g.players[0].DTO(),
g.players[1].DTO(), g.players[1].DTO(),
}, },
Action: g.action.String(),
Winner: w,
Round: g.round, Round: g.round,
Dealer: g.CurrentPlayer() == &g.players[0], Dealer: g.CurrentPlayer() == &g.players[0],
Damage: g.damage, Damage: g.damage,

View File

@ -446,6 +446,7 @@ func TestGameDTO(t *testing.T) {
g.players[0].DTO(), g.players[0].DTO(),
g.players[1].DTO(), g.players[1].DTO(),
}, },
Action: "Start",
Round: g.round, Round: g.round,
Dealer: false, Dealer: false,
Damage: g.damage, Damage: g.damage,
@ -471,6 +472,7 @@ func TestGameDTO(t *testing.T) {
g.players[0].DTO(), g.players[0].DTO(),
g.players[1].DTO(), g.players[1].DTO(),
}, },
Action: "Shoot",
Round: g.round, Round: g.round,
Dealer: true, Dealer: true,
Damage: g.damage, 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) 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) { func deref[T any, P ~*T](p P) (x T) {

View File

@ -20,9 +20,8 @@ func (i Item) Apply(g *Match) bool {
case ItemNone: case ItemNone:
return false return false
case ItemLens: case ItemLens:
if g.reveal { // Lenses are always used, even if they have no effect.
return false g.action = actLens
}
g.reveal = true g.reveal = true
return true return true
case ItemCig: case ItemCig:
@ -31,10 +30,13 @@ func (i Item) Apply(g *Match) bool {
cur.hp++ cur.hp++
// Cigs are always used, even if they have no effect. // Cigs are always used, even if they have no effect.
} }
g.action = actCig
return true return true
case ItemBeer: case ItemBeer:
g.popShell() g.popShell()
g.action = actBeer
if g.Empty() { if g.Empty() {
g.action = actBeerGameEnd
g.NextGame() g.NextGame()
} }
return true return true
@ -43,12 +45,14 @@ func (i Item) Apply(g *Match) bool {
if opp.cuffs != Uncuffed { if opp.cuffs != Uncuffed {
return false return false
} }
g.action = actCuff
opp.cuffs = Cuffed opp.cuffs = Cuffed
return true return true
case ItemKnife: case ItemKnife:
if g.damage != 1 { if g.damage != 1 {
return false return false
} }
g.action = actKnife
g.damage = 2 g.damage = 2
return true return true
default: default:
@ -56,7 +60,8 @@ func (i Item) Apply(g *Match) bool {
} }
} }
var itemNames = [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"}
func (i Item) String() string { func (i Item) String() string {
s := [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"} return itemNames[i]
return s[i]
} }

View File

@ -7,6 +7,10 @@ type Game struct {
// Players is the players in the game. // Players is the players in the game.
// The dealer is always the first player. // The dealer is always the first player.
Players [2]Player `json:"players"` 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 is the current round.
Round int8 `json:"round"` Round int8 `json:"round"`
// Dealer indicates whether the current player is the dealer. // Dealer indicates whether the current player is the dealer.
@ -45,3 +49,22 @@ type GameStart struct {
ID GameID `json:"id"` ID GameID `json:"id"`
Dealer bool `json:"dealer"` 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
}