From fa7457ec6c66c2e2a9a74e322ca54bc722dc2168 Mon Sep 17 00:00:00 2001 From: Mariano Gappa Date: Wed, 26 Jun 2024 17:53:52 +0100 Subject: [PATCH 1/2] Fix envidowinnerplayerid bug. Streamline state. --- exampleclient/ui.go | 12 +-- exampleclient/websocket_client.go | 2 +- truco/action_any_quiero.go | 16 ++-- truco/action_me_voy_al_mazo.go | 16 ++-- truco/action_reveal_card.go | 16 ++-- truco/action_son_buenas.go | 17 ++-- truco/action_son_mejores.go | 17 ++-- truco/actions_any_envido.go | 4 +- truco/actions_any_truco.go | 2 +- truco/card_reveal_sequence.go | 8 +- truco/deck_test.go | 4 +- truco/envido_sequence_test.go | 2 +- truco/truco.go | 147 +++++++++++++----------------- truco/truco_test.go | 51 +++++------ 14 files changed, 141 insertions(+), 173 deletions(-) diff --git a/exampleclient/ui.go b/exampleclient/ui.go index b9d2ed9..a3797b6 100644 --- a/exampleclient/ui.go +++ b/exampleclient/ui.go @@ -39,7 +39,7 @@ func (u *ui) play(playerID int, gameState truco.GameState) (truco.Action, error) return nil, err } - if gameState.IsEnded { + if gameState.IsGameEnded { return nil, nil } @@ -76,7 +76,7 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { mx, my = termbox.Size() you = playerID them = state.OpponentOf(you) - hand = *state.Hands[them] + hand = *state.Players[them].Hand ) if mode == PRINT_MODE_SHOW_ROUND_RESULT { @@ -101,10 +101,10 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { themMano = " (mano)" } - printUpToAt(mx-1, 1, fmt.Sprintf("Vos%v %v", youMano, spanishScore(state.Scores[you]))) - printUpToAt(mx-1, 2, fmt.Sprintf("Elle%v %v", themMano, spanishScore(state.Scores[them]))) + printUpToAt(mx-1, 1, fmt.Sprintf("Vos%v %v", youMano, spanishScore(state.Players[you].Score))) + printUpToAt(mx-1, 2, fmt.Sprintf("Elle%v %v", themMano, spanishScore(state.Players[them].Score))) - hand = *state.Hands[you] + hand = *state.Players[you].Hand if mode == PRINT_MODE_SHOW_ROUND_RESULT { hand = *state.HandsDealt[len(state.HandsDealt)-2][you] @@ -249,7 +249,7 @@ func getLastActionString(playerID int, state truco.GameState) (string, error) { if len(state.Actions) == 0 { return "¡Empezó el juego!", nil } - if state.RoundJustStarted { + if state.IsRoundJustStarted { return "¡Empezó la mano!", nil } diff --git a/exampleclient/websocket_client.go b/exampleclient/websocket_client.go index 91f714a..98c53c0 100644 --- a/exampleclient/websocket_client.go +++ b/exampleclient/websocket_client.go @@ -30,7 +30,7 @@ func Player(playerID int, address string) { log.Fatal(err) } - if gameState.IsEnded { + if gameState.IsGameEnded { _ = ui.render(playerID, *gameState, PRINT_MODE_END) return } diff --git a/truco/action_any_quiero.go b/truco/action_any_quiero.go index f0ca87a..021440d 100644 --- a/truco/action_any_quiero.go +++ b/truco/action_any_quiero.go @@ -9,14 +9,14 @@ type ActionSayTrucoQuiero struct{ act } type ActionSayTrucoNoQuiero struct{ act } func (a ActionSayEnvidoNoQuiero) IsPossible(g GameState) bool { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return false } return g.EnvidoSequence.CanAddStep(a.GetName()) } func (a ActionSayEnvidoQuiero) IsPossible(g GameState) bool { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return false } return g.EnvidoSequence.CanAddStep(a.GetName()) @@ -53,12 +53,12 @@ func (a ActionSayEnvidoNoQuiero) Run(g *GameState) error { return errActionNotPossible } g.EnvidoSequence.AddStep(a.GetName()) - g.EnvidoFinished = true - cost, err := g.EnvidoSequence.Cost(g.CurrentPlayerScore(), g.OpponentPlayerScore()) + g.IsEnvidoFinished = true + cost, err := g.EnvidoSequence.Cost(g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score) if err != nil { return err } - g.Scores[g.OpponentPlayerID()] += cost + g.Players[g.TurnOpponentPlayerID].Score += cost return nil } @@ -84,14 +84,14 @@ func (a ActionSayTrucoNoQuiero) Run(g *GameState) error { return errActionNotPossible } g.TrucoSequence.AddStep(a.GetName()) - g.RoundFinished = true + g.IsRoundFinished = true cost, err := g.TrucoSequence.Cost() if err != nil { return err } g.CurrentRoundResult.TrucoPoints = cost - g.CurrentRoundResult.TrucoWinnerPlayerID = g.OpponentPlayerID() - g.Scores[g.OpponentPlayerID()] += cost + g.CurrentRoundResult.TrucoWinnerPlayerID = g.TurnOpponentPlayerID + g.Players[g.TurnOpponentPlayerID].Score += cost return nil } diff --git a/truco/action_me_voy_al_mazo.go b/truco/action_me_voy_al_mazo.go index 4d830af..08a1ea6 100644 --- a/truco/action_me_voy_al_mazo.go +++ b/truco/action_me_voy_al_mazo.go @@ -5,10 +5,10 @@ type ActionSayMeVoyAlMazo struct { } func (a ActionSayMeVoyAlMazo) IsPossible(g GameState) bool { - if !g.EnvidoSequence.IsEmpty() && !g.EnvidoFinished && !g.EnvidoSequence.IsFinished() { + if !g.EnvidoSequence.IsEmpty() && !g.IsEnvidoFinished && !g.EnvidoSequence.IsFinished() { return false } - if g.EnvidoFinished && !g.TrucoSequence.IsEmpty() && !g.TrucoSequence.IsFinished() { + if g.IsEnvidoFinished && !g.TrucoSequence.IsEmpty() && !g.TrucoSequence.IsFinished() { return false } return true @@ -16,14 +16,14 @@ func (a ActionSayMeVoyAlMazo) IsPossible(g GameState) bool { func (a ActionSayMeVoyAlMazo) Run(g *GameState) error { var cost int - if g.TrucoSequence.IsEmpty() && g.EnvidoFinished { + if g.TrucoSequence.IsEmpty() && g.IsEnvidoFinished { // Envido is finished, so either the envido cost was updated already, or it's zero cost = 1 } - if g.EnvidoSequence.IsEmpty() && g.TrucoSequence.IsEmpty() && !g.EnvidoFinished { + if g.EnvidoSequence.IsEmpty() && g.TrucoSequence.IsEmpty() && !g.IsEnvidoFinished { cost = 2 } - if g.EnvidoFinished && !g.TrucoSequence.IsEmpty() { + if g.IsEnvidoFinished && !g.TrucoSequence.IsEmpty() { var err error cost, err = g.TrucoSequence.Cost() if err != nil { @@ -31,8 +31,8 @@ func (a ActionSayMeVoyAlMazo) Run(g *GameState) error { } } g.CurrentRoundResult.TrucoPoints = cost - g.CurrentRoundResult.TrucoWinnerPlayerID = g.OpponentPlayerID() - g.Scores[g.OpponentPlayerID()] += cost - g.RoundFinished = true + g.CurrentRoundResult.TrucoWinnerPlayerID = g.TurnOpponentPlayerID + g.Players[g.TurnOpponentPlayerID].Score += cost + g.IsRoundFinished = true return nil } diff --git a/truco/action_reveal_card.go b/truco/action_reveal_card.go index d16cd41..f520f7b 100644 --- a/truco/action_reveal_card.go +++ b/truco/action_reveal_card.go @@ -11,7 +11,7 @@ func NewActionRevealCard(card Card) ActionRevealCard { func (a ActionRevealCard) IsPossible(g GameState) bool { // If envido was said and it hasn't finished, then the card can't be revealed - if !g.EnvidoFinished && !g.EnvidoSequence.IsEmpty() && !g.EnvidoSequence.IsFinished() { + if !g.IsEnvidoFinished && !g.EnvidoSequence.IsEmpty() && !g.EnvidoSequence.IsFinished() { return false } @@ -22,7 +22,7 @@ func (a ActionRevealCard) IsPossible(g GameState) bool { step := CardRevealSequenceStep{ card: a.Card, - playerID: g.CurrentPlayerID(), + playerID: g.TurnPlayerID, } return g.CardRevealSequence.CanAddStep(step, g) @@ -34,15 +34,15 @@ func (a ActionRevealCard) Run(g *GameState) error { } step := CardRevealSequenceStep{ card: a.Card, - playerID: g.CurrentPlayerID(), + playerID: g.TurnPlayerID, } g.CardRevealSequence.AddStep(step, *g) - err := g.Hands[g.CurrentPlayerID()].RevealCard(a.Card) + err := g.Players[g.TurnPlayerID].Hand.RevealCard(a.Card) if err != nil { return err } if g.CardRevealSequence.IsFinished() { - g.RoundFinished = true + g.IsRoundFinished = true var score int @@ -57,13 +57,13 @@ func (a ActionRevealCard) Run(g *GameState) error { score = cost } - g.Scores[g.CardRevealSequence.WinnerPlayerID()] += score + g.Players[g.CardRevealSequence.WinnerPlayerID()].Score += score g.CurrentRoundResult.TrucoPoints = score g.CurrentRoundResult.TrucoWinnerPlayerID = g.CardRevealSequence.WinnerPlayerID() } // If both players have revealed a card, then envido cannot be played anymore - if !g.EnvidoFinished && len(g.Hands[g.TurnPlayerID].Revealed) >= 1 && len(g.Hands[g.OpponentOf(g.TurnPlayerID)].Revealed) >= 1 { - g.EnvidoFinished = true + if !g.IsEnvidoFinished && len(g.Players[g.TurnPlayerID].Hand.Revealed) >= 1 && len(g.Players[g.TurnOpponentPlayerID].Hand.Revealed) >= 1 { + g.IsEnvidoFinished = true } return nil } diff --git a/truco/action_son_buenas.go b/truco/action_son_buenas.go index 74cfa88..e290ed4 100644 --- a/truco/action_son_buenas.go +++ b/truco/action_son_buenas.go @@ -6,16 +6,16 @@ type ActionSaySonBuenas struct { } func (a ActionSaySonBuenas) IsPossible(g GameState) bool { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return false } var ( mano = g.RoundTurnPlayerID me = g.TurnPlayerID - other = g.OpponentOf(g.TurnPlayerID) - meScore = g.Hands[me].EnvidoScore() - otherScore = g.Hands[other].EnvidoScore() + other = g.TurnOpponentPlayerID + meScore = g.Players[me].Hand.EnvidoScore() + otherScore = g.Players[other].Hand.EnvidoScore() ) // TODO: should I allow people to lose voluntarily? @@ -35,15 +35,14 @@ func (a ActionSaySonBuenas) Run(g *GameState) error { return errActionNotPossible } g.EnvidoSequence.AddStep(a.GetName()) - cost, err := g.EnvidoSequence.Cost(g.CurrentPlayerScore(), g.OpponentPlayerScore()) + cost, err := g.EnvidoSequence.Cost(g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score) if err != nil { return err } g.CurrentRoundResult.EnvidoPoints = cost - g.CurrentRoundResult.EnvidoWinnerPlayerID = g.OpponentOf(g.CurrentPlayerID()) - g.EnvidoWinnerPlayerID = g.OpponentOf(g.CurrentPlayerID()) - g.EnvidoFinished = true - g.Scores[g.OpponentPlayerID()] += cost + g.CurrentRoundResult.EnvidoWinnerPlayerID = g.TurnOpponentPlayerID + g.IsEnvidoFinished = true + g.Players[g.TurnOpponentPlayerID].Score += cost return nil } diff --git a/truco/action_son_mejores.go b/truco/action_son_mejores.go index d55f6a6..f542b31 100644 --- a/truco/action_son_mejores.go +++ b/truco/action_son_mejores.go @@ -6,15 +6,15 @@ type ActionSaySonMejores struct { } func (a ActionSaySonMejores) IsPossible(g GameState) bool { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return false } var ( mano = g.RoundTurnPlayerID me = g.TurnPlayerID - other = g.OpponentOf(g.TurnPlayerID) - meScore = g.Hands[me].EnvidoScore() - otherScore = g.Hands[other].EnvidoScore() + other = g.TurnOpponentPlayerID + meScore = g.Players[me].Hand.EnvidoScore() + otherScore = g.Players[other].Hand.EnvidoScore() ) // TODO: should I allow people to lose voluntarily? @@ -34,15 +34,14 @@ func (a ActionSaySonMejores) Run(g *GameState) error { return errActionNotPossible } g.EnvidoSequence.AddStep(a.GetName()) - cost, err := g.EnvidoSequence.Cost(g.CurrentPlayerScore(), g.OpponentPlayerScore()) + cost, err := g.EnvidoSequence.Cost(g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score) if err != nil { return err } g.CurrentRoundResult.EnvidoPoints = cost - g.CurrentRoundResult.EnvidoWinnerPlayerID = g.OpponentOf(g.CurrentPlayerID()) - g.EnvidoWinnerPlayerID = g.CurrentPlayerID() - g.EnvidoFinished = true - g.Scores[g.CurrentPlayerID()] += cost + g.CurrentRoundResult.EnvidoWinnerPlayerID = g.TurnPlayerID + g.IsEnvidoFinished = true + g.Players[g.TurnPlayerID].Score += cost return nil } diff --git a/truco/actions_any_envido.go b/truco/actions_any_envido.go index 1a985c0..00eadbc 100644 --- a/truco/actions_any_envido.go +++ b/truco/actions_any_envido.go @@ -13,7 +13,7 @@ func (a ActionSayFaltaEnvido) Run(g *GameState) error { return g.AnyEnvidoAction func (a ActionSayRealEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(a) } func (g GameState) AnyEnvidoActionTypeIsPossible(a Action) bool { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return false } // If there was a "truco" and an answer to it, regardless when, envido is not possible anymore. @@ -28,7 +28,7 @@ func (g GameState) AnyEnvidoActionTypeIsPossible(a Action) bool { } func (g *GameState) AnyEnvidoActionTypeRunAction(a Action) error { - if g.EnvidoFinished { + if g.IsEnvidoFinished { return errEnvidoFinished } if !g.AnyEnvidoActionTypeIsPossible(a) { diff --git a/truco/actions_any_truco.go b/truco/actions_any_truco.go index bbf6b07..48dd712 100644 --- a/truco/actions_any_truco.go +++ b/truco/actions_any_truco.go @@ -13,7 +13,7 @@ func (a ActionSayQuieroRetruco) Run(g *GameState) error { return g.AnyTrucoAc func (a ActionSayQuieroValeCuatro) Run(g *GameState) error { return g.AnyTrucoActionRunAction(a) } func (g GameState) AnyTrucoActionIsPossible(a Action) bool { - if !g.EnvidoSequence.IsEmpty() && !g.EnvidoFinished { + if !g.EnvidoSequence.IsEmpty() && !g.IsEnvidoFinished { return false } // Only the player who said "quiero" last can raise the stakes, unless quiero hasn't been said yet, diff --git a/truco/card_reveal_sequence.go b/truco/card_reveal_sequence.go index e383550..d657c33 100644 --- a/truco/card_reveal_sequence.go +++ b/truco/card_reveal_sequence.go @@ -12,11 +12,11 @@ type CardRevealSequence struct { func (crs CardRevealSequence) CanAddStep(step CardRevealSequenceStep, g GameState) bool { // Sanity check: the action's player must be the current player - if g.CurrentPlayerID() != step.playerID { + if g.TurnPlayerID != step.playerID { return false } // Sanity check: the card must be in the player's hand, and it must be unrevealed - if !g.Hands[step.playerID].HasUnrevealedCard(step.card) { + if !g.Players[step.playerID].Hand.HasUnrevealedCard(step.card) { return false } // Sanity check: the sequence must not be finished (i.e. neither player must have won) @@ -27,7 +27,7 @@ func (crs CardRevealSequence) CanAddStep(step CardRevealSequenceStep, g GameStat case 0: // Sanity check: if there are no steps, the first step must be from the rounds's first player return step.playerID == g.RoundTurnPlayerID case 1: // If there is one step, the second step must be from the round's second player - return step.playerID == g.RoundTurnOpponentPlayerID() + return step.playerID == g.OpponentOf((g.RoundTurnPlayerID)) case 2: // If there are two steps, the third step must be from the first faceoff winner, or round's first player if tied if crs.BistepWinners[0] == -1 { return step.playerID == g.RoundTurnPlayerID @@ -35,7 +35,7 @@ func (crs CardRevealSequence) CanAddStep(step CardRevealSequenceStep, g GameStat return step.playerID == crs.BistepWinners[0] case 3: // If there are 3 steps, the 4th step must be from the first faceoff winner's opponent, or round's second player if tied if crs.BistepWinners[0] == -1 { - return step.playerID == g.RoundTurnOpponentPlayerID() + return step.playerID == g.OpponentOf((g.RoundTurnPlayerID)) } return step.playerID == g.OpponentOf(crs.BistepWinners[0]) case 4: diff --git a/truco/deck_test.go b/truco/deck_test.go index 4ec36ae..be5913a 100644 --- a/truco/deck_test.go +++ b/truco/deck_test.go @@ -109,8 +109,8 @@ func TestEnvidoScore(t *testing.T) { t.Run(fmt.Sprintf("%v vs %v", tt.hands[0], tt.hands[1]), func(t *testing.T) { gameState := New(withDeck(newTestDeck(tt.hands))) - assert.Equal(t, tt.expected1, gameState.Hands[0].EnvidoScore()) - assert.Equal(t, tt.expected2, gameState.Hands[1].EnvidoScore()) + assert.Equal(t, tt.expected1, gameState.Players[0].Hand.EnvidoScore()) + assert.Equal(t, tt.expected2, gameState.Players[1].Hand.EnvidoScore()) }) } } diff --git a/truco/envido_sequence_test.go b/truco/envido_sequence_test.go index b1d8115..d1b09c7 100644 --- a/truco/envido_sequence_test.go +++ b/truco/envido_sequence_test.go @@ -160,7 +160,7 @@ func TestEnvidoSequence(t *testing.T) { continue } - cost, err := gameState.EnvidoSequence.Cost(gameState.Scores[gameState.TurnPlayerID], gameState.Scores[gameState.OpponentPlayerID()]) + cost, err := gameState.EnvidoSequence.Cost(gameState.Players[gameState.TurnPlayerID].Score, gameState.Players[gameState.TurnOpponentPlayerID].Score) require.NoError(t, err) assert.Equal(t, step.expectedCostAfterRunning, cost, "at step %v expected cost %v but got %v", i, step.expectedCostAfterRunning, cost) } diff --git a/truco/truco.go b/truco/truco.go index d4f04de..7919179 100644 --- a/truco/truco.go +++ b/truco/truco.go @@ -22,12 +22,17 @@ type GameState struct { // They are the same at the beginning of the round. TurnPlayerID int `json:"turnPlayerID"` - // Hands is a map of player IDs to their respective hands. - Hands map[int]*Hand `json:"hands"` + // TurnOpponentPlayerID is the player ID of the opponent of the player whose turn it is. + TurnOpponentPlayerID int `json:"turnOpponentPlayerID"` - // Scores is a map of player IDs to their respective scores. - // Scores go from 0 to 30. - Scores map[int]int `json:"scores"` + Players map[int]*Player `json:"players"` + + // // Hands is a map of player IDs to their respective hands. + // Hands map[int]*Hand `json:"hands"` + + // // Scores is a map of player IDs to their respective scores. + // // Scores go from 0 to 30. + // Scores map[int]int `json:"scores"` // PossibleActions is a list of possible actions that the current player can take. // Possible actions are calculated based on game state at the beginnin of the round and after @@ -51,25 +56,26 @@ type GameState struct { // A faceoff result will have the playerID of the winner, or -1 if it was a tie. CardRevealSequence *CardRevealSequence `json:"cardRevealSequence"` - // EnvidoFinished is true if the envido sequence is finished, or can no longer be continued. + // IsEnvidoFinished is true if the envido sequence is finished, or can no longer be continued. // TODO: can we remove this? Looks redundant to other state. But need tests first. - EnvidoFinished bool `json:"envidoFinished"` - - // EnvidoWinnerPlayerID is the player ID of the player who won the envido sequence. - // TODO: looks like this is assigned to but never used. Can we remove? - EnvidoWinnerPlayerID int `json:"envidoWinnerPlayerID"` + IsEnvidoFinished bool `json:"isEnvidoFinished"` - // RoundFinished is true if the current round is finished. Each action's `Run()` method is responsible + // IsRoundFinished is true if the current round is finished. Each action's `Run()` method is responsible // for setting this. During `GameState.RunAction()`, If the action's `Run()` method sets this to true, // then `GameState.startNewRound()` will be called. // // Clients are not really notified of a round change, so they should keep track of the "last round // number" to see if it changes. - RoundFinished bool `json:"roundFinished"` + IsRoundFinished bool `json:"isRoundFinished"` - // IsEnded is true if the whole game is ended, rather than an individual round. This happens when + // IsGameEnded is true if the whole game is ended, rather than an individual round. This happens when // a player reaches 30 points. - IsEnded bool `json:"isEnded"` + IsGameEnded bool `json:"isGameEnded"` + + // IsRoundJustStarted is true if a round has just started (i.e. no actions have been run on this round). + // This isn't used at all by the engine. It's strictly for clients to know, since there's no way to + // relate actions to rounds. TODO: that's probably a problem? + IsRoundJustStarted bool `json:"isRoundJustStarted"` // WinnerPlayerID is the player ID of the player who won the game. This is only set when `IsEnded` is // `true`. Otherwise, it's -1. @@ -81,11 +87,6 @@ type GameState struct { // you can use it to summarise what happened. You should use `RoundResults` in this case. CurrentRoundResult RoundResult `json:"currentRoundResult"` - // RoundJustStarted is true if a round has just started (i.e. no actions have been run on this round). - // This isn't used at all by the engine. It's strictly for clients to know, since there's no way to - // relate actions to rounds. TODO: that's probably a problem? - RoundJustStarted bool `json:"roundJustStarted"` - // TrucoQuieroOwnerPlayerId is the player ID of the player who said "quiero" last in the truco // sequence. This is used to determine who can raise the stakes in the truco sequence. // @@ -119,17 +120,27 @@ type GameState struct { deck *deck `json:"-"` } +type Player struct { + // Hands contains the revealed and unrevealed cards of the player. + Hand *Hand `json:"hand"` + + // Score is the player's scores (from 0 to 30). + Score int `json:"score"` +} + func New(opts ...func(*GameState)) *GameState { // TODO: support taking player ids, ser/de, ... gs := &GameState{ RoundTurnPlayerID: 1, RoundNumber: 0, - Scores: map[int]int{0: 0, 1: 0}, - Hands: map[int]*Hand{0: nil, 1: nil}, - IsEnded: false, - WinnerPlayerID: -1, - Actions: []json.RawMessage{}, - deck: newDeck(), + Players: map[int]*Player{ + 0: {Hand: nil, Score: 0}, + 1: {Hand: nil, Score: 0}, + }, + IsGameEnded: false, + WinnerPlayerID: -1, + Actions: []json.RawMessage{}, + deck: newDeck(), } for _, opt := range opts { @@ -149,33 +160,28 @@ func (g *GameState) startNewRound() { TrucoPoints: 0, } - g.RoundJustStarted = true + g.IsRoundJustStarted = true g.RoundTurnPlayerID = g.OpponentOf(g.RoundTurnPlayerID) g.RoundNumber++ g.TurnPlayerID = g.RoundTurnPlayerID - - handPlayer0 := g.deck.dealHand() - handPlayer1 := g.deck.dealHand() + g.TurnOpponentPlayerID = g.OpponentOf(g.TurnPlayerID) + g.Players[g.TurnPlayerID].Hand = g.deck.dealHand() + g.Players[g.TurnOpponentPlayerID].Hand = g.deck.dealHand() g.HandsDealt = append(g.HandsDealt, map[int]*Hand{ - g.RoundTurnPlayerID: handPlayer0, - g.OpponentOf(g.RoundTurnPlayerID): handPlayer1, + g.TurnPlayerID: g.Players[g.TurnPlayerID].Hand, + g.TurnOpponentPlayerID: g.Players[g.TurnOpponentPlayerID].Hand, }) - g.Hands = map[int]*Hand{ - g.RoundTurnPlayerID: handPlayer0, - g.OpponentOf(g.RoundTurnPlayerID): handPlayer1, - } - g.EnvidoWinnerPlayerID = -1 g.EnvidoSequence = &EnvidoSequence{StartingPlayerID: -1} - g.TrucoSequence = &TrucoSequence{} + g.TrucoSequence = &TrucoSequence{StartingPlayerID: -1} g.CardRevealSequence = &CardRevealSequence{} - g.EnvidoFinished = false - g.RoundFinished = false + g.IsEnvidoFinished = false + g.IsRoundFinished = false g.TrucoQuieroOwnerPlayerId = -1 g.PossibleActions = _serializeActions(g.CalculatePossibleActions()) } func (g *GameState) RunAction(action Action) error { - if g.IsEnded { + if g.IsGameEnded { return errGameIsEnded } @@ -186,32 +192,29 @@ func (g *GameState) RunAction(action Action) error { if err != nil { return err } - g.RoundJustStarted = false + g.IsRoundJustStarted = false bs := SerializeAction(action) g.Actions = append(g.Actions, bs) - g.ActionOwnerPlayerIDs = append(g.ActionOwnerPlayerIDs, g.CurrentPlayerID()) + g.ActionOwnerPlayerIDs = append(g.ActionOwnerPlayerIDs, g.TurnPlayerID) // Start new round if current round is finished - if !g.IsEnded && g.RoundFinished { + if !g.IsGameEnded && g.IsRoundFinished { g.RoundResults = append(g.RoundResults, g.CurrentRoundResult) g.startNewRound() return nil } // Switch player turn within current round (unless current action doesn't yield turn) - if !g.IsEnded && !g.RoundFinished && action.YieldsTurn(*g) { - g.TurnPlayerID = g.OpponentOf(g.TurnPlayerID) + if !g.IsGameEnded && !g.IsRoundFinished && action.YieldsTurn(*g) { + g.TurnPlayerID, g.TurnOpponentPlayerID = g.TurnOpponentPlayerID, g.TurnPlayerID } // Handle end of game due to score - // TODO: this changes if players are not 0 & 1 (or more than 2 players) - if g.Scores[0] >= 30 || g.Scores[1] >= 30 { - g.IsEnded = true - g.Scores[0] = min(30, g.Scores[0]) - g.Scores[1] = min(30, g.Scores[1]) - g.WinnerPlayerID = 0 - if g.Scores[1] > g.Scores[0] { - g.WinnerPlayerID = 1 + for playerID := range g.Players { + if g.Players[playerID].Score >= 30 { + g.Players[playerID].Score = 30 + g.IsGameEnded = true + g.WinnerPlayerID = playerID } } @@ -219,36 +222,10 @@ func (g *GameState) RunAction(action Action) error { return nil } -func (g GameState) CurrentPlayerID() int { - return g.TurnPlayerID -} - -func (g GameState) CurrentPlayerScore() int { - return g.Scores[g.TurnPlayerID] -} - -func (g GameState) OpponentPlayerID() int { - return g.OpponentOf(g.CurrentPlayerID()) -} - -func (g GameState) OpponentPlayerScore() int { - return g.Scores[g.OpponentPlayerID()] -} - -func (g GameState) RoundTurnOpponentPlayerID() int { - return g.OpponentOf(g.RoundTurnPlayerID) -} - func (g GameState) OpponentOf(playerID int) int { - // N.B. this function doesn't check if playerID is valid - players := []int{} - for playerID := range g.Hands { - players = append(players, playerID) - } - // TODO: This still assumes 2 players, but at least it doesn't hardcode the player IDs - for _, p := range players { - if p != playerID { - return p + for id := range g.Players { + if id != playerID { + return id } } return -1 // Unreachable @@ -281,11 +258,11 @@ var ( ) func (g GameState) CalculatePossibleActions() []Action { - envidoScore := g.Hands[g.TurnPlayerID].EnvidoScore() + envidoScore := g.Players[g.TurnPlayerID].Hand.EnvidoScore() allActions := []Action{} - for _, card := range g.Hands[g.TurnPlayerID].Unrevealed { + for _, card := range g.Players[g.TurnPlayerID].Hand.Unrevealed { allActions = append(allActions, newActionRevealCard(card)) } diff --git a/truco/truco_test.go b/truco/truco_test.go index cff4f65..352f753 100644 --- a/truco/truco_test.go +++ b/truco/truco_test.go @@ -6,50 +6,43 @@ import ( "github.com/stretchr/testify/require" ) -// func TestTruco(t *testing.T) { -// gameState := New() -// err := gameState.RunAction(ActionSayRealEnvido{}) -// if err != nil { -// t.Fatal(err) -// } -// pretty, err := gameState.PrettyPrint() -// if err != nil { -// t.Error(err) -// } - -// fmt.Printf("gameState: \n%v\n", pretty) -// t.Fail() -// } - func TestInitialOptions(t *testing.T) { gameState := New() + + expectedActions := []Action{ + newActionRevealCard(gameState.Players[gameState.TurnPlayerID].Hand.Unrevealed[0]), + newActionRevealCard(gameState.Players[gameState.TurnPlayerID].Hand.Unrevealed[1]), + newActionRevealCard(gameState.Players[gameState.TurnPlayerID].Hand.Unrevealed[2]), + newActionSayEnvido(), + newActionSayRealEnvido(), + newActionSayFaltaEnvido(), + newActionSayTruco(), + newActionSayMeVoyAlMazo(), + } + require.Equal( t, - []string{ - "reveal_card", - "say_envido", - "say_real_envido", - "say_falta_envido", - "say_truco", - "say_me_voy_al_mazo", - }, + _serializeActions(expectedActions), gameState.PossibleActions, ) } func TestAfterRealEnvidoOptions(t *testing.T) { gameState := New() - err := gameState.RunAction(ActionSayRealEnvido{act: act{Name: SAY_REAL_ENVIDO}}) + + expectedActions := []Action{ + newActionSayFaltaEnvido(), + newActionSayEnvidoQuiero(gameState.Players[gameState.TurnOpponentPlayerID].Hand.EnvidoScore()), + newActionSayEnvidoNoQuiero(), + } + + err := gameState.RunAction(newActionSayRealEnvido()) if err != nil { t.Fatal(err) } require.Equal( t, - []string{ - "say_falta_envido", - "say_envido_quiero", - "say_envido_no_quiero", - }, + _serializeActions(expectedActions), gameState.PossibleActions, ) } From 879f4a748ee9f07b71f619f5269e5091c3cd4a2b Mon Sep 17 00:00:00 2001 From: Mariano Gappa Date: Wed, 26 Jun 2024 19:36:04 +0100 Subject: [PATCH 2/2] Streamline GameState. --- exampleclient/ui.go | 50 ++++++++------ truco/action_any_quiero.go | 8 ++- truco/action_me_voy_al_mazo.go | 4 +- truco/action_reveal_card.go | 4 +- truco/action_son_buenas.go | 4 +- truco/action_son_mejores.go | 4 +- truco/actions_any_truco.go | 9 ++- truco/truco.go | 123 +++++++++++++++++---------------- truco/truco_sequence.go | 9 ++- 9 files changed, 117 insertions(+), 98 deletions(-) diff --git a/exampleclient/ui.go b/exampleclient/ui.go index a3797b6..7f68066 100644 --- a/exampleclient/ui.go +++ b/exampleclient/ui.go @@ -80,7 +80,9 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { ) if mode == PRINT_MODE_SHOW_ROUND_RESULT { - hand = *state.HandsDealt[len(state.HandsDealt)-2][them] + // Note that RoundNumber has already been incremented + // so we need to get the previous round's hands. + hand = *state.RoundsLog[state.RoundNumber-1].HandsDealt[them] } var ( @@ -107,7 +109,9 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { hand = *state.Players[you].Hand if mode == PRINT_MODE_SHOW_ROUND_RESULT { - hand = *state.HandsDealt[len(state.HandsDealt)-2][you] + // Note that RoundNumber has already been incremented + // so we need to get the previous round's hands. + hand = *state.RoundsLog[state.RoundNumber-1].HandsDealt[you] } unrevealed = getCardsString(hand.Unrevealed, false, false) @@ -125,27 +129,27 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { printAt(0, my/2, lastActionString) case PRINT_MODE_SHOW_ROUND_RESULT: - lastActionString, err := getActionString(state.Actions[len(state.Actions)-1], state.ActionOwnerPlayerIDs[len(state.ActionOwnerPlayerIDs)-1], you) + lastRoundLog := state.RoundsLog[state.RoundNumber-1] + lastActionString, err := getActionString(lastRoundLog.ActionsLog[len(lastRoundLog.ActionsLog)-1], you) if err != nil { return err } printAt(0, my/2, lastActionString) - lastRoundResult := state.RoundResults[len(state.RoundResults)-1] envidoPart := "el envido no se jugó" - if lastRoundResult.EnvidoWinnerPlayerID != -1 { + if lastRoundLog.EnvidoWinnerPlayerID != -1 { envidoWinner := "vos" won := "ganaste" - if lastRoundResult.EnvidoWinnerPlayerID == them { + if lastRoundLog.EnvidoWinnerPlayerID == them { envidoWinner = "elle" won = "ganó" } - envidoPart = fmt.Sprintf("%v %v %v puntos por el envido", envidoWinner, won, lastRoundResult.EnvidoPoints) + envidoPart = fmt.Sprintf("%v %v %v puntos por el envido", envidoWinner, won, lastRoundLog.EnvidoPoints) } trucoWinner := "vos" won := "ganaste" - if lastRoundResult.TrucoWinnerPlayerID == them { + if lastRoundLog.TrucoWinnerPlayerID == them { trucoWinner = "elle" won = "ganó" } @@ -155,11 +159,12 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { envidoPart, trucoWinner, won, - lastRoundResult.TrucoPoints, + lastRoundLog.TrucoPoints, ) printAt(0, my/2+1, result) case PRINT_MODE_END: - lastActionString, err := getActionString(state.Actions[len(state.Actions)-1], state.ActionOwnerPlayerIDs[len(state.ActionOwnerPlayerIDs)-1], you) + lastActionLog := state.RoundsLog[state.RoundNumber].ActionsLog[len(state.RoundsLog[state.RoundNumber].ActionsLog)-1] + lastActionString, err := getActionString(lastActionLog, you) if err != nil { return err } @@ -182,7 +187,7 @@ func (u *ui) render(playerID int, state truco.GameState, mode printMode) error { if mode == PRINT_MODE_NORMAL { actionsString := "" for i, action := range _deserializeActions(state.PossibleActions) { - action := spanishAction(action, state) + action := spanishAction(action) actionsString += fmt.Sprintf("%d. %s ", i+1, action) } printAt(0, my-2, actionsString) @@ -246,20 +251,21 @@ func suitEmoji(suit string) string { } func getLastActionString(playerID int, state truco.GameState) (string, error) { - if len(state.Actions) == 0 { - return "¡Empezó el juego!", nil - } - if state.IsRoundJustStarted { + actionsLog := state.RoundsLog[state.RoundNumber].ActionsLog + + if len(actionsLog) == 0 { + if state.RoundNumber == 1 { + return "¡Empezó el juego!", nil + } return "¡Empezó la mano!", nil } - lastActionBs := state.Actions[len(state.Actions)-1] - lastActionOwnerPlayerID := state.ActionOwnerPlayerIDs[len(state.ActionOwnerPlayerIDs)-1] - return getActionString(lastActionBs, lastActionOwnerPlayerID, playerID) + lastActionLog := actionsLog[len(actionsLog)-1] + return getActionString(lastActionLog, playerID) } -func getActionString(lastActionBs json.RawMessage, lastActionOwnerPlayerID int, playerID int) (string, error) { - lastAction, err := truco.DeserializeAction(lastActionBs) +func getActionString(log truco.ActionLog, playerID int) (string, error) { + lastAction, err := truco.DeserializeAction(log.Action) if err != nil { return "", err } @@ -267,7 +273,7 @@ func getActionString(lastActionBs json.RawMessage, lastActionOwnerPlayerID int, said := "dijiste" revealed := "tiraste" who := "Vos" - if playerID != lastActionOwnerPlayerID { + if playerID != log.PlayerID { who = "Elle" said = "dijo" revealed = "tiró" @@ -370,7 +376,7 @@ func spanishScore(score int) string { return fmt.Sprintf("%d buenas", score-14) } -func spanishAction(action truco.Action, state truco.GameState) string { +func spanishAction(action truco.Action) string { switch action.GetName() { case truco.REVEAL_CARD: _action := action.(*truco.ActionRevealCard) diff --git a/truco/action_any_quiero.go b/truco/action_any_quiero.go index 021440d..e1ca999 100644 --- a/truco/action_any_quiero.go +++ b/truco/action_any_quiero.go @@ -58,6 +58,8 @@ func (a ActionSayEnvidoNoQuiero) Run(g *GameState) error { if err != nil { return err } + g.RoundsLog[g.RoundNumber].EnvidoPoints = cost + g.RoundsLog[g.RoundNumber].EnvidoWinnerPlayerID = g.TurnOpponentPlayerID g.Players[g.TurnOpponentPlayerID].Score += cost return nil } @@ -75,7 +77,7 @@ func (a ActionSayTrucoQuiero) Run(g *GameState) error { return errActionNotPossible } g.TrucoSequence.AddStep(a.GetName()) - g.TrucoQuieroOwnerPlayerId = g.TurnPlayerID + g.TrucoSequence.QuieroOwnerPlayerID = g.TurnPlayerID return nil } @@ -89,8 +91,8 @@ func (a ActionSayTrucoNoQuiero) Run(g *GameState) error { if err != nil { return err } - g.CurrentRoundResult.TrucoPoints = cost - g.CurrentRoundResult.TrucoWinnerPlayerID = g.TurnOpponentPlayerID + g.RoundsLog[g.RoundNumber].TrucoPoints = cost + g.RoundsLog[g.RoundNumber].TrucoWinnerPlayerID = g.TurnOpponentPlayerID g.Players[g.TurnOpponentPlayerID].Score += cost return nil } diff --git a/truco/action_me_voy_al_mazo.go b/truco/action_me_voy_al_mazo.go index 08a1ea6..e89b657 100644 --- a/truco/action_me_voy_al_mazo.go +++ b/truco/action_me_voy_al_mazo.go @@ -30,8 +30,8 @@ func (a ActionSayMeVoyAlMazo) Run(g *GameState) error { return err } } - g.CurrentRoundResult.TrucoPoints = cost - g.CurrentRoundResult.TrucoWinnerPlayerID = g.TurnOpponentPlayerID + g.RoundsLog[g.RoundNumber].TrucoPoints = cost + g.RoundsLog[g.RoundNumber].TrucoWinnerPlayerID = g.TurnOpponentPlayerID g.Players[g.TurnOpponentPlayerID].Score += cost g.IsRoundFinished = true return nil diff --git a/truco/action_reveal_card.go b/truco/action_reveal_card.go index f520f7b..479354b 100644 --- a/truco/action_reveal_card.go +++ b/truco/action_reveal_card.go @@ -58,8 +58,8 @@ func (a ActionRevealCard) Run(g *GameState) error { } g.Players[g.CardRevealSequence.WinnerPlayerID()].Score += score - g.CurrentRoundResult.TrucoPoints = score - g.CurrentRoundResult.TrucoWinnerPlayerID = g.CardRevealSequence.WinnerPlayerID() + g.RoundsLog[g.RoundNumber].TrucoPoints = score + g.RoundsLog[g.RoundNumber].TrucoWinnerPlayerID = g.CardRevealSequence.WinnerPlayerID() } // If both players have revealed a card, then envido cannot be played anymore if !g.IsEnvidoFinished && len(g.Players[g.TurnPlayerID].Hand.Revealed) >= 1 && len(g.Players[g.TurnOpponentPlayerID].Hand.Revealed) >= 1 { diff --git a/truco/action_son_buenas.go b/truco/action_son_buenas.go index e290ed4..93b9447 100644 --- a/truco/action_son_buenas.go +++ b/truco/action_son_buenas.go @@ -39,8 +39,8 @@ func (a ActionSaySonBuenas) Run(g *GameState) error { if err != nil { return err } - g.CurrentRoundResult.EnvidoPoints = cost - g.CurrentRoundResult.EnvidoWinnerPlayerID = g.TurnOpponentPlayerID + g.RoundsLog[g.RoundNumber].EnvidoPoints = cost + g.RoundsLog[g.RoundNumber].EnvidoWinnerPlayerID = g.TurnOpponentPlayerID g.IsEnvidoFinished = true g.Players[g.TurnOpponentPlayerID].Score += cost return nil diff --git a/truco/action_son_mejores.go b/truco/action_son_mejores.go index f542b31..6ceb938 100644 --- a/truco/action_son_mejores.go +++ b/truco/action_son_mejores.go @@ -38,8 +38,8 @@ func (a ActionSaySonMejores) Run(g *GameState) error { if err != nil { return err } - g.CurrentRoundResult.EnvidoPoints = cost - g.CurrentRoundResult.EnvidoWinnerPlayerID = g.TurnPlayerID + g.RoundsLog[g.RoundNumber].EnvidoPoints = cost + g.RoundsLog[g.RoundNumber].EnvidoWinnerPlayerID = g.TurnPlayerID g.IsEnvidoFinished = true g.Players[g.TurnPlayerID].Score += cost return nil diff --git a/truco/actions_any_truco.go b/truco/actions_any_truco.go index 48dd712..8476168 100644 --- a/truco/actions_any_truco.go +++ b/truco/actions_any_truco.go @@ -18,17 +18,20 @@ func (g GameState) AnyTrucoActionIsPossible(a Action) bool { } // Only the player who said "quiero" last can raise the stakes, unless quiero hasn't been said yet, // which can only happen if the last action is "truco" - if !g.IsLastActionOfName(SAY_TRUCO) && (a.GetName() == SAY_QUIERO_RETRUCO || a.GetName() == SAY_QUIERO_VALE_CUATRO) && g.TrucoQuieroOwnerPlayerId != g.TurnPlayerID { + if !g.IsLastActionOfName(SAY_TRUCO) && + (a.GetName() == SAY_QUIERO_RETRUCO || a.GetName() == SAY_QUIERO_VALE_CUATRO) && + g.TrucoSequence.QuieroOwnerPlayerID != g.TurnPlayerID { return false } return g.TrucoSequence.CanAddStep(a.GetName()) } func (g GameState) IsLastActionOfName(name string) bool { - if len(g.Actions) == 0 { + actionsLog := g.RoundsLog[g.RoundNumber].ActionsLog + if len(actionsLog) == 0 { return false } - lastActionBs := g.Actions[len(g.Actions)-1] + lastActionBs := actionsLog[len(actionsLog)-1].Action lastAction, err := DeserializeAction(lastActionBs) if err != nil { return false diff --git a/truco/truco.go b/truco/truco.go index 7919179..195e0ad 100644 --- a/truco/truco.go +++ b/truco/truco.go @@ -25,15 +25,11 @@ type GameState struct { // TurnOpponentPlayerID is the player ID of the opponent of the player whose turn it is. TurnOpponentPlayerID int `json:"turnOpponentPlayerID"` + // Players is a map of player IDs to their respective hands and scores. + // There are 2 players in a game. Use TurnPlayerID and TurnOpponentPlayerID to index + // into this map, or iterate over it to discover player ids. Players map[int]*Player `json:"players"` - // // Hands is a map of player IDs to their respective hands. - // Hands map[int]*Hand `json:"hands"` - - // // Scores is a map of player IDs to their respective scores. - // // Scores go from 0 to 30. - // Scores map[int]int `json:"scores"` - // PossibleActions is a list of possible actions that the current player can take. // Possible actions are calculated based on game state at the beginnin of the round and after // each action is run (i.e. GameState.RunAction). @@ -72,50 +68,28 @@ type GameState struct { // a player reaches 30 points. IsGameEnded bool `json:"isGameEnded"` - // IsRoundJustStarted is true if a round has just started (i.e. no actions have been run on this round). - // This isn't used at all by the engine. It's strictly for clients to know, since there's no way to - // relate actions to rounds. TODO: that's probably a problem? - IsRoundJustStarted bool `json:"isRoundJustStarted"` - // WinnerPlayerID is the player ID of the player who won the game. This is only set when `IsEnded` is // `true`. Otherwise, it's -1. WinnerPlayerID int `json:"winnerPlayerID"` - // CurrentRoundResult contains the live results of the ongoing round for envido/truco winners & points. - // It is set when actions are run, and is reset at the beginning of each round. Be careful when using - // this in the client, because if the last action caused the round to finish, it will be reset before - // you can use it to summarise what happened. You should use `RoundResults` in this case. - CurrentRoundResult RoundResult `json:"currentRoundResult"` - // TrucoQuieroOwnerPlayerId is the player ID of the player who said "quiero" last in the truco // sequence. This is used to determine who can raise the stakes in the truco sequence. // // TODO: this should probably be inside TrucoSequence? - TrucoQuieroOwnerPlayerId int `json:"trucoQuieroOwnerPlayerId"` + // TrucoQuieroOwnerPlayerId int `json:"trucoQuieroOwnerPlayerId"` - // Actions is the list of actions that have been run in the game. - // Each element is a JSON-serialized action. This is because `Action` is an interface, and we can't - // serialize it directly otherwise. Clients should use `DeserializeAction` to get the actual action. + // RoundsLog is the ordered list of logs of each round that was played in the game. + // + // Use GameState.RoundNumber to index into this list (note thus that it's 1-indexed). + // This means that there is an empty round at the beginning of the list. // - // TODO: rather than having "Actions", "HandsDealt", "RoundResults", "ActionOwnerPlayerIDs" as - // separate fields, we should have a single "ActionLog" field that contains all of these. - Actions []json.RawMessage `json:"actions"` - - // HandsDealt is the list of hands that were dealt in the game. Each element is a map of player IDs to - // their respective hands. - HandsDealt []map[int]*Hand `json:"handsDealt"` - - // RoundResults is the list of results of each round. Each element is a `RoundResult` struct. - // This is useful for clients to see the results of each round, since `CurrentRoundResult` is reset - // at the beginning of each round. - RoundResults []RoundResult `json:"roundResults"` - - // ActionOwnerPlayerIDs is the list of player IDs who ran each action. There is no PlayerID field in - // the `Action` struct, mostly because it would be annoying to have to distrust the client who sends - // it. + // Note that there is a "live entry" for the current round. This entry will always have + // the HandsDealt, but the other fields depend on the stage the round is in. You can + // use the ActionsLog's length to determine if a round has just started. // - // Note: this definitely should be inside an "ActionLog" slice, instead of here. - ActionOwnerPlayerIDs []int `json:"actionOwnerPlayerIDs"` + // Note that, if the last action ran caused a round to finish, if you want to render + // the screen with the last action, you have to check the last action of the previous round instead! + RoundsLog []*RoundLog `json:"actionLog"` deck *deck `json:"-"` } @@ -128,8 +102,39 @@ type Player struct { Score int `json:"score"` } +// RoundLog is a log of a round that was played in the game +type RoundLog struct { + // HandsDealt is a map from PlayerID to its hand durinf this round. + HandsDealt map[int]*Hand `json:"handsDealt"` + + // For envido/truco winners and points, note that there is still a + // winner of 1 point if a player said "no quiero" to the envido/truco. + // + // If envido wasn't played at all, then EnvidoWinnerPlayerID is -1. + // + // At the end of a round, there will always be a TrucoWinnerPlayerID, + // even if truco wasn't played, implicitly by revealing the cards. + + EnvidoWinnerPlayerID int `json:"envidoWinnerPlayerID"` + EnvidoPoints int `json:"envidoPoints"` + TrucoWinnerPlayerID int `json:"trucoWinnerPlayerID"` + TrucoPoints int `json:"trucoPoints"` + + // ActionsLog is the ordered list of actions of this round. + ActionsLog []ActionLog `json:"actionsLog"` +} + +// ActionLog is a log of an action that was run in a round. +type ActionLog struct { + // PlayerID is the player ID of the player who ran the action. + PlayerID int `json:"playerID"` + + // Action is a JSON-serialized action. This is because `Action` is an interface, and we can't + // serialize it directly otherwise. Clients should use `truco.DeserializeAction`.` + Action json.RawMessage `json:"action"` +} + func New(opts ...func(*GameState)) *GameState { - // TODO: support taking player ids, ser/de, ... gs := &GameState{ RoundTurnPlayerID: 1, RoundNumber: 0, @@ -139,7 +144,7 @@ func New(opts ...func(*GameState)) *GameState { }, IsGameEnded: false, WinnerPlayerID: -1, - Actions: []json.RawMessage{}, + RoundsLog: []*RoundLog{{}}, // initialised with an empty round to be 1-indexed deck: newDeck(), } @@ -153,30 +158,28 @@ func New(opts ...func(*GameState)) *GameState { } func (g *GameState) startNewRound() { - g.CurrentRoundResult = RoundResult{ - EnvidoWinnerPlayerID: -1, - EnvidoPoints: 0, - TrucoWinnerPlayerID: -1, - TrucoPoints: 0, - } - - g.IsRoundJustStarted = true g.RoundTurnPlayerID = g.OpponentOf(g.RoundTurnPlayerID) g.RoundNumber++ g.TurnPlayerID = g.RoundTurnPlayerID g.TurnOpponentPlayerID = g.OpponentOf(g.TurnPlayerID) g.Players[g.TurnPlayerID].Hand = g.deck.dealHand() g.Players[g.TurnOpponentPlayerID].Hand = g.deck.dealHand() - g.HandsDealt = append(g.HandsDealt, map[int]*Hand{ - g.TurnPlayerID: g.Players[g.TurnPlayerID].Hand, - g.TurnOpponentPlayerID: g.Players[g.TurnOpponentPlayerID].Hand, - }) g.EnvidoSequence = &EnvidoSequence{StartingPlayerID: -1} - g.TrucoSequence = &TrucoSequence{StartingPlayerID: -1} + g.TrucoSequence = &TrucoSequence{StartingPlayerID: -1, QuieroOwnerPlayerID: -1} g.CardRevealSequence = &CardRevealSequence{} g.IsEnvidoFinished = false g.IsRoundFinished = false - g.TrucoQuieroOwnerPlayerId = -1 + g.RoundsLog = append(g.RoundsLog, &RoundLog{ + HandsDealt: map[int]*Hand{ + g.TurnPlayerID: g.Players[g.TurnPlayerID].Hand, + g.TurnOpponentPlayerID: g.Players[g.TurnOpponentPlayerID].Hand, + }, + EnvidoWinnerPlayerID: -1, + EnvidoPoints: 0, + TrucoWinnerPlayerID: -1, + TrucoPoints: 0, + ActionsLog: []ActionLog{}, + }) g.PossibleActions = _serializeActions(g.CalculatePossibleActions()) } @@ -192,14 +195,14 @@ func (g *GameState) RunAction(action Action) error { if err != nil { return err } - g.IsRoundJustStarted = false bs := SerializeAction(action) - g.Actions = append(g.Actions, bs) - g.ActionOwnerPlayerIDs = append(g.ActionOwnerPlayerIDs, g.TurnPlayerID) + g.RoundsLog[g.RoundNumber].ActionsLog = append(g.RoundsLog[g.RoundNumber].ActionsLog, ActionLog{ + PlayerID: g.TurnPlayerID, + Action: bs, + }) // Start new round if current round is finished if !g.IsGameEnded && g.IsRoundFinished { - g.RoundResults = append(g.RoundResults, g.CurrentRoundResult) g.startNewRound() return nil } diff --git a/truco/truco_sequence.go b/truco/truco_sequence.go index 76820bb..7959e19 100644 --- a/truco/truco_sequence.go +++ b/truco/truco_sequence.go @@ -48,8 +48,13 @@ type TrucoSequence struct { // Sub-sequences are separated by "quiero" actions. // // StartingPlayerID holds the player ID that started the _current_ sub-sequence. - StartingPlayerID int `json:"starting_player_id"` - Sequence []string `json:"sequence"` + StartingPlayerID int `json:"starting_player_id"` + + // QuieroOwnerPlayerID is the player ID of the player who said "quiero" last in the truco + // sequence. This is used to determine who can raise the stakes in the truco sequence. + QuieroOwnerPlayerID int `json:"quiero_owner_player_id"` + + Sequence []string `json:"sequence"` } func (ts TrucoSequence) String() string {