faster run search, entropy parameter, refactor currentPlayer, round test
This commit is contained in:
parent
2f2c3cdd22
commit
279d536458
4
card.go
4
card.go
@ -87,8 +87,8 @@ func NewCardFrom(face int, suit Suit) Card {
|
||||
return Card(face*5 + int(suit))
|
||||
}
|
||||
|
||||
func Shuffle(c []Card) {
|
||||
rand.Shuffle(len(c), func(i, j int) {
|
||||
func Shuffle(c []Card, entropy *rand.Rand) {
|
||||
entropy.Shuffle(len(c), func(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -10,10 +11,12 @@ func TestCards(t *testing.T) {
|
||||
d := Deck()
|
||||
|
||||
assert.Len(t, d, DeckSize)
|
||||
|
||||
assert.EqualValues(t, []Card{0, 0, 0, 0, 0, 0, 15, 15, 20, 20, 25, 25, 30, 30, 35, 35, 40, 40, 45, 45, 50, 50, 55, 55, 60, 60, 65, 65, 16, 16, 21, 21, 26, 26, 31, 31, 36, 36, 41, 41, 46, 46, 51, 51, 56, 56, 61, 61, 66, 66, 17, 17, 22, 22, 27, 27, 32, 32, 37, 37, 42, 42, 47, 47, 52, 52, 57, 57, 62, 62, 67, 67, 18, 18, 23, 23, 28, 28, 33, 33, 38, 38, 43, 43, 48, 48, 53, 53, 58, 58, 63, 63, 68, 68, 19, 19, 24, 24, 29, 29, 34, 34, 39, 39, 44, 44, 49, 49, 54, 54, 59, 59, 64, 64, 69, 69}, d)
|
||||
|
||||
Shuffle(d)
|
||||
entropy := rand.New(rand.NewSource(0xdeadbeef))
|
||||
|
||||
Shuffle(d, entropy)
|
||||
|
||||
assert.Len(t, d, DeckSize)
|
||||
assert.EqualValues(t, []Card{50, 47, 24, 25, 22, 61, 33, 66, 36, 41, 16, 18, 58, 31, 16, 48, 38, 39, 33, 17, 0, 15, 32, 68, 23, 67, 27, 18, 22, 0, 46, 45, 31, 65, 60, 28, 68, 51, 26, 63, 41, 53, 65, 42, 59, 56, 40, 55, 30, 54, 57, 66, 61, 26, 51, 49, 55, 46, 35, 44, 42, 24, 45, 52, 59, 39, 19, 30, 36, 47, 49, 56, 0, 32, 62, 67, 50, 37, 58, 21, 69, 48, 53, 23, 57, 64, 34, 0, 21, 43, 27, 0, 64, 52, 60, 69, 17, 63, 35, 44, 25, 20, 43, 29, 19, 38, 34, 15, 54, 29, 28, 37, 20, 0, 62, 40}, d)
|
||||
}
|
||||
|
50
game.go
50
game.go
@ -2,9 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func PlayGame(numPlayers int) {
|
||||
func PlayGame(numPlayers int, entropy *rand.Rand) {
|
||||
|
||||
currentPlayer := 0
|
||||
|
||||
@ -12,9 +13,10 @@ func PlayGame(numPlayers int) {
|
||||
|
||||
for round := 3; round < 14; round++ {
|
||||
|
||||
r := NewRound(round, numPlayers, currentPlayer)
|
||||
roundScores := r.Play()
|
||||
currentPlayer = r.currentPlayer
|
||||
r := NewRound(round, numPlayers, entropy)
|
||||
nextPlayer, roundScores := r.Play(currentPlayer)
|
||||
|
||||
currentPlayer = nextPlayer
|
||||
|
||||
for p := 0; p < numPlayers; p++ {
|
||||
runningScores[p] += roundScores[p]
|
||||
@ -28,24 +30,22 @@ func PlayGame(numPlayers int) {
|
||||
}
|
||||
|
||||
type Round struct {
|
||||
d []Card
|
||||
discard []Card
|
||||
round int
|
||||
numPlayers int
|
||||
currentPlayer int
|
||||
hands [][]Card
|
||||
d []Card
|
||||
discard []Card
|
||||
round int
|
||||
numPlayers int
|
||||
hands [][]Card
|
||||
}
|
||||
|
||||
func NewRound(round, numPlayers, currentPlayer int) *Round {
|
||||
func NewRound(round, numPlayers int, entropy *rand.Rand) *Round {
|
||||
|
||||
r := Round{
|
||||
round: round,
|
||||
numPlayers: numPlayers,
|
||||
currentPlayer: currentPlayer,
|
||||
round: round,
|
||||
numPlayers: numPlayers,
|
||||
}
|
||||
|
||||
r.d = Deck()
|
||||
Shuffle(r.d)
|
||||
Shuffle(r.d, entropy)
|
||||
|
||||
r.discard = []Card{}
|
||||
|
||||
@ -95,7 +95,9 @@ func FormatHandGroupings(hand []Card, groupings [][]int) string {
|
||||
return fmt.Sprintf("[ %v leftover %v ]", cgroups, leftover)
|
||||
}
|
||||
|
||||
func (r *Round) Play() []int {
|
||||
func (r *Round) Play(startingPlayer int) (nextPlayer int, roundScores []int) {
|
||||
|
||||
currentPlayer := startingPlayer
|
||||
|
||||
fmt.Printf("Discard stack: %v\n", r.discard)
|
||||
|
||||
@ -104,34 +106,34 @@ func (r *Round) Play() []int {
|
||||
roundEndsWhenPlayerIs := -1
|
||||
|
||||
for {
|
||||
if roundEndsWhenPlayerIs == r.currentPlayer {
|
||||
if roundEndsWhenPlayerIs == currentPlayer {
|
||||
break
|
||||
}
|
||||
|
||||
// Play the strategy for the current player
|
||||
r.PlayDefaultStrategy()
|
||||
r.PlayDefaultStrategy(currentPlayer)
|
||||
|
||||
// Check, one more time, if we have a winning hand
|
||||
|
||||
currentBestGrouping, currentScore := FindBestGrouping(r.hands[r.currentPlayer], r.round)
|
||||
currentBestGrouping, currentScore := FindBestGrouping(r.hands[currentPlayer], r.round)
|
||||
if currentScore == 0 {
|
||||
|
||||
// Declare victory
|
||||
fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n", r.currentPlayer, r.hands[r.currentPlayer], currentBestGrouping)
|
||||
fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n", currentPlayer, r.hands[currentPlayer], currentBestGrouping)
|
||||
|
||||
if roundEndsWhenPlayerIs == -1 {
|
||||
roundEndsWhenPlayerIs = r.currentPlayer
|
||||
roundEndsWhenPlayerIs = currentPlayer
|
||||
} // Otherwise, the round is already ending anyway
|
||||
}
|
||||
|
||||
// Move to the next player
|
||||
r.currentPlayer = (r.currentPlayer + 1) % r.numPlayers
|
||||
currentPlayer = (currentPlayer + 1) % r.numPlayers
|
||||
}
|
||||
|
||||
// The round has ended
|
||||
// Figure out what each player scored
|
||||
|
||||
roundScores := make([]int, r.numPlayers)
|
||||
roundScores = make([]int, r.numPlayers)
|
||||
|
||||
for p := 0; p < r.numPlayers; p++ {
|
||||
|
||||
@ -148,5 +150,5 @@ func (r *Round) Play() []int {
|
||||
}
|
||||
|
||||
// Done
|
||||
return roundScores
|
||||
return currentPlayer, roundScores
|
||||
}
|
||||
|
14
game_test.go
14
game_test.go
@ -1,9 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPlayGame(t *testing.T) {
|
||||
PlayGame(4)
|
||||
func TestPlayRound(t *testing.T) {
|
||||
|
||||
entropy := rand.New(rand.NewSource(0xdeadbeef))
|
||||
|
||||
rr := NewRound(5, 4, entropy)
|
||||
nextPlayer, scores := rr.Play(0)
|
||||
|
||||
assert.EqualValues(t, 0, nextPlayer)
|
||||
assert.EqualValues(t, []int{0, 28, 6, 6}, scores)
|
||||
}
|
||||
|
24
search.go
24
search.go
@ -41,25 +41,29 @@ func MakeBooks(hand []Card, wildFace int) [][]int {
|
||||
return pairings
|
||||
}
|
||||
|
||||
func continueRun(hand []Card, wildFace int, requireSuit Suit, searchNextFace int, takenIndexes map[int]struct{}) []map[int]struct{} {
|
||||
func continueRun(hand []Card, wildFace int, requireSuit Suit, searchNextFace int, takenIndexes []int) [][]int {
|
||||
|
||||
possibilities := []map[int]struct{}{
|
||||
possibilities := [][]int{
|
||||
takenIndexes,
|
||||
}
|
||||
|
||||
// Do you have any XX|wild
|
||||
posns := searchExcept(hand, wildFace, NewCardFrom(searchNextFace, requireSuit), takenIndexes)
|
||||
posns := search(hand, wildFace, NewCardFrom(searchNextFace, requireSuit))
|
||||
|
||||
// For each match:
|
||||
for _, midx := range posns {
|
||||
|
||||
// Take out that card
|
||||
branchTaken := forkPossibilities(takenIndexes)
|
||||
branchTaken[midx] = struct{}{}
|
||||
branchTaken := make([]int, len(takenIndexes)+1)
|
||||
copy(branchTaken, takenIndexes)
|
||||
branchTaken[len(branchTaken)-1] = midx
|
||||
|
||||
branchTakenHand := forkHand(hand)
|
||||
branchTakenHand[midx] = NewMasked()
|
||||
|
||||
// Try and continue the run upwards to the next card, using only the remaining parts of the hand
|
||||
// (We will never find K+1)
|
||||
continuations := continueRun(hand, wildFace, requireSuit, searchNextFace+1, branchTaken)
|
||||
continuations := continueRun(branchTakenHand, wildFace, requireSuit, searchNextFace+1, branchTaken)
|
||||
|
||||
// We found those possibilities for this run
|
||||
possibilities = append(possibilities, continuations...)
|
||||
@ -76,14 +80,14 @@ func MakeRuns(hand []Card, wildFace int) [][]int {
|
||||
for f := 3; f < 12; f++ { // -2 from usual 14, as you can't start an upward run from Q/K
|
||||
|
||||
// Starting with this card, find all runs going upward
|
||||
found := continueRun(hand, wildFace, Suit(s), f, map[int]struct{}{})
|
||||
found := continueRun(hand, wildFace, Suit(s), f, make([]int, 0))
|
||||
|
||||
// For each possible run we could make starting here:
|
||||
for _, ff := range found {
|
||||
if len(ff) < 3 {
|
||||
continue // Short runs are no good
|
||||
}
|
||||
pairings = append(pairings, flattenPossibility(ff))
|
||||
pairings = append(pairings, ff)
|
||||
}
|
||||
|
||||
}
|
||||
@ -157,7 +161,9 @@ func ScoreAsUnpaired(hand []Card) int {
|
||||
func FindBestGrouping(hand []Card, wildface int) ([][]int, int) {
|
||||
groupings := MakeMultiGroups(hand, wildface)
|
||||
|
||||
fmt.Printf("Found %d possible groupings\n", len(groupings))
|
||||
if len(groupings) > 1000 {
|
||||
fmt.Printf("Found %d possible groupings\n", len(groupings))
|
||||
}
|
||||
|
||||
bestScore := 50 * len(hand) // Worst possible score
|
||||
bestGroupIdx := -1
|
||||
|
26
strategy.go
26
strategy.go
@ -4,12 +4,10 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (r *Round) PlayDefaultStrategy() {
|
||||
|
||||
currentPlayer := r.currentPlayer
|
||||
func (r *Round) PlayDefaultStrategy(currentPlayer int) {
|
||||
|
||||
// Score our hand as if we pick up the discard and discard it immediately
|
||||
_, score := FindBestGrouping(r.hands[r.currentPlayer], r.round)
|
||||
_, score := FindBestGrouping(r.hands[currentPlayer], r.round)
|
||||
baseScore := score
|
||||
bestDiscardBasedIdx := -1
|
||||
bestDiscardBasedScore := score
|
||||
@ -20,7 +18,7 @@ func (r *Round) PlayDefaultStrategy() {
|
||||
|
||||
for cidx := 0; cidx < r.round; cidx++ {
|
||||
// Pick up the last discard card, and discard card@cidx
|
||||
theoretically := forkHand(r.hands[r.currentPlayer])
|
||||
theoretically := forkHand(r.hands[currentPlayer])
|
||||
theoretically[cidx] = r.discard[len(r.discard)-1]
|
||||
_, score := FindBestGrouping(theoretically, r.round)
|
||||
|
||||
@ -36,20 +34,20 @@ func (r *Round) PlayDefaultStrategy() {
|
||||
// If taking the discard would let us win, or reduce our score by >5, take it
|
||||
if bestDiscardBasedScore == 0 || (baseScore-bestDiscardBasedScore) > 5 {
|
||||
|
||||
fmt.Printf("P%d taking %v from the discard pile\n", r.currentPlayer, r.discard[len(r.discard)-1])
|
||||
fmt.Printf("P%d taking %v from the discard pile\n", currentPlayer, r.discard[len(r.discard)-1])
|
||||
|
||||
// We're going to win
|
||||
if bestDiscardBasedIdx == -1 {
|
||||
// Take the discard card + put it back again i.e. no change
|
||||
fmt.Printf("P%d discards %v immediately\n", r.currentPlayer, r.discard[len(r.discard)-1])
|
||||
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]
|
||||
|
||||
unwantedCard := r.hands[r.currentPlayer][bestDiscardBasedIdx]
|
||||
r.hands[r.currentPlayer][bestDiscardBasedIdx] = newCard
|
||||
unwantedCard := r.hands[currentPlayer][bestDiscardBasedIdx]
|
||||
r.hands[currentPlayer][bestDiscardBasedIdx] = newCard
|
||||
|
||||
r.discard = append(r.discard, unwantedCard) // On top = available for next player
|
||||
|
||||
@ -64,9 +62,9 @@ func (r *Round) PlayDefaultStrategy() {
|
||||
|
||||
newCard := r.d[0]
|
||||
r.d = r.d[1:]
|
||||
r.hands[r.currentPlayer] = append(r.hands[r.currentPlayer], newCard)
|
||||
r.hands[currentPlayer] = append(r.hands[currentPlayer], newCard)
|
||||
|
||||
fmt.Printf("P%d taking %v from the deck\n", r.currentPlayer, newCard)
|
||||
fmt.Printf("P%d taking %v from the deck\n", currentPlayer, newCard)
|
||||
|
||||
bestRandomBasedIdx := -1
|
||||
bestRandomBasedScore := (r.round * 50) + 51 // Worse than the worst possible score
|
||||
@ -89,12 +87,12 @@ func (r *Round) PlayDefaultStrategy() {
|
||||
// Discard our chosen card
|
||||
// Slice tricks: Copy end card into this slot and reslice bounds
|
||||
|
||||
unwantedCard := r.hands[r.currentPlayer][bestRandomBasedIdx]
|
||||
unwantedCard := r.hands[currentPlayer][bestRandomBasedIdx]
|
||||
|
||||
fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, unwantedCard, bestRandomBasedIdx)
|
||||
|
||||
r.hands[r.currentPlayer][bestRandomBasedIdx] = r.hands[r.currentPlayer][len(r.hands[r.currentPlayer])-1]
|
||||
r.hands[r.currentPlayer] = r.hands[r.currentPlayer][0 : len(r.hands[r.currentPlayer])-1]
|
||||
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
|
||||
|
||||
|
16
util.go
16
util.go
@ -7,25 +7,11 @@ import (
|
||||
func search(hand []Card, wildFace int, search Card) []int {
|
||||
ret := []int{}
|
||||
for idx, c := range hand {
|
||||
if c == search || c.Joker() || c.Face() == wildFace {
|
||||
ret = append(ret, idx)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func searchExcept(hand []Card, wildFace int, search Card, blockindexes map[int]struct{}) []int {
|
||||
ret := []int{}
|
||||
for idx, c := range hand {
|
||||
if _, blocked := blockindexes[idx]; blocked {
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Masked() {
|
||||
continue // Doesn't match anything
|
||||
}
|
||||
|
||||
if c.Joker() || c == search || c.Face() == wildFace {
|
||||
if c == search || c.Joker() || c.Face() == wildFace {
|
||||
ret = append(ret, idx)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user