Skip to content

Commit

Permalink
Leverage Enrich pattern for costs. Implement New() options.
Browse files Browse the repository at this point in the history
  • Loading branch information
marianogappa committed Jul 27, 2024
1 parent ff83ef1 commit bcfafea
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 50 deletions.
60 changes: 51 additions & 9 deletions examplebot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log"
"sort"

"math/rand"

Expand Down Expand Up @@ -149,10 +150,42 @@ func sortPossibleEnvidoActions(gs truco.ClientGameState) []truco.Action {
actions = append(actions, action)
}
}

// Sort actions based on their cost
// TODO: this is broken at the moment because the cost doesn't work well
sort.Slice(actions, func(i, j int) bool {
return _getEnvidoActionQuieroCost(actions[i]) < _getEnvidoActionQuieroCost(actions[j])
})

return actions
}

func _getEnvidoActionQuieroCost(action truco.Action) int {
switch a := action.(type) {
case *truco.ActionSayEnvidoQuiero:
return a.Cost
case *truco.ActionSayEnvido:
return a.QuieroCost
case *truco.ActionSayRealEnvido:
return a.QuieroCost
case *truco.ActionSayFaltaEnvido:
return a.QuieroCost
default:
panic("this code should be unreachable! bug in _getEnvidoActionCost! please report this bug.")
}
}

func shouldAnyEnvido(gs truco.ClientGameState, aggresiveness string, log func(string, ...any)) bool {
// if "no quiero" is possible and saying no quiero means losing, return true
possible := possibleActionsMap(gs)
noQuieroActions := filter(possible, truco.NewActionSayEnvidoNoQuiero(gs.YouPlayerID))
if len(noQuieroActions) > 0 {
cost := noQuieroActions[0].(*truco.ActionSayEnvidoNoQuiero).Cost
if gs.TheirScore+cost >= gs.RuleMaxPoints {
return true
}
}

shouldMap := map[string]int{
"low": 29,
"normal": 27,
Expand Down Expand Up @@ -451,6 +484,16 @@ func chooseTrucoAction(gs truco.ClientGameState, aggresiveness string) truco.Act
}

func shouldAcceptTruco(gs truco.ClientGameState, aggresiveness string, log func(string, ...any)) bool {
// if "no quiero" is possible and saying no quiero means losing, return true
possible := possibleActionsMap(gs)
noQuieroActions := filter(possible, truco.NewActionSayTrucoNoQuiero(gs.YouPlayerID))
if len(noQuieroActions) > 0 {
cost := noQuieroActions[0].(*truco.ActionSayTrucoNoQuiero).Cost
if gs.TheirScore+cost >= gs.RuleMaxPoints {
return true
}
}

shouldMap := map[string]float64{
"low": 0.55,
"normal": 0.5,
Expand Down Expand Up @@ -583,17 +626,20 @@ func meVoy(gs truco.ClientGameState) truco.Action {
}

func (m Bot) ChooseAction(gs truco.ClientGameState) truco.Action {
actions := possibleActionsMap(gs)
for _, action := range actions {
m.log("possible action: %v", action)
}

if len(gs.PossibleActions) == 0 {
m.log("there are no actions left.")
return nil
}
if len(gs.PossibleActions) == 1 {
m.log("there was only one action: %v", string(gs.PossibleActions[0]))
return _deserializeActions(gs.PossibleActions)[0]
}

// If there's only a say_son_buenas, say_son_mejores or a single action, choose it
actions := possibleActionsMap(gs)
for _, action := range actions {
m.log("possible action: %v", action)
}
sonBuenasActions := filter(actions, sonBuenas(gs))
if len(sonBuenasActions) > 0 {
m.log("I have to say son buenas.")
Expand All @@ -604,10 +650,6 @@ func (m Bot) ChooseAction(gs truco.ClientGameState) truco.Action {
m.log("I have to say son mejores.")
return sonMejoresActions[0]
}
if len(gs.PossibleActions) == 1 {
m.log("there was only one action: %v", string(gs.PossibleActions[0]))
return _deserializeActions(gs.PossibleActions)[0]
}

var (
aggresiveness = calculateAggresiveness(gs)
Expand Down
21 changes: 20 additions & 1 deletion main_wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,27 @@ var (
bot truco.Bot
)

type rules struct {
MaxPoints int `json:"maxPoints"`
IsFlorEnabled bool `json:"isFlorEnabled"`
}

func trucoNew(this js.Value, p []js.Value) interface{} {
state = truco.New()
jsonBytes := make([]byte, p[0].Length())
js.CopyBytesToGo(jsonBytes, p[0])
var r rules
// ignore rules if unmarshal fails
_ = json.Unmarshal(jsonBytes, &r)

opts := []func(*truco.GameState){}
if r.MaxPoints > 0 {
opts = append(opts, truco.WithMaxPoints(r.MaxPoints))
}
if r.IsFlorEnabled {
opts = append(opts, truco.WithFlorEnabled(r.IsFlorEnabled))
}
state = truco.New(opts...)

bot = examplebot.New()

nbs, err := json.Marshal(state.ToClientGameState(0))
Expand Down
48 changes: 44 additions & 4 deletions truco/action_any_quiero.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import (
"slices"
)

type ActionSayEnvidoNoQuiero struct{ act }
type ActionSayEnvidoQuiero struct{ act }
type ActionSayEnvidoNoQuiero struct {
act
Cost int `json:"cost"`
}
type ActionSayEnvidoQuiero struct {
act
Cost int `json:"cost"`
}
type ActionSayEnvidoScore struct {
act
Score int `json:"score"`
Expand All @@ -17,12 +23,14 @@ type ActionRevealEnvidoScore struct {
}
type ActionSayTrucoQuiero struct {
act
Cost int `json:"cost"`
// RequiresReminder is true if a player ran say_truco and the other player
// initiated an envido sequence. This action might seem out of context.
RequiresReminder bool `json:"requires_reminder"`
}
type ActionSayTrucoNoQuiero struct {
act
Cost int `json:"cost"`
// RequiresReminder is true if a player ran say_truco and the other player
// initiated an envido sequence. This action might seem out of context.
RequiresReminder bool `json:"requires_reminder"`
Expand Down Expand Up @@ -70,7 +78,7 @@ func (a ActionRevealEnvidoScore) IsPossible(g GameState) bool {
if roundLog.EnvidoWinnerPlayerID != a.PlayerID {
return false
}
if !g.IsRoundFinished && g.Players[a.PlayerID].Score+roundLog.EnvidoPoints < MaxPoints {
if !g.IsRoundFinished && g.Players[a.PlayerID].Score+roundLog.EnvidoPoints < g.RuleMaxPoints {
return false
}
revealedHand := Hand{Revealed: g.Players[a.PlayerID].Hand.Revealed}
Expand Down Expand Up @@ -123,7 +131,7 @@ func (a ActionSayEnvidoNoQuiero) Run(g *GameState) error {
}
g.EnvidoSequence.AddStep(a.GetName())
g.IsEnvidoFinished = true
cost, err := g.EnvidoSequence.Cost(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)
if err != nil {
return err
}
Expand Down Expand Up @@ -253,10 +261,42 @@ func (a ActionRevealEnvidoScore) YieldsTurn(g GameState) bool {

func (a *ActionSayTrucoQuiero) Enrich(g GameState) {
a.RequiresReminder = _doesTrucoActionRequireReminder(g)
quieroSeq, _ := g.TrucoSequence.WithStep(SAY_TRUCO_QUIERO)
quieroCost := quieroSeq.Cost()
a.Cost = quieroCost
}

func (a *ActionSayTrucoNoQuiero) Enrich(g GameState) {
a.RequiresReminder = _doesTrucoActionRequireReminder(g)
noQuieroSeq, _ := g.TrucoSequence.WithStep(SAY_TRUCO_NO_QUIERO)
quieroCost := noQuieroSeq.Cost()
a.Cost = quieroCost
}

func (a *ActionSayEnvidoQuiero) Enrich(g GameState) {
if !a.IsPossible(g) {
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)
)
a.Cost = quieroCost
}

func (a *ActionSayEnvidoNoQuiero) Enrich(g GameState) {
if !a.IsPossible(g) {
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)
)
a.Cost = noQuieroCost
}

func _doesTrucoActionRequireReminder(g GameState) bool {
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.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)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions 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.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)
if err != nil {
return err
}
Expand All @@ -51,7 +51,7 @@ func (a ActionSaySonMejores) Run(g *GameState) error {
func (a ActionSaySonMejores) YieldsTurn(g GameState) bool {
// In son_buenas/son_mejores/no_quiero, the turn should go to whoever started the sequence
// Unless the game should end due to the points won by this action.
if g.Players[a.PlayerID].Score+g.RoundsLog[g.RoundNumber].EnvidoPoints >= MaxPoints {
if g.Players[a.PlayerID].Score+g.RoundsLog[g.RoundNumber].EnvidoPoints >= g.RuleMaxPoints {
return false
}
return g.TurnPlayerID != g.EnvidoSequence.StartingPlayerID
Expand Down
62 changes: 53 additions & 9 deletions truco/actions_any_envido.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package truco

type ActionSayEnvido struct{ act }
type ActionSayFaltaEnvido struct{ act }
type ActionSayRealEnvido struct{ act }
type ActionSayEnvido struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}
type ActionSayFaltaEnvido struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}
type ActionSayRealEnvido struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}

func (a ActionSayEnvido) IsPossible(g GameState) bool { return g.AnyEnvidoActionTypeIsPossible(&a) }
func (a ActionSayFaltaEnvido) IsPossible(g GameState) bool {
return g.AnyEnvidoActionTypeIsPossible(&a)
}
func (a ActionSayRealEnvido) IsPossible(g GameState) bool { return g.AnyEnvidoActionTypeIsPossible(&a) }

func (a ActionSayEnvido) IsPossible(g GameState) bool { return g.AnyEnvidoActionTypeIsPossible(a) }
func (a ActionSayFaltaEnvido) IsPossible(g GameState) bool { return g.AnyEnvidoActionTypeIsPossible(a) }
func (a ActionSayRealEnvido) IsPossible(g GameState) bool { return g.AnyEnvidoActionTypeIsPossible(a) }
func (a ActionSayEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(&a) }
func (a ActionSayFaltaEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(&a) }
func (a ActionSayRealEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(&a) }

func (a ActionSayEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(a) }
func (a ActionSayFaltaEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(a) }
func (a ActionSayRealEnvido) Run(g *GameState) error { return g.AnyEnvidoActionTypeRunAction(a) }
func (a *ActionSayEnvido) Enrich(g GameState) { g.AnyEnvidoActionTypeEnrich(a) }
func (a *ActionSayFaltaEnvido) Enrich(g GameState) { g.AnyEnvidoActionTypeEnrich(a) }
func (a *ActionSayRealEnvido) Enrich(g GameState) { g.AnyEnvidoActionTypeEnrich(a) }

func (g GameState) AnyEnvidoActionTypeIsPossible(a Action) bool {
if g.IsRoundFinished {
Expand Down Expand Up @@ -46,3 +64,29 @@ func (g *GameState) AnyEnvidoActionTypeRunAction(a Action) error {
}
return nil
}

func (g GameState) AnyEnvidoActionTypeEnrich(a Action) {
if !a.IsPossible(g) {
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)
)

switch a.GetName() {
case SAY_ENVIDO:
a.(*ActionSayEnvido).QuieroCost = quieroCost
a.(*ActionSayEnvido).NoQuieroCost = noQuieroCost
case SAY_FALTA_ENVIDO:
a.(*ActionSayFaltaEnvido).QuieroCost = quieroCost
a.(*ActionSayFaltaEnvido).NoQuieroCost = noQuieroCost
case SAY_REAL_ENVIDO:
a.(*ActionSayRealEnvido).QuieroCost = quieroCost
a.(*ActionSayRealEnvido).NoQuieroCost = noQuieroCost
}
}
60 changes: 51 additions & 9 deletions truco/actions_any_truco.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package truco

type ActionSayTruco struct{ act }
type ActionSayQuieroRetruco struct{ act }
type ActionSayQuieroValeCuatro struct{ act }
type ActionSayTruco struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}
type ActionSayQuieroRetruco struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}
type ActionSayQuieroValeCuatro struct {
act
NoQuieroCost int `json:"noQuieroCost"`
QuieroCost int `json:"quieroCost"`
}

func (a ActionSayTruco) IsPossible(g GameState) bool { return g.AnyTrucoActionIsPossible(&a) }
func (a ActionSayQuieroRetruco) IsPossible(g GameState) bool { return g.AnyTrucoActionIsPossible(&a) }
func (a ActionSayQuieroValeCuatro) IsPossible(g GameState) bool {
return g.AnyTrucoActionIsPossible(&a)
}

func (a ActionSayTruco) IsPossible(g GameState) bool { return g.AnyTrucoActionIsPossible(a) }
func (a ActionSayQuieroRetruco) IsPossible(g GameState) bool { return g.AnyTrucoActionIsPossible(a) }
func (a ActionSayQuieroValeCuatro) IsPossible(g GameState) bool { return g.AnyTrucoActionIsPossible(a) }
func (a ActionSayTruco) Run(g *GameState) error { return g.AnyTrucoActionRunAction(&a) }
func (a ActionSayQuieroRetruco) Run(g *GameState) error { return g.AnyTrucoActionRunAction(&a) }
func (a ActionSayQuieroValeCuatro) Run(g *GameState) error { return g.AnyTrucoActionRunAction(&a) }

func (a ActionSayTruco) Run(g *GameState) error { return g.AnyTrucoActionRunAction(a) }
func (a ActionSayQuieroRetruco) Run(g *GameState) error { return g.AnyTrucoActionRunAction(a) }
func (a ActionSayQuieroValeCuatro) Run(g *GameState) error { return g.AnyTrucoActionRunAction(a) }
func (a *ActionSayTruco) Enrich(g GameState) { g.AnyTrucoActionTypeEnrich(a) }
func (a *ActionSayQuieroRetruco) Enrich(g GameState) { g.AnyTrucoActionTypeEnrich(a) }
func (a *ActionSayQuieroValeCuatro) Enrich(g GameState) { g.AnyTrucoActionTypeEnrich(a) }

func (g GameState) AnyTrucoActionIsPossible(a Action) bool {
if g.IsRoundFinished {
Expand Down Expand Up @@ -47,3 +65,27 @@ func (g *GameState) AnyTrucoActionRunAction(at Action) error {

return nil
}

func (g GameState) AnyTrucoActionTypeEnrich(a Action) {
if !a.IsPossible(g) {
return
}
var (
quieroSeq, _ = g.TrucoSequence.WithStep(SAY_TRUCO_QUIERO)
quieroCost = quieroSeq.Cost()
noQuieroSeq, _ = g.TrucoSequence.WithStep(SAY_TRUCO_NO_QUIERO)
noQuieroCost = noQuieroSeq.Cost()
)

switch a.GetName() {
case SAY_TRUCO:
a.(*ActionSayTruco).QuieroCost = quieroCost
a.(*ActionSayTruco).NoQuieroCost = noQuieroCost
case SAY_QUIERO_RETRUCO:
a.(*ActionSayQuieroRetruco).QuieroCost = quieroCost
a.(*ActionSayQuieroRetruco).NoQuieroCost = noQuieroCost
case SAY_QUIERO_VALE_CUATRO:
a.(*ActionSayQuieroValeCuatro).QuieroCost = quieroCost
a.(*ActionSayQuieroValeCuatro).NoQuieroCost = noQuieroCost
}
}
Loading

0 comments on commit bcfafea

Please sign in to comment.