diff --git a/format.go b/format.go new file mode 100644 index 0000000..31aab57 --- /dev/null +++ b/format.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" +) + +func FormatHandGroupings(hand []Card, groupings [][]int) string { + tmp := forkHand(hand) + + cgroups := [][]Card{} + for _, group := range groupings { + cgroup := []Card{} + for _, cidx := range group { + cgroup = append(cgroup, hand[cidx]) + + tmp[cidx] = NewMasked() + } + cgroups = append(cgroups, cgroup) + } + + leftover := []Card{} + for _, cv := range tmp { + if !cv.Masked() { + leftover = append(leftover, cv) + } + } + + return fmt.Sprintf("[ %v leftover %v ]", cgroups, leftover) +} diff --git a/format_test.go b/format_test.go new file mode 100644 index 0000000..aaa87b7 --- /dev/null +++ b/format_test.go @@ -0,0 +1,12 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormatHandGrouping(t *testing.T) { + str := FormatHandGroupings([]Card{5, 10, 15, 20}, [][]int{[]int{0, 1}, []int{2}}) + assert.EqualValues(t, "[ [[ 1♠ 2♠] [ 3♠]] leftover [ 4♠] ]", str) +} diff --git a/game.go b/game.go index 8a74253..895b5bc 100644 --- a/game.go +++ b/game.go @@ -71,28 +71,36 @@ func NewRound(round, numPlayers int, entropy *rand.Rand) *Round { return &r } -func FormatHandGroupings(hand []Card, groupings [][]int) string { - tmp := forkHand(hand) +func (r *Round) DrawFromDeck() Card { + newCard := r.d[0] + r.d = r.d[1:] + return newCard +} - cgroups := [][]Card{} - for _, group := range groupings { - cgroup := []Card{} - for _, cidx := range group { - cgroup = append(cgroup, hand[cidx]) +func (r *Round) PeekFromDiscard() Card { + return r.discard[len(r.discard)-1] +} - tmp[cidx] = Card(-1) - } - cgroups = append(cgroups, cgroup) - } +func (r *Round) DrawFromDiscard() Card { + newCard := r.discard[len(r.discard)-1] + r.discard = r.discard[0 : len(r.discard)-1] - leftover := []Card{} - for _, cv := range tmp { - if cv != Card(-1) { - leftover = append(leftover, cv) - } - } + return newCard +} - return fmt.Sprintf("[ %v leftover %v ]", cgroups, leftover) +func (r *Round) AddToHand(player int, c Card) { + r.hands[player] = append(r.hands[player], c) +} + +func (r *Round) Discard(player, idx int) { + unwantedCard := r.hands[player][idx] + + // Slice tricks: Copy end card into this slot and reslice bounds + + r.hands[player][idx] = r.hands[player][len(r.hands[player])-1] + r.hands[player] = r.hands[player][0 : len(r.hands[player])-1] + + r.discard = append(r.discard, unwantedCard) // On top = available for next player } func (r *Round) Play(startingPlayer int) (nextPlayer int, roundScores []int) { @@ -119,7 +127,7 @@ func (r *Round) Play(startingPlayer int) (nextPlayer int, roundScores []int) { if currentScore == 0 { // Declare victory - fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n", currentPlayer, r.hands[currentPlayer], currentBestGrouping) + fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n- Summary: %v\n", currentPlayer, r.hands[currentPlayer], currentBestGrouping, FormatHandGroupings(r.hands[currentPlayer], currentBestGrouping)) if roundEndsWhenPlayerIs == -1 { roundEndsWhenPlayerIs = currentPlayer @@ -141,6 +149,11 @@ func (r *Round) Play(startingPlayer int) (nextPlayer int, roundScores []int) { fmt.Printf("P%d has %v (score: %d)\n", p, FormatHandGroupings(r.hands[p], gr) /*r.hands[p], gr,*/, score) + if roundEndsWhenPlayerIs == p && score != 0 { + fmt.Printf("Expected player %d to have won, but they didn't?\n", p) + panic("???") + } + roundScores[p] = score } diff --git a/game_test.go b/game_test.go index e477ebf..a86d9fd 100644 --- a/game_test.go +++ b/game_test.go @@ -17,3 +17,28 @@ func TestPlayRound(t *testing.T) { assert.EqualValues(t, 0, nextPlayer) assert.EqualValues(t, []int{0, 28, 6, 6}, scores) } + +func TestDrawDiscard(t *testing.T) { + + entropy := rand.New(rand.NewSource(0xdeadbeef)) + + rr := NewRound(5, 4, entropy) + + assert.EqualValues(t, []Card{50, 47, 24, 25, 22}, rr.hands[0]) + + c := rr.DrawFromDeck() + assert.EqualValues(t, Card(15), c) + + rr.AddToHand(0, c) + assert.EqualValues(t, []Card{50, 47, 24, 25, 22, 15}, rr.hands[0]) + + rr.Discard(0, 2) + assert.EqualValues(t, []Card{50, 47, 15, 25, 22}, rr.hands[0]) + + assert.EqualValues(t, Card(24), rr.PeekFromDiscard()) + + c = rr.DrawFromDiscard() + rr.AddToHand(0, c) + assert.EqualValues(t, []Card{50, 47, 15, 25, 22, 24}, rr.hands[0]) + +} diff --git a/search.go b/search.go index 6e82059..a4c111f 100644 --- a/search.go +++ b/search.go @@ -119,7 +119,7 @@ func MakeMultiGroups(hand []Card, wildface int) [][][]int { // If that uses up all cards, then early-exit anyUnpaired := false for cidx := 0; cidx < len(cloneHand); cidx++ { - if cloneHand[cidx] != Card(-1) { + if !cloneHand[cidx].Masked() { anyUnpaired = true break } diff --git a/strategy.go b/strategy.go index 995092a..727cfac 100644 --- a/strategy.go +++ b/strategy.go @@ -19,7 +19,7 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { for cidx := 0; cidx < r.round; cidx++ { // Pick up the last discard card, and discard card@cidx theoretically := forkHand(r.hands[currentPlayer]) - theoretically[cidx] = r.discard[len(r.discard)-1] + theoretically[cidx] = r.PeekFromDiscard() _, score := FindBestGrouping(theoretically, r.round) if score < bestDiscardBasedScore { @@ -27,6 +27,10 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { bestDiscardBasedIdx = cidx bestDiscardBasedScore = score } + + if score == 0 { + break // Early exit + } } } @@ -42,16 +46,15 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { fmt.Printf("P%d discards %v immediately\n", currentPlayer, r.discard[len(r.discard)-1]) } else { - // Take the discard card and discard cidx - newCard := r.discard[len(r.discard)-1] - r.discard = r.discard[0 : len(r.discard)-1] + // Take the discard card + newCard := r.DrawFromDiscard() - unwantedCard := r.hands[currentPlayer][bestDiscardBasedIdx] - r.hands[currentPlayer][bestDiscardBasedIdx] = newCard + r.AddToHand(currentPlayer, newCard) - r.discard = append(r.discard, unwantedCard) // On top = available for next player + // Discard cidx - fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, unwantedCard, bestDiscardBasedIdx) + fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, r.hands[currentPlayer][bestDiscardBasedIdx], bestDiscardBasedIdx) + r.Discard(currentPlayer, bestDiscardBasedIdx) } @@ -60,9 +63,8 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { // score // Take from the deck - newCard := r.d[0] - r.d = r.d[1:] - r.hands[currentPlayer] = append(r.hands[currentPlayer], newCard) + newCard := r.DrawFromDeck() + r.AddToHand(currentPlayer, newCard) fmt.Printf("P%d taking %v from the deck\n", currentPlayer, newCard) @@ -73,7 +75,7 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { for cidx := 0; cidx < r.round+1; cidx++ { // Assume we discard that card, theoretically := forkHand(r.hands[currentPlayer]) - theoretically[cidx] = Card(-1) // Never matches + worth 0 points + theoretically[cidx] = NewMasked() // Never matches + worth 0 points _, score := FindBestGrouping(theoretically, r.round) if score < bestRandomBasedScore { @@ -82,19 +84,15 @@ func (r *Round) PlayDefaultStrategy(currentPlayer int) { bestRandomBasedScore = score } + if score == 0 { + break // Early exit + } } // Discard our chosen card - // Slice tricks: Copy end card into this slot and reslice bounds - unwantedCard := r.hands[currentPlayer][bestRandomBasedIdx] - - fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, unwantedCard, bestRandomBasedIdx) - - r.hands[currentPlayer][bestRandomBasedIdx] = r.hands[currentPlayer][len(r.hands[currentPlayer])-1] - r.hands[currentPlayer] = r.hands[currentPlayer][0 : len(r.hands[currentPlayer])-1] - - r.discard = append(r.discard, unwantedCard) // On top = available for next player + fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, r.hands[currentPlayer][bestRandomBasedIdx], bestRandomBasedIdx) + r.Discard(currentPlayer, bestRandomBasedIdx) }