Skip to content

Commit

Permalink
Add REVEAL_ENVIDO_SCORE action & logic.
Browse files Browse the repository at this point in the history
Also clean up ClientGameState state.
  • Loading branch information
marianogappa committed Jul 18, 2024
1 parent fc0f7d0 commit 7fc169e
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 25 deletions.
10 changes: 5 additions & 5 deletions exampleclient/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,18 @@ func renderEndSummary(rs renderState) {
switch rs.mode {
case PRINT_MODE_SHOW_ROUND_RESULT:
envidoPart := "el envido no se jugó"
if rs.gs.LastRoundLog.EnvidoWinnerPlayerID != -1 {
if rs.gs.EnvidoWinnerPlayerID != -1 {
envidoWinner := "vos"
won := "ganaste"
if rs.gs.LastRoundLog.EnvidoWinnerPlayerID == rs.gs.ThemPlayerID {
if rs.gs.EnvidoWinnerPlayerID == rs.gs.ThemPlayerID {
envidoWinner = "elle"
won = "ganó"
}
envidoPart = fmt.Sprintf("%v %v %v puntos por el envido", envidoWinner, won, rs.gs.LastRoundLog.EnvidoPoints)
envidoPart = fmt.Sprintf("%v %v %v puntos por el envido", envidoWinner, won, rs.gs.EnvidoPoints)
}
trucoWinner := "vos"
won := "ganaste"
if rs.gs.LastRoundLog.TrucoWinnerPlayerID == rs.gs.ThemPlayerID {
if rs.gs.TrucoWinnerPlayerID == rs.gs.ThemPlayerID {
trucoWinner = "elle"
won = "ganó"
}
Expand All @@ -147,7 +147,7 @@ func renderEndSummary(rs renderState) {
envidoPart,
trucoWinner,
won,
rs.gs.LastRoundLog.TrucoPoints,
rs.gs.TrucoPoints,
)
case PRINT_MODE_END:
var resultText string
Expand Down
8 changes: 7 additions & 1 deletion exampleclient/ui_spanish.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func getActionString(log truco.ActionLog, playerID int) string {
what = fmt.Sprintf("%v %d son mejores", said, action.Score)
case truco.SAY_ME_VOY_AL_MAZO:
what = fmt.Sprintf("%v me voy al mazo", said)
case truco.REVEAL_ENVIDO_SCORE:
_action := lastAction.(*truco.ActionRevealEnvidoScore)
what = fmt.Sprintf("%v en mesa", _action.Score)
case truco.CONFIRM_ROUND_FINISHED:
what = ""
default:
Expand All @@ -86,7 +89,7 @@ func spanishScore(score int) string {
if score == 15 {
return "entraste"
}
return fmt.Sprintf("%d buenas", score-14)
return fmt.Sprintf("%d buenas", score-15)
}

func spanishAction(action truco.Action) string {
Expand Down Expand Up @@ -126,6 +129,9 @@ func spanishAction(action truco.Action) string {
return "me voy al mazo"
case truco.CONFIRM_ROUND_FINISHED:
return "seguir"
case truco.REVEAL_ENVIDO_SCORE:
_action := action.(*truco.ActionRevealEnvidoScore)
return fmt.Sprintf("mostrar las %v", _action.Score)
default:
return "???"
}
Expand Down
74 changes: 74 additions & 0 deletions truco/action_any_quiero.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package truco

import "fmt"

type ActionSayEnvidoNoQuiero struct{ act }
type ActionSayEnvidoQuiero struct{ act }
type ActionSayEnvidoScore struct {
act
Score int `json:"score"`
}
type ActionRevealEnvidoScore struct {
act
Score int `json:"score"`
}
type ActionSayTrucoQuiero struct{ act }
type ActionSayTrucoNoQuiero struct{ act }

Expand Down Expand Up @@ -40,6 +46,21 @@ func (a ActionSayEnvidoScore) IsPossible(g GameState) bool {
return g.EnvidoSequence.CanAddStep(a.GetName())
}

func (a ActionRevealEnvidoScore) IsPossible(g GameState) bool {
if !g.IsRoundFinished {
return false
}
if !g.EnvidoSequence.WasAccepted() {
return false
}
roundLog := g.RoundsLog[g.RoundNumber]
if roundLog.EnvidoWinnerPlayerID != a.PlayerID {
return false
}
revealedHand := Hand{Revealed: g.Players[a.PlayerID].Hand.Revealed}
return revealedHand.EnvidoScore() != g.Players[a.PlayerID].Hand.EnvidoScore()
}

func (a ActionSayTrucoQuiero) IsPossible(g GameState) bool {
if g.IsRoundFinished {
return false
Expand Down Expand Up @@ -104,6 +125,53 @@ func (a ActionSayEnvidoScore) Run(g *GameState) error {
return nil
}

func (a ActionRevealEnvidoScore) Run(g *GameState) error {
if !a.IsPossible(*g) {
return errActionNotPossible
}
// We need to reveal the least amount of cards such that the envido score is revealed.
// Since we don't know which cards to reveal, let's try all possible reveal combinations.
//
// allPossibleReveals is a `map[unrevealed_len][]map[card_index]struct{}{}`
//
// Note: len(unrevealed) == 0 must be impossible if this line is reached
_s := struct{}{}
allPossibleReveals := map[int][]map[int]struct{}{
1: {{0: _s}}, // i.e. if there's only one unrevealed card, only option is to reveal that card
2: {{0: _s}, {1: _s}, {0: _s, 1: _s}},
3: {{0: _s}, {1: _s}, {2: _s}, {0: _s, 1: _s}, {0: _s, 2: _s}, {1: _s, 2: _s}},
}
curPlayersHand := g.Players[a.PlayerID].Hand

// for each possible combination of card reveals
for _, is := range allPossibleReveals[len(curPlayersHand.Unrevealed)] {
// create a candidate hand but only with reveal cards
candidateHand := Hand{Revealed: append([]Card{}, curPlayersHand.Revealed...)}
// and reveal the additional cards of this combination
for i := range is {
candidateHand.Revealed = append(candidateHand.Revealed, curPlayersHand.Unrevealed[i])
}
// 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!
// Note: we didn't add the unrevealed cards to the candidate hand yet, because we need to
// reach the expected envido score only with revealed cards! That's the whole point!
if candidateHand.EnvidoScore() == curPlayersHand.EnvidoScore() {
// don't forget to add the unrevealed cards to the candidate hand
for i := range curPlayersHand.Unrevealed {
// add all unrevealed cards from the players hand, except if we revealed them
if _, ok := is[i]; !ok {
candidateHand.Unrevealed = append(candidateHand.Unrevealed, curPlayersHand.Unrevealed[i])
}
}
// replace hand with our satisfactory candidate hand
g.Players[a.PlayerID].Hand = &candidateHand
return nil
}
}
// we tried all possible reveal combinations, so it should be impossible that we didn't find the right combination!
return fmt.Errorf("couldn't reveal envido score due to a bug, this code should be unreachable")
}

func (a ActionSayTrucoQuiero) Run(g *GameState) error {
if !a.IsPossible(*g) {
return errActionNotPossible
Expand Down Expand Up @@ -146,3 +214,9 @@ func (a ActionSayEnvidoQuiero) YieldsTurn(g GameState) bool {
// This should always be the "mano" player.
return g.TurnPlayerID != g.RoundTurnPlayerID
}

func (a ActionRevealEnvidoScore) YieldsTurn(g GameState) bool {
// this action doesn't change turn because the round is finished at this point
// and the current player must confirm round finished right after this action
return false
}
4 changes: 3 additions & 1 deletion truco/action_confirm_round_ended.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ type ActionConfirmRoundFinished struct {
}

func (a ActionConfirmRoundFinished) IsPossible(g GameState) bool {
return g.IsRoundFinished && !g.RoundFinishedConfirmedPlayerIDs[a.PlayerID]
return g.IsRoundFinished &&
!NewActionRevealEnvidoScore(a.PlayerID, 0).IsPossible(g) &&
!g.RoundFinishedConfirmedPlayerIDs[a.PlayerID]
}

func (a ActionConfirmRoundFinished) Run(g *GameState) error {
Expand Down
4 changes: 4 additions & 0 deletions truco/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ func NewActionSayMeVoyAlMazo(playerID int) Action {
func NewActionConfirmRoundFinished(playerID int) Action {
return ActionConfirmRoundFinished{act: act{Name: CONFIRM_ROUND_FINISHED, PlayerID: playerID}}
}

func NewActionRevealEnvidoScore(playerID int, score int) Action {
return ActionRevealEnvidoScore{act: act{Name: REVEAL_ENVIDO_SCORE, PlayerID: playerID}, Score: score}
}
14 changes: 12 additions & 2 deletions truco/envido_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
REVEAL_CARD = "reveal_card"
CONFIRM_ROUND_FINISHED = "confirm_round_finished"
SAY_ENVIDO_SCORE = "say_envido_score"
REVEAL_ENVIDO_SCORE = "reveal_envido_score"

COST_NOT_READY = -1
COST_FALTA_ENVIDO = -2
Expand Down Expand Up @@ -141,12 +142,21 @@ func (es EnvidoSequence) Cost(currentPlayerScore int, otherPlayerScore int) (int
}
cost := validEnvidoSequenceCosts[es.String()]
if cost == COST_FALTA_ENVIDO {
return es.calculateFaltaEnvidoCost(currentPlayerScore, otherPlayerScore), nil
return calculateFaltaEnvidoCost(currentPlayerScore, otherPlayerScore), nil
}
return cost, nil
}

func (es EnvidoSequence) calculateFaltaEnvidoCost(meScore int, youScore int) int {
func (es EnvidoSequence) WasAccepted() bool {
for _, step := range es.Sequence {
if step == SAY_ENVIDO_QUIERO {
return true
}
}
return false
}

func calculateFaltaEnvidoCost(meScore int, youScore int) int {
if meScore < 15 && youScore < 15 {
return 15 - meScore
}
Expand Down
109 changes: 109 additions & 0 deletions truco/reveal_envido_score_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package truco

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRevealEnvidoScore(t *testing.T) {
type testStep struct {
action Action
expectedIsValid bool
expectedPlayerTurnAfterRunning int
ignoreAction bool
}

tests := []struct {
name string
hands []Hand
steps []testStep
}{
{
name: "test reveal envido score",
steps: []testStep{
{
action: NewActionSayEnvido(0),
ignoreAction: true,
},
{
action: NewActionSayEnvidoQuiero(1),
ignoreAction: true,
},
{
action: NewActionSayEnvidoScore(25, 0),
ignoreAction: true,
},
{
action: NewActionSaySonMejores(31, 1),
ignoreAction: true,
},
{
action: NewActionSayTruco(0),
ignoreAction: true,
},
{
action: NewActionSayTrucoNoQuiero(1),
ignoreAction: true,
},
// At this point the round is finished, so it's valid for player1 to reveal the envido score, but not to confirm round end
{
action: NewActionConfirmRoundFinished(1),
expectedIsValid: false,
},
// Revealing the envido score is valid
{
action: NewActionRevealEnvidoScore(1, 31),
expectedIsValid: true,
expectedPlayerTurnAfterRunning: 1, // doesn't yield turn
},
// Now that the envido score is revealed, it's valid to confirm the round end
{
action: NewActionConfirmRoundFinished(1),
expectedIsValid: true,
expectedPlayerTurnAfterRunning: 0, // yields turn
},
// Revealing the envido score again is invalid
{
action: NewActionRevealEnvidoScore(1, 31),
expectedIsValid: false,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defaultHands := []Hand{
{Unrevealed: []Card{{Number: 1, Suit: ORO}, {Number: 2, Suit: ORO}, {Number: 3, Suit: ORO}}}, // 25
{Unrevealed: []Card{{Number: 4, Suit: ORO}, {Number: 5, Suit: ORO}, {Number: 6, Suit: ORO}}}, // 31
}
if len(tt.hands) == 0 {
tt.hands = defaultHands
}
gameState := New(withDeck(newTestDeck(tt.hands)))

require.Equal(t, 0, gameState.TurnPlayerID)

for i, step := range tt.steps {
if !step.ignoreAction {
actualIsValid := step.action.IsPossible(*gameState)
require.Equal(t, step.expectedIsValid, actualIsValid, "at step %v expected isValid to be %v but wasn't", i, step.expectedIsValid)
if !step.expectedIsValid {
continue
}
}

err := gameState.RunAction(step.action)
require.NoError(t, err)

if step.ignoreAction {
continue
}

assert.Equal(t, step.expectedPlayerTurnAfterRunning, gameState.TurnPlayerID, "at step %v expected player turn %v but got %v", i, step.expectedPlayerTurnAfterRunning, gameState.TurnPlayerID)
}
})
}
}
Loading

0 comments on commit 7fc169e

Please sign in to comment.