Skip to content

Commit

Permalink
Fix truco yield rule bug. Small improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
marianogappa committed Jun 25, 2024
1 parent 00e271b commit 987cabc
Show file tree
Hide file tree
Showing 12 changed files with 490 additions and 35 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist/
truco
/truco
!/truco/
14 changes: 11 additions & 3 deletions exampleclient/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ func NewUI() *ui {
sendKeyPressCh: make(chan rune),
}
ui.startKeyEventLoop()
err := termbox.Init()
if err != nil {
panic(err)
}
return ui
}

func (u *ui) Close() {
termbox.Close()
}

func (u *ui) play(playerID int, gameState truco.GameState) (truco.Action, error) {
err := u.printState(playerID, gameState, PRINT_MODE_NORMAL)
err := u.render(playerID, gameState, PRINT_MODE_NORMAL)
if err != nil {
return nil, err
}
Expand All @@ -52,7 +60,7 @@ func (u *ui) play(playerID int, gameState truco.GameState) (truco.Action, error)
input = fmt.Sprintf(`{"name":"%v","score":%d}`, actionName, gameState.Hands[gameState.TurnPlayerID].EnvidoScore())
}
if actionName == "reveal_card" {
err := u.printState(playerID, gameState, PRINT_MODE_WHICH_CARD_REVEAL)
err := u.render(playerID, gameState, PRINT_MODE_WHICH_CARD_REVEAL)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -100,7 +108,7 @@ const (
PRINT_MODE_END
)

func (u *ui) printState(playerID int, state truco.GameState, mode printMode) error {
func (u *ui) render(playerID int, state truco.GameState, mode printMode) error {
err := termbox.Clear(termbox.ColorWhite, termbox.ColorBlack)
if err != nil {
return err
Expand Down
36 changes: 11 additions & 25 deletions exampleclient/websocket_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,57 @@ import (
"github.com/gorilla/websocket"
"github.com/marianogappa/truco/server"
"github.com/marianogappa/truco/truco"
"github.com/nsf/termbox-go"
)

func Player(playerID int, address string) {
ui := NewUI()

err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()
defer ui.Close()

conn, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://%v/ws", address), nil)
if err != nil {
log.Println("Failed to connect to WebSocket server:", err)
return
log.Fatalf("Failed to connect to WebSocket server: %v", err)
}
defer conn.Close()

if err := server.WsSend(conn, server.NewMessageHello(playerID)); err != nil {
log.Println(err)
return
log.Fatal(err)
}

lastRound := 0
for {
gameState, err := server.WsReadMessage[truco.GameState, server.MessageHeresGameState](conn, server.MessageTypeHeresGameState)
if err != nil {
log.Println(err)
return
log.Fatal(err)
}

if gameState.IsEnded {
_ = ui.printState(playerID, *gameState, PRINT_MODE_END)
_ = ui.render(playerID, *gameState, PRINT_MODE_END)
return
}

if gameState.RoundNumber != lastRound && lastRound != 0 {
err := ui.printState(playerID, *gameState, PRINT_MODE_SHOW_ROUND_RESULT)
err := ui.render(playerID, *gameState, PRINT_MODE_SHOW_ROUND_RESULT)
if err != nil {
log.Println(err)
return
log.Fatal(err)
}
}
lastRound = gameState.RoundNumber

if gameState.TurnPlayerID != playerID {
err := ui.printState(playerID, *gameState, PRINT_MODE_NORMAL)
if err != nil {
log.Println(err)
return
if err := ui.render(playerID, *gameState, PRINT_MODE_NORMAL); err != nil {
log.Fatal(err)
}
continue
}

action, err := ui.play(playerID, *gameState)
if err != nil {
fmt.Println("Invalid action:", err)
break
log.Fatal("Invalid action:", err)
}

msg, _ := server.NewMessageAction(action)
if err := server.WsSend(conn, msg); err != nil {
log.Println(err)
return
log.Fatal(err)
}
}
}
7 changes: 7 additions & 0 deletions truco/action_any_quiero.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,10 @@ func (a ActionSayTrucoNoQuiero) Run(g *GameState) error {
g.Scores[g.OpponentPlayerID()] += cost
return nil
}

func (a ActionSayTrucoQuiero) YieldsTurn(g GameState) bool {
// Next turn belongs to the player who started the truco
// "sub-sequence". Thus, yield turn if the current player
// is not the one who started the sub-sequence.
return g.TurnPlayerID != g.TrucoSequence.StartingPlayerID
}
44 changes: 44 additions & 0 deletions truco/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,47 @@ func (a act) GetName() string {
func (a act) YieldsTurn(g GameState) bool {
return true
}

func newActionSayEnvido() Action {
return ActionSayEnvido{act: act{Name: SAY_ENVIDO}}
}

func newActionSayEnvidoNoQuiero() Action {
return ActionSayEnvidoNoQuiero{act: act{Name: SAY_ENVIDO_NO_QUIERO}}
}

func newActionSayEnvidoQuiero(score int) Action {
return ActionSayEnvidoQuiero{act: act{Name: SAY_ENVIDO_QUIERO}, Score: score}
}

func newActionSayTrucoQuiero() Action {
return ActionSayTrucoQuiero{act: act{Name: SAY_TRUCO_QUIERO}}
}

func newActionSayTrucoNoQuiero() Action {
return ActionSayTrucoNoQuiero{act: act{Name: SAY_TRUCO_NO_QUIERO}}
}

func newActionSayTruco() Action {
return ActionSayTruco{act: act{Name: SAY_TRUCO}}
}

func newActionSayQuieroRetruco() Action {
return ActionSayQuieroRetruco{act: act{Name: SAY_QUIERO_RETRUCO}}
}

func newActionSayQuieroValeCuatro() Action {
return ActionSayQuieroValeCuatro{act: act{Name: SAY_QUIERO_VALE_CUATRO}}
}

func newActionSaySonBuenas() Action {
return ActionSaySonBuenas{act: act{Name: SAY_SON_BUENAS}}
}

func newActionSaySonMejores(score int) Action {
return ActionSaySonMejores{act: act{Name: SAY_SON_MEJORES}, Score: score}
}

func newActionRevealCard(card Card) Action {
return ActionRevealCard{act: act{Name: REVEAL_CARD}, Card: card}
}
9 changes: 9 additions & 0 deletions truco/actions_any_truco.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,14 @@ func (g *GameState) AnyTrucoActionRunAction(at Action) error {
if !ok {
return errActionNotPossible
}

// Possible actions are "truco", "quiero retruco" and "quiero vale cuatro", not "quiero"/"no quiero".
// If this is the first action in a sub-sequence (subsequences are delimited by "quiero" actions),
// Store the player ID that started the sub-sequence, so that turn can be yielded correctly after
// a "quiero" action.
if g.TrucoSequence.IsSubsequenceStart() {
g.TrucoSequence.StartingPlayerID = g.TurnPlayerID
}

return nil
}
25 changes: 24 additions & 1 deletion truco/deck.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func (c Card) String() string {
}

type deck struct {
cards []Card
cards []Card
dealHandFunc func() *Hand
}

// Hand represents a player's hand. Cards can be revealed or unrevealed.
Expand All @@ -37,6 +38,23 @@ type Hand struct {
Revealed []Card `json:"revealed"`
}

func (h Hand) DeepCopy() Hand {
cpyUnrevealed := []Card{}
cpyRevealed := []Card{}
for _, c := range h.Unrevealed {
newC := c
cpyUnrevealed = append(cpyUnrevealed, newC)
}
for _, c := range h.Revealed {
newC := c
cpyRevealed = append(cpyRevealed, newC)
}
return Hand{
Unrevealed: cpyUnrevealed,
Revealed: cpyRevealed,
}
}

func (h Hand) HasUnrevealedCard(c Card) bool {
for _, card := range h.Unrevealed {
if card == c {
Expand Down Expand Up @@ -88,10 +106,15 @@ func makeSpanishCards() []Card {

func newDeck() *deck {
d := deck{cards: makeSpanishCards()}
d.dealHandFunc = d.defaultDealHand
return &d
}

func (d *deck) dealHand() *Hand {
return d.dealHandFunc()
}

func (d *deck) defaultDealHand() *Hand {
hand := &Hand{}
for i := 0; i < 3; i++ {
hand.Unrevealed = append(hand.Unrevealed, d.cards[i])
Expand Down
53 changes: 53 additions & 0 deletions truco/deck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package truco
import (
"fmt"
"testing"

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

func TestCard_CompareTrucoScore(t *testing.T) {
Expand Down Expand Up @@ -61,3 +63,54 @@ func TestCard_CompareTrucoScore(t *testing.T) {
})
}
}

func withDeck(d *deck) func(*GameState) {
return func(g *GameState) {
g.deck = d
}
}

func newTestDeck(hands []Hand) *deck {
if len(hands) < 2 {
panic("need at least 2 hands")
}
var i int
_dealHand := func() *Hand {
h := hands[i%len(hands)].DeepCopy()
i++
return &h
}
return &deck{cards: nil, dealHandFunc: _dealHand}
}

func TestEnvidoScore(t *testing.T) {
tests := []struct {
hands []Hand
expected1 int
expected2 int
}{
{
hands: []Hand{
{
Unrevealed: []Card{{Suit: ESPADA, Number: 1}, {Suit: ESPADA, Number: 7}},
Revealed: []Card{{Suit: ORO, Number: 6}},
},
{
Unrevealed: []Card{{Suit: ESPADA, Number: 5}, {Suit: ESPADA, Number: 6}},
Revealed: []Card{{Suit: ORO, Number: 1}},
},
},
expected1: 28,
expected2: 31,
},
}

for _, tt := range tests {
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())
})
}
}
Loading

0 comments on commit 987cabc

Please sign in to comment.