Skip to content

Commit

Permalink
Fix issues with flor implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
marianogappa committed Aug 1, 2024
1 parent 5e925f6 commit a35e324
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 82 deletions.
44 changes: 31 additions & 13 deletions examplebot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,20 +233,38 @@ func shouldAnyEnvido(gs truco.ClientGameState, aggresiveness string, log func(st
}

func shouldAnyFlor(gs truco.ClientGameState, aggresiveness string, log func(string, ...any)) bool {
// If bot doesn't have flor, bot shouldn't say flor
if calculateFlorScore(gs) == 0 {
log("I don't have flor, so I'm not going to say flor.")
return false
}

possible := possibleActionsMap(gs)

// If human doesn't necessarily have flor, and bot has flor then bot should say flor
if quieroActions := filter(possible, truco.NewActionSayConFlorQuiero(gs.YouPlayerID)); len(quieroActions) == 0 {
log("Human doesn't necessarily have flor, so I'm going to say flor.")
return true
}

// if "no quiero" is possible and saying no quiero means losing, return true
// possible := possibleActionsMap(gs)
// noQuieroActions := filter(possible, truco.NewActionSayConFlorMeAchico(gs.YouPlayerID))
// if len(noQuieroActions) > 0 {
// cost := noQuieroActions[0].(*truco.ActionSayConFlorMeAchico).Cost
// if gs.TheirScore+cost >= gs.RuleMaxPoints {
// return true
// }
// }
// //TODO
// return true
noQuieroActions := filter(possible, truco.NewActionSayConFlorMeAchico(gs.YouPlayerID))
if len(noQuieroActions) > 0 {
log("Bot can say no quiero to flor, and saying no quiero might mean losing.")
cost := noQuieroActions[0].(*truco.ActionSayConFlorMeAchico).Cost
if gs.TheirScore+cost >= gs.RuleMaxPoints {
log("Bot should say quiero to flor, because bot loses otherwise.")
return true
}
}

// In principle let's always choose an action, since flor is unlikely to be matched once one has it
return true
// Both have flor and saying no quiero doesn't lose the game. At this point it depends on the score and the aggresiveness
log("Bot has flor, and human has flor. Bot's flor score is %v, and aggresiveness is: %v", calculateFlorScore(gs), aggresiveness)
return calculateFlorScore(gs) >= map[string]int{
"low": 31,
"normal": 29,
"high": 26,
}[aggresiveness]
}

func chooseFlorAction(gs truco.ClientGameState, aggresiveness string) truco.Action {
Expand Down Expand Up @@ -758,7 +776,7 @@ func (m Bot) ChooseAction(gs truco.ClientGameState) truco.Action {
return truco.NewActionSayConFlorMeAchico(gs.YouPlayerID)
}
if shouldFlor {
// This is the case where the bot initiates the envido
// This is the case where the bot initiates the flor
// Sometimes (<50%), a human player would hide their envido by not initiating, and hoping the other says it first
// TODO: should this chance based on aggresiveness?
if rand.Float64() < 0.67 {
Expand Down
30 changes: 21 additions & 9 deletions truco/action_any_quiero.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (a ActionSayEnvidoNoQuiero) Run(g *GameState) error {
}
g.EnvidoSequence.AddStep(a.GetName())
g.IsEnvidoFinished = true
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score)
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -170,11 +170,17 @@ func (a *ActionSayEnvidoQuiero) Enrich(g GameState) {
return
}
var (
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
quieroSeq, _ = g.EnvidoSequence.WithStep(SAY_ENVIDO_QUIERO)
quieroCost, _ = quieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore)
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
quieroSeq, err = g.EnvidoSequence.WithStep(SAY_ENVIDO_QUIERO)
quieroCost, err2 = quieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore, true)
)
if err != nil {
panic(err)
}
if err2 != nil {
panic(err2)
}
a.Cost = quieroCost
}

Expand All @@ -183,11 +189,17 @@ func (a *ActionSayEnvidoNoQuiero) Enrich(g GameState) {
return
}
var (
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
noQuieroSeq, _ = g.EnvidoSequence.WithStep(SAY_ENVIDO_NO_QUIERO)
noQuieroCost, _ = noQuieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore)
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
noQuieroSeq, err = g.EnvidoSequence.WithStep(SAY_ENVIDO_NO_QUIERO)
noQuieroCost, err2 = noQuieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore, true)
)
if err != nil {
panic(err)
}
if err2 != nil {
panic(err2)
}
a.Cost = noQuieroCost
}

Expand Down
3 changes: 3 additions & 0 deletions truco/action_me_voy_al_mazo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func (a ActionSayMeVoyAlMazo) IsPossible(g GameState) bool {
if g.IsEnvidoFinished && !g.TrucoSequence.IsEmpty() && !g.TrucoSequence.IsFinished() {
return false
}
if NewActionRevealFlorScore(a.PlayerID).IsPossible(g) || NewActionRevealEnvidoScore(a.PlayerID).IsPossible(g) {
return false
}
return true
}

Expand Down
2 changes: 1 addition & 1 deletion truco/action_reveal_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (a *ActionRevealCard) Run(g *GameState) error {
a.Score = g.Players[a.PlayerID].Hand.EnvidoScore() // it must be the action's player
}
// Revealing a card may cause the flor score to be revealed
if g.tryAwardFlorPoints() {
if ok, _ := g.tryAwardFlorPoints(); ok {
a.EnMesa = true
a.Score = g.Players[a.PlayerID].Hand.FlorScore() // it must be the action's player
}
Expand Down
2 changes: 1 addition & 1 deletion truco/action_son_buenas.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (a ActionSaySonBuenas) Run(g *GameState) error {
return errActionNotPossible
}
g.EnvidoSequence.AddStep(a.GetName())
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score)
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score, false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion truco/action_son_mejores.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (a ActionSaySonMejores) Run(g *GameState) error {
return errActionNotPossible
}
g.EnvidoSequence.AddStep(a.GetName())
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score)
cost, err := g.EnvidoSequence.Cost(g.RuleMaxPoints, g.Players[g.TurnPlayerID].Score, g.Players[g.TurnOpponentPlayerID].Score, false)
if err != nil {
return err
}
Expand Down
25 changes: 19 additions & 6 deletions truco/actions_any_envido.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,26 @@ func (g GameState) AnyEnvidoActionTypeEnrich(a Action) {
return
}
var (
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
quieroSeq, _ = g.EnvidoSequence.WithStep(SAY_ENVIDO_QUIERO)
quieroCost, _ = quieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore)
noQuieroSeq, _ = g.EnvidoSequence.WithStep(SAY_ENVIDO_NO_QUIERO)
noQuieroCost, _ = noQuieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore)
seq, _ = g.EnvidoSequence.WithStep(a.GetName())
youScore = g.Players[a.GetPlayerID()].Score
theirScore = g.Players[g.OpponentOf(a.GetPlayerID())].Score
quieroSeq, err = seq.WithStep(SAY_ENVIDO_QUIERO)
quieroCost, err2 = quieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore, true)
noQuieroSeq, err3 = seq.WithStep(SAY_ENVIDO_NO_QUIERO)
noQuieroCost, err4 = noQuieroSeq.Cost(g.RuleMaxPoints, youScore, theirScore, true)
)
if err != nil {
panic(err)
}
if err2 != nil {
panic(err2)
}
if err3 != nil {
panic(err3)
}
if err4 != nil {
panic(err4)
}

switch a.GetName() {
case SAY_ENVIDO:
Expand Down
100 changes: 100 additions & 0 deletions truco/actions_any_envido_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package truco

import (
"testing"

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

func TestEnvido(t *testing.T) {
type testStep struct {
action Action
expectedPlayerTurnAfterRunning *int
expectedIsFinishedAfterRunning *bool
expectedPossibleActionNamesBefore []string
expectedPossibleActionNamesAfter []string
expectedCustomValidationBeforeAction func(*GameState)
}

tests := []struct {
name string
hands []Hand
steps []testStep
}{
{
name: "it is still possible to say envido after opponent's first card is revealed",
steps: []testStep{
{
action: NewActionRevealCard(Card{Number: 1, Suit: COPA}, 0),
},
{
expectedPossibleActionNamesBefore: []string{
REVEAL_CARD,
REVEAL_CARD,
REVEAL_CARD,
SAY_ENVIDO,
SAY_REAL_ENVIDO,
SAY_FALTA_ENVIDO,
SAY_TRUCO,
SAY_ME_VOY_AL_MAZO,
},
},
},
},
}

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

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

for i, step := range tt.steps {

if step.expectedPossibleActionNamesBefore != nil {
actualAvailableActionNamesBefore := []string{}
for _, a := range gameState.CalculatePossibleActions() {
actualAvailableActionNamesBefore = append(actualAvailableActionNamesBefore, a.GetName())
}
assert.ElementsMatch(t, step.expectedPossibleActionNamesBefore, actualAvailableActionNamesBefore, "at step %v", i)
}

if step.expectedCustomValidationBeforeAction != nil {
step.expectedCustomValidationBeforeAction(gameState)
}

if step.action == nil {
continue
}

step.action.Enrich(*gameState)
err := gameState.RunAction(step.action)
require.NoError(t, err, "at step %v", i)

if step.expectedPossibleActionNamesAfter != nil {
actualAvailableActionNamesAfter := []string{}
for _, a := range gameState.CalculatePossibleActions() {
actualAvailableActionNamesAfter = append(actualAvailableActionNamesAfter, a.GetName())
}
assert.ElementsMatch(t, step.expectedPossibleActionNamesAfter, actualAvailableActionNamesAfter, "at step %v", i)
}

if step.expectedPlayerTurnAfterRunning != nil {
assert.Equal(t, *step.expectedPlayerTurnAfterRunning, gameState.TurnPlayerID, "at step %v expected player turn %v but got %v", i, step.expectedPlayerTurnAfterRunning, gameState.TurnPlayerID)
}

if step.expectedIsFinishedAfterRunning != nil {
assert.Equal(t, *step.expectedIsFinishedAfterRunning, gameState.EnvidoSequence.IsFinished(), "at step %v expected isFinished to be %v but wasn't", i, step.expectedIsFinishedAfterRunning)
}
}
})
}
}
Loading

0 comments on commit a35e324

Please sign in to comment.