From faaa10a976f04ce69b5215478184ad6c52b55b88 Mon Sep 17 00:00:00 2001 From: Mariano Gappa Date: Sun, 21 Jul 2024 19:05:26 +0100 Subject: [PATCH] Add concept of DisplayCards to make UI simpler. --- exampleclient/ui.go | 26 ++++++++++++- truco/action_any_quiero.go | 6 +++ truco/deck.go | 71 +++++++++++++++++++++++++++++++++-- truco/truco.go | 76 +++++++++++++++++++++++--------------- 4 files changed, 145 insertions(+), 34 deletions(-) diff --git a/exampleclient/ui.go b/exampleclient/ui.go index 23efe90..b159f11 100644 --- a/exampleclient/ui.go +++ b/exampleclient/ui.go @@ -109,7 +109,17 @@ func renderScores(rs renderState) { } func renderTheirUnrevealedCards(rs renderState) { - renderAt(0, 0, strings.Repeat("[] ", rs.gs.TheirUnrevealedCardLength)) + displayText := "" + for _, card := range rs.gs.TheirDisplayUnrevealedCards { + if card.IsHole { + displayText += " " + } else { + displayText += "[]" + } + displayText += " " + } + + renderAt(0, 0, displayText) } func renderTheirRevealedCards(rs renderState) { @@ -167,7 +177,16 @@ func renderYourRevealedCards(rs renderState) { } func renderYourUnrevealedCards(rs renderState) { - renderAt(0, rs.viewportHeight-4, getCardsString(rs.gs.YourUnrevealedCards)) + displayText := "" + for _, card := range rs.gs.YourDisplayUnrevealedCards { + if !card.IsHole { + displayText += getDisplayCardString(card) + } else { + displayText += " " + } + displayText += " " + } + renderAt(0, rs.viewportHeight-4, displayText) } func renderActions(rs renderState) { @@ -217,6 +236,9 @@ func getCardsString(cards []truco.Card) string { func getCardString(card truco.Card) string { return fmt.Sprintf("[%v%v ]", card.Number, suitEmoji(card.Suit)) } +func getDisplayCardString(card truco.DisplayCard) string { + return fmt.Sprintf("[%v%v ]", card.Number, suitEmoji(card.Suit)) +} func suitEmoji(suit string) string { switch suit { diff --git a/truco/action_any_quiero.go b/truco/action_any_quiero.go index 18123b7..f69a1c8 100644 --- a/truco/action_any_quiero.go +++ b/truco/action_any_quiero.go @@ -158,9 +158,15 @@ func (a ActionRevealEnvidoScore) Run(g *GameState) error { for _, is := range allPossibleReveals[len(curPlayersHand.Unrevealed)] { // create a candidate hand but only with reveal cards candidateHand := Hand{Revealed: append([]Card{}, curPlayersHand.Revealed...)} + for i := range curPlayersHand.Unrevealed { + card := curPlayersHand.Unrevealed[i] + candidateHand.displayUnrevealedCards = append(candidateHand.displayUnrevealedCards, DisplayCard{Number: card.Number, Suit: card.Suit}) + } + // and reveal the additional cards of this combination for i := range is { candidateHand.Revealed = append(candidateHand.Revealed, curPlayersHand.Unrevealed[i]) + candidateHand.displayUnrevealedCards[i].IsHole = true } // if by revealing these cards we reach the expected envido score, this is the right reveal // Note: this is only true if the reveal combinations are sorted by reveal count ascending! diff --git a/truco/deck.go b/truco/deck.go index e8051a4..1a28ea8 100644 --- a/truco/deck.go +++ b/truco/deck.go @@ -22,6 +22,27 @@ type Card struct { Number int `json:"number"` } +func (c Card) ToDisplayCard() DisplayCard { + return DisplayCard{ + Suit: c.Suit, + Number: c.Number, + } +} + +type DisplayCard struct { + // Suit is the card's suit, which can be "oro", "copa", "espada" or "basto". + Suit string `json:"suit"` + + // Number is the card's number, from 1 to 12. + Number int `json:"number"` + + // This card is backwards (we don't know the suit & number) + IsBackwards bool `json:"is_backwards"` + + // This card is a hole (it used to be the card with this suit & number) + IsHole bool `json:"is_hole"` +} + func (c Card) String() string { return fmt.Sprintf("%d de %s", c.Number, c.Suit) } @@ -36,6 +57,8 @@ type deck struct { type Hand struct { Unrevealed []Card `json:"unrevealed"` Revealed []Card `json:"revealed"` + + displayUnrevealedCards []DisplayCard } func (h Hand) DeepCopy() Hand { @@ -70,16 +93,56 @@ func (h *Hand) RevealCard(card Card) error { return errCardAlreadyRevealed } } - for i, c := range h.Unrevealed { + for _, c := range h.Unrevealed { if c == card { h.Revealed = append(h.Revealed, c) - h.Unrevealed = append(h.Unrevealed[:i], h.Unrevealed[i+1:]...) + h.removeUnrevealedCard(c) return nil } } return errCardNotInHand } +func (h *Hand) removeUnrevealedCard(card Card) { + for i, c := range h.Unrevealed { + if c == card { + h.Unrevealed = append(h.Unrevealed[:i], h.Unrevealed[i+1:]...) + break + } + } + for i := range h.displayUnrevealedCards { + if h.displayUnrevealedCards[i].Suit == card.Suit && h.displayUnrevealedCards[i].Number == card.Number { + h.displayUnrevealedCards[i].IsHole = true + break + } + } +} + +func (h *Hand) initializeDisplayUnrevealedCards() { + h.displayUnrevealedCards = []DisplayCard{} + for _, c := range h.Unrevealed { + h.displayUnrevealedCards = append(h.displayUnrevealedCards, c.ToDisplayCard()) + } +} + +// prepareDisplayUnrevealedCards makes sure that display cards are elided when not +// revealed and for the opponent. +func (h *Hand) prepareDisplayUnrevealedCards(isYou bool) []DisplayCard { + result := []DisplayCard{} + result = append(result, h.displayUnrevealedCards...) + if isYou { + return result + } + for i := range result { + if !result[i].IsHole { + result[i].IsBackwards = true + result[i].Suit = "" + result[i].Number = 0 + } + } + return result +} + var ( errCardNotInHand = errors.New("card not in hand") errCardAlreadyRevealed = errors.New("card already revealed") @@ -115,7 +178,9 @@ func (d *deck) shuffle() { } func (d *deck) dealHand() *Hand { - return d.dealHandFunc() + hand := d.dealHandFunc() + hand.initializeDisplayUnrevealedCards() + return hand } func (d *deck) defaultDealHand() *Hand { diff --git a/truco/truco.go b/truco/truco.go index 9948157..74b1b6e 100644 --- a/truco/truco.go +++ b/truco/truco.go @@ -442,27 +442,28 @@ func (g *GameState) ToClientGameState(youPlayerID int) ClientGameState { } cgs := ClientGameState{ - RoundTurnPlayerID: g.RoundTurnPlayerID, - RoundNumber: g.RoundNumber, - TurnPlayerID: g.TurnPlayerID, - YouPlayerID: youPlayerID, - ThemPlayerID: themPlayerID, - YourScore: g.Players[youPlayerID].Score, - TheirScore: g.Players[themPlayerID].Score, - YourRevealedCards: g.Players[youPlayerID].Hand.Revealed, - TheirRevealedCards: g.Players[themPlayerID].Hand.Revealed, - YourUnrevealedCards: g.Players[youPlayerID].Hand.Unrevealed, - TheirUnrevealedCardLength: len(g.Players[themPlayerID].Hand.Unrevealed), - PossibleActions: _serializeActions(filteredPossibleActions), - IsGameEnded: g.IsGameEnded, - IsRoundFinished: g.IsRoundFinished, - WinnerPlayerID: g.WinnerPlayerID, - EnvidoWinnerPlayerID: g.RoundsLog[g.RoundNumber].EnvidoWinnerPlayerID, - WasEnvidoAccepted: g.EnvidoSequence.WasAccepted(), - EnvidoPoints: g.RoundsLog[g.RoundNumber].EnvidoPoints, - TrucoWinnerPlayerID: g.RoundsLog[g.RoundNumber].TrucoWinnerPlayerID, - TrucoPoints: g.RoundsLog[g.RoundNumber].TrucoPoints, - WasTrucoAccepted: g.TrucoSequence.WasAccepted(), + RoundTurnPlayerID: g.RoundTurnPlayerID, + RoundNumber: g.RoundNumber, + TurnPlayerID: g.TurnPlayerID, + YouPlayerID: youPlayerID, + ThemPlayerID: themPlayerID, + YourScore: g.Players[youPlayerID].Score, + TheirScore: g.Players[themPlayerID].Score, + YourRevealedCards: g.Players[youPlayerID].Hand.Revealed, + TheirRevealedCards: g.Players[themPlayerID].Hand.Revealed, + YourUnrevealedCards: g.Players[youPlayerID].Hand.Unrevealed, + PossibleActions: _serializeActions(filteredPossibleActions), + IsGameEnded: g.IsGameEnded, + IsRoundFinished: g.IsRoundFinished, + WinnerPlayerID: g.WinnerPlayerID, + EnvidoWinnerPlayerID: g.RoundsLog[g.RoundNumber].EnvidoWinnerPlayerID, + WasEnvidoAccepted: g.EnvidoSequence.WasAccepted(), + EnvidoPoints: g.RoundsLog[g.RoundNumber].EnvidoPoints, + TrucoWinnerPlayerID: g.RoundsLog[g.RoundNumber].TrucoWinnerPlayerID, + TrucoPoints: g.RoundsLog[g.RoundNumber].TrucoPoints, + WasTrucoAccepted: g.TrucoSequence.WasAccepted(), + YourDisplayUnrevealedCards: g.Players[youPlayerID].Hand.prepareDisplayUnrevealedCards(true), + TheirDisplayUnrevealedCards: g.Players[themPlayerID].Hand.prepareDisplayUnrevealedCards(false), } if len(g.RoundsLog[g.RoundNumber].ActionsLog) > 0 { @@ -489,14 +490,31 @@ type ClientGameState struct { // They are the same at the beginning of the round. TurnPlayerID int `json:"turnPlayerID"` - YouPlayerID int `json:"you"` - ThemPlayerID int `json:"them"` - YourScore int `json:"yourScore"` - TheirScore int `json:"theirScore"` - YourRevealedCards []Card `json:"yourRevealedCards"` - TheirRevealedCards []Card `json:"theirRevealedCards"` - YourUnrevealedCards []Card `json:"yourUnrevealedCards"` - TheirUnrevealedCardLength int `json:"theirUnrevealedCardLength"` + YouPlayerID int `json:"you"` + ThemPlayerID int `json:"them"` + YourScore int `json:"yourScore"` + TheirScore int `json:"theirScore"` + YourRevealedCards []Card `json:"yourRevealedCards"` + TheirRevealedCards []Card `json:"theirRevealedCards"` + YourUnrevealedCards []Card `json:"yourUnrevealedCards"` + + // YourDisplayUnrevealedCards is like YourUnrevealedCards, but it always has 3 cards + // and it adds two properties: `IsBackwards` and `IsHole`. + // + // `IsBackwards` is true if the card is facing backwards (i.e. the client doesn't know what it is). + // `IsHole` is true if the card was revealed by the opponent, and it used to be that card. + // + // Use this property to render the card + YourDisplayUnrevealedCards []DisplayCard `json:"yourDisplayUnrevealedCards"` + + // TheirDisplayUnrevealedCards is like TheirUnrevealedCards, but it always has 3 cards + // and it adds two properties: `IsBackwards` and `IsHole`. + // + // `IsBackwards` is true if the card is facing backwards (i.e. the client doesn't know what it is). + // `IsHole` is true if the card was revealed by the opponent, and it used to be that card. + // + // Use this property to render the card + TheirDisplayUnrevealedCards []DisplayCard `json:"theirDisplayUnrevealedCards"` // PossibleActions is a list of possible actions that the current player can take. // Possible actions are calculated based on game state at the beginning of the round and after