split files, fix scoring
This commit is contained in:
parent
42490e2b4b
commit
f0d6923c71
22
card.go
22
card.go
@ -30,11 +30,23 @@ func (c Card) Joker() bool {
|
||||
return (c == 0)
|
||||
}
|
||||
|
||||
func (c Card) Masked() bool {
|
||||
return (c == -1)
|
||||
}
|
||||
|
||||
func (c Card) Suit() Suit {
|
||||
if c.Joker() || c.Masked() {
|
||||
panic("Suit on invalid")
|
||||
}
|
||||
|
||||
return Suit(int(c) % 5)
|
||||
}
|
||||
|
||||
func (c Card) Face() int {
|
||||
if c.Joker() || c.Masked() {
|
||||
panic("Face on invalid")
|
||||
}
|
||||
|
||||
return int(c) / 5
|
||||
}
|
||||
|
||||
@ -43,7 +55,7 @@ func (c Card) String() string {
|
||||
return "Jok" // 🂿 U+1F0BF
|
||||
}
|
||||
|
||||
if c == -1 {
|
||||
if c.Masked() {
|
||||
return "INV" // Invalid
|
||||
}
|
||||
|
||||
@ -63,6 +75,10 @@ func (c Card) String() string {
|
||||
|
||||
}
|
||||
|
||||
func NewMasked() Card {
|
||||
return Card(-1)
|
||||
}
|
||||
|
||||
func NewJoker() Card {
|
||||
return Card(0)
|
||||
}
|
||||
@ -77,10 +93,12 @@ func Shuffle(c []Card) {
|
||||
})
|
||||
}
|
||||
|
||||
const DeckSize = 58 * 2
|
||||
|
||||
func Deck() []Card {
|
||||
// The full game deck is two 58-card decks
|
||||
// Each 58-card deck has 3 jokers plus {3..10 J Q K} in 5 suits.
|
||||
ret := make([]Card, 0, 58*2)
|
||||
ret := make([]Card, 0, DeckSize)
|
||||
for i := 0; i < 6; i++ {
|
||||
ret = append(ret, NewJoker()) // Joker
|
||||
}
|
||||
|
@ -9,12 +9,11 @@ import (
|
||||
func TestCards(t *testing.T) {
|
||||
d := Deck()
|
||||
|
||||
assert.Len(t, d, 58*2)
|
||||
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)
|
||||
|
||||
assert.Len(t, d, 58*2)
|
||||
|
||||
assert.Len(t, d, DeckSize)
|
||||
}
|
||||
|
270
game.go
270
game.go
@ -8,147 +8,145 @@ func PlayGame(numPlayers int) {
|
||||
|
||||
currentPlayer := 0
|
||||
|
||||
runningScores := make([]int, numPlayers)
|
||||
|
||||
for round := 3; round < 14; round++ {
|
||||
|
||||
d := Deck()
|
||||
Shuffle(d)
|
||||
r := NewRound(round, numPlayers, currentPlayer)
|
||||
roundScores := r.Play()
|
||||
currentPlayer = r.currentPlayer
|
||||
|
||||
discard := []Card{}
|
||||
|
||||
fmt.Printf("# Round %d\n\n", round)
|
||||
|
||||
// Deal starting cards
|
||||
|
||||
hands := make([][]Card, numPlayers)
|
||||
for p := 0; p < numPlayers; p++ {
|
||||
hands[p] = d[0:round] // Deal from the bottom (0)
|
||||
d = d[round:]
|
||||
|
||||
fmt.Printf("P%d starting hand: %v\n", p, hands[p])
|
||||
runningScores[p] += roundScores[p]
|
||||
}
|
||||
|
||||
// Deal one into the discard pile
|
||||
|
||||
discard = append(discard, d[0])
|
||||
d = d[1:]
|
||||
|
||||
fmt.Printf("Discard stack: %v\n", discard)
|
||||
|
||||
fmt.Printf("Deck has %d cards remaining\n", len(d))
|
||||
|
||||
roundEndsWhenPlayerIs := -1
|
||||
|
||||
for {
|
||||
if roundEndsWhenPlayerIs == currentPlayer {
|
||||
break
|
||||
}
|
||||
|
||||
// Score our hand as if we pick up the discard and discard it immediately
|
||||
_, score := FindBestGrouping(hands[currentPlayer], round)
|
||||
baseScore := score
|
||||
bestDiscardBasedIdx := -1
|
||||
bestDiscardBasedScore := score
|
||||
|
||||
// Score our hand as if we pick up the discard card and discard each other card
|
||||
|
||||
for cidx := 0; cidx < round; cidx++ {
|
||||
// Pick up the last discard card, and discard card@cidx
|
||||
theoretically := forkHand(hands[currentPlayer])
|
||||
theoretically[cidx] = discard[len(discard)-1]
|
||||
_, score := FindBestGrouping(theoretically, round)
|
||||
|
||||
if score < bestDiscardBasedScore {
|
||||
// TODO if the points are equal or near-equal, prefer not to discard a wild for the next player
|
||||
bestDiscardBasedIdx = cidx
|
||||
bestDiscardBasedScore = score
|
||||
}
|
||||
}
|
||||
|
||||
// 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", currentPlayer, discard[len(discard)-1])
|
||||
|
||||
// We're going to win
|
||||
if bestDiscardBasedIdx == -1 {
|
||||
// Take the discard card + put it back again i.e. no change
|
||||
|
||||
} else {
|
||||
// Take the discard card and discard cidx
|
||||
newCard := discard[len(discard)-1]
|
||||
discard = discard[0 : len(discard)-1]
|
||||
|
||||
unwantedCard := hands[currentPlayer][bestDiscardBasedIdx]
|
||||
hands[currentPlayer][bestDiscardBasedIdx] = newCard
|
||||
|
||||
discard = append(discard, unwantedCard) // On top = available for next player
|
||||
|
||||
fmt.Printf("P%d discards %v\n", currentPlayer, unwantedCard)
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// The discard card is not going to make us win or significantly lower our
|
||||
// score
|
||||
// Take from the deck
|
||||
|
||||
newCard := d[0]
|
||||
d = d[1:]
|
||||
hands[currentPlayer] = append(hands[currentPlayer], newCard)
|
||||
|
||||
fmt.Printf("P%d taking %v from the deck\n", currentPlayer, newCard)
|
||||
|
||||
bestRandomBasedIdx := -1
|
||||
bestRandomBasedScore := (round * 50) + 51 // Worse than the worst possible score
|
||||
|
||||
// Look at what we can now drop (hand size + 1 new card). We have to drop something
|
||||
for cidx := 0; cidx < round+1; cidx++ {
|
||||
// Assume we discard that card,
|
||||
theoretically := forkHand(hands[currentPlayer])
|
||||
theoretically[cidx] = Card(-1) // Never matches + worth 0 points
|
||||
_, score := FindBestGrouping(theoretically, round)
|
||||
|
||||
if score < bestRandomBasedScore {
|
||||
// TODO if the points are equal or near-equal, prefer not to discard a wild for the next player
|
||||
bestRandomBasedIdx = cidx
|
||||
bestRandomBasedScore = score
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Discard our chosen card
|
||||
// Slice tricks: Copy end card into this slot and reslice bounds
|
||||
|
||||
unwantedCard := hands[currentPlayer][bestRandomBasedIdx]
|
||||
|
||||
fmt.Printf("P%d discards %v\n", currentPlayer, unwantedCard)
|
||||
|
||||
hands[currentPlayer][bestRandomBasedIdx] = hands[currentPlayer][len(hands[currentPlayer])-1]
|
||||
hands[currentPlayer] = hands[currentPlayer][0 : len(hands[currentPlayer])-1]
|
||||
|
||||
discard = append(discard, unwantedCard) // On top = available for next player
|
||||
|
||||
}
|
||||
|
||||
// Check, one more time, if we have a winning hand
|
||||
|
||||
currentBestGrouping, currentScore := FindBestGrouping(hands[currentPlayer], round)
|
||||
if currentScore == 0 {
|
||||
|
||||
// Declare victory
|
||||
fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n", currentPlayer, hands[currentPlayer], currentBestGrouping)
|
||||
|
||||
roundEndsWhenPlayerIs = currentPlayer
|
||||
}
|
||||
|
||||
// Move to the next player
|
||||
currentPlayer = (currentPlayer + 1) % numPlayers
|
||||
}
|
||||
|
||||
// The round has ended
|
||||
// Figure out what each player scored
|
||||
|
||||
return
|
||||
|
||||
fmt.Printf("The round has ended\n")
|
||||
fmt.Printf("Running total scores: %v\n", runningScores)
|
||||
}
|
||||
|
||||
fmt.Printf("The game has ended\n")
|
||||
}
|
||||
|
||||
type Round struct {
|
||||
d []Card
|
||||
discard []Card
|
||||
round int
|
||||
numPlayers int
|
||||
currentPlayer int
|
||||
hands [][]Card
|
||||
}
|
||||
|
||||
func NewRound(round, numPlayers, currentPlayer int) *Round {
|
||||
|
||||
r := Round{
|
||||
round: round,
|
||||
numPlayers: numPlayers,
|
||||
currentPlayer: currentPlayer,
|
||||
}
|
||||
|
||||
r.d = Deck()
|
||||
Shuffle(r.d)
|
||||
|
||||
r.discard = []Card{}
|
||||
|
||||
fmt.Printf("# Round %d\n\n", r.round)
|
||||
|
||||
// Deal starting cards
|
||||
|
||||
r.hands = make([][]Card, numPlayers)
|
||||
for p := 0; p < numPlayers; p++ {
|
||||
r.hands[p] = r.d[0:round] // Deal from the bottom (0)
|
||||
r.d = r.d[round:]
|
||||
|
||||
fmt.Printf("P%d starting hand: %v\n", p, r.hands[p])
|
||||
}
|
||||
|
||||
// Deal one into the discard pile
|
||||
|
||||
r.discard = append(r.discard, r.d[0])
|
||||
r.d = r.d[1:]
|
||||
|
||||
// Done
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
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] = Card(-1)
|
||||
}
|
||||
cgroups = append(cgroups, cgroup)
|
||||
}
|
||||
|
||||
leftover := []Card{}
|
||||
for _, cv := range tmp {
|
||||
if cv != Card(-1) {
|
||||
leftover = append(leftover, cv)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %v leftover %v ]", cgroups, leftover)
|
||||
}
|
||||
|
||||
func (r *Round) Play() []int {
|
||||
|
||||
fmt.Printf("Discard stack: %v\n", r.discard)
|
||||
|
||||
fmt.Printf("Deck has %d cards remaining\n", len(r.d))
|
||||
|
||||
roundEndsWhenPlayerIs := -1
|
||||
|
||||
for {
|
||||
if roundEndsWhenPlayerIs == r.currentPlayer {
|
||||
break
|
||||
}
|
||||
|
||||
// Play the strategy for the current player
|
||||
r.PlayDefaultStrategy()
|
||||
|
||||
// Check, one more time, if we have a winning hand
|
||||
|
||||
currentBestGrouping, currentScore := FindBestGrouping(r.hands[r.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)
|
||||
|
||||
if roundEndsWhenPlayerIs == -1 {
|
||||
roundEndsWhenPlayerIs = r.currentPlayer
|
||||
} // Otherwise, the round is already ending anyway
|
||||
}
|
||||
|
||||
// Move to the next player
|
||||
r.currentPlayer = (r.currentPlayer + 1) % r.numPlayers
|
||||
}
|
||||
|
||||
// The round has ended
|
||||
// Figure out what each player scored
|
||||
|
||||
roundScores := make([]int, r.numPlayers)
|
||||
|
||||
for p := 0; p < r.numPlayers; p++ {
|
||||
|
||||
gr, score := FindBestGrouping(r.hands[p], r.round)
|
||||
|
||||
fmt.Printf("P%d has %v (score: %d)\n", p, FormatHandGroupings(r.hands[p], gr) /*r.hands[p], gr,*/, score)
|
||||
|
||||
roundScores[p] = score
|
||||
}
|
||||
|
||||
// Check stack alignment
|
||||
if len(r.discard)+len(r.d)+(r.numPlayers*len(r.hands[0])) != DeckSize {
|
||||
panic("Cards on the floor")
|
||||
}
|
||||
|
||||
// Done
|
||||
return roundScores
|
||||
}
|
||||
|
97
search.go
97
search.go
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"fmt"
|
||||
|
||||
"github.com/mxschmitt/golang-combinations"
|
||||
)
|
||||
@ -15,7 +15,11 @@ func MakeBooks(hand []Card, wildFace int) [][]int {
|
||||
for i := 3; i < 14; i++ {
|
||||
book := []int{}
|
||||
for idx, c := range hand {
|
||||
if c.Face() == i || c.Joker() || c.Face() == wildFace {
|
||||
if c.Masked() {
|
||||
continue // Doesn't match anything
|
||||
}
|
||||
|
||||
if c.Joker() || c.Face() == i || c.Face() == wildFace {
|
||||
book = append(book, idx)
|
||||
}
|
||||
}
|
||||
@ -37,64 +41,6 @@ func MakeBooks(hand []Card, wildFace int) [][]int {
|
||||
return pairings
|
||||
}
|
||||
|
||||
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 == search || c.Joker() || c.Face() == wildFace {
|
||||
ret = append(ret, idx)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// take takes a single card out of a hand by its index, returning the remaining cards.
|
||||
func take(hand []Card, index int) (Card, []Card) {
|
||||
cc := hand[index]
|
||||
|
||||
rem := make([]Card, 0, len(hand)-1)
|
||||
rem = append(rem, hand[0:index]...)
|
||||
rem = append(rem, hand[index+1:]...)
|
||||
|
||||
return cc, rem
|
||||
}
|
||||
|
||||
func forkPossibilities(in map[int]struct{}) map[int]struct{} {
|
||||
ret := map[int]struct{}{}
|
||||
for prev, _ := range in {
|
||||
ret[prev] = struct{}{}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func forkHand(hand []Card) []Card {
|
||||
ret := make([]Card, len(hand))
|
||||
copy(ret, hand)
|
||||
return ret
|
||||
}
|
||||
|
||||
func flattenPossibility(in map[int]struct{}) []int {
|
||||
flat := make([]int, 0, len(in))
|
||||
for cidx, _ := range in {
|
||||
flat = append(flat, cidx)
|
||||
}
|
||||
sort.Ints(flat)
|
||||
return flat
|
||||
}
|
||||
|
||||
func continueRun(hand []Card, wildFace int, requireSuit Suit, searchNextFace int, takenIndexes map[int]struct{}) []map[int]struct{} {
|
||||
|
||||
possibilities := []map[int]struct{}{
|
||||
@ -163,7 +109,20 @@ func MakeMultiGroups(hand []Card, wildface int) [][][]int {
|
||||
|
||||
cloneHand := forkHand(hand)
|
||||
for _, midx := range p {
|
||||
cloneHand[midx] = Card(-1) // can't match with anything
|
||||
cloneHand[midx] = NewMasked() // can't match with anything
|
||||
}
|
||||
|
||||
// If that uses up all cards, then early-exit
|
||||
anyUnpaired := false
|
||||
for cidx := 0; cidx < len(cloneHand); cidx++ {
|
||||
if cloneHand[cidx] != Card(-1) {
|
||||
anyUnpaired = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyUnpaired {
|
||||
return [][][]int{[][]int{p}} // Shut out all other possibilities, this is a winner
|
||||
}
|
||||
|
||||
chposs := MakeMultiGroups(cloneHand, wildface)
|
||||
@ -185,30 +144,37 @@ func ScoreAsUnpaired(hand []Card) int {
|
||||
for _, c := range hand {
|
||||
if c.Joker() {
|
||||
score += 50
|
||||
} else if c == Card(-1) {
|
||||
} else if c.Masked() {
|
||||
// skip (already accounted for)
|
||||
} else {
|
||||
score += c.Face()
|
||||
}
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func FindBestGrouping(hand []Card, wildface int) ([][]int, int) {
|
||||
groupings := MakeMultiGroups(hand, wildface)
|
||||
|
||||
fmt.Printf("Found %d possible groupings\n", len(groupings))
|
||||
|
||||
bestScore := 50 * len(hand) // Worst possible score
|
||||
bestGroupIdx := -1
|
||||
for ggidx, grouping := range groupings {
|
||||
|
||||
cloneHand := make([]Card, len(hand))
|
||||
cloneHand := forkHand(hand)
|
||||
for _, group := range grouping {
|
||||
for _, cidx := range group {
|
||||
cloneHand[cidx] = Card(-1) // invalid
|
||||
cloneHand[cidx] = NewMasked() // invalid
|
||||
}
|
||||
}
|
||||
|
||||
score := ScoreAsUnpaired(cloneHand)
|
||||
if score == 0 {
|
||||
return groupings[ggidx], 0 // Early exit
|
||||
}
|
||||
|
||||
if score < bestScore {
|
||||
bestScore = score
|
||||
bestGroupIdx = ggidx
|
||||
@ -217,7 +183,8 @@ func FindBestGrouping(hand []Card, wildface int) ([][]int, int) {
|
||||
}
|
||||
|
||||
if bestGroupIdx == -1 {
|
||||
return [][]int{}, bestScore // No pairings could be found, full points
|
||||
// No pairings could be found, score all remaining cards as ungrouped
|
||||
return [][]int{}, ScoreAsUnpaired(hand)
|
||||
}
|
||||
return groupings[bestGroupIdx], bestScore
|
||||
}
|
||||
|
@ -43,3 +43,15 @@ func TestSolveHand(t *testing.T) {
|
||||
assert.EqualValues(t, [][]int{[]int{0, 4, 5}, []int{1, 2, 3}}, grouping)
|
||||
assert.EqualValues(t, 0, score)
|
||||
}
|
||||
|
||||
func TestScoreHand(t *testing.T) {
|
||||
// P2 has [ [[ J★ 8♥ 8★] [ 9♥ 9♥ 9♠] [ Q♠ Jok Q♠]] leftover [10♣ 4♣] ] (score: 100)
|
||||
|
||||
hand := []Card{15, 20, 25, 30, 16, 17, 55, 60}
|
||||
|
||||
t.Logf("Finding score for hand: %v", hand)
|
||||
|
||||
grouping, score := FindBestGrouping(hand, 10)
|
||||
assert.EqualValues(t, [][]int{[]int{0, 4, 5}, []int{1, 2, 3}}, grouping)
|
||||
assert.EqualValues(t, 23, score)
|
||||
}
|
||||
|
103
strategy.go
Normal file
103
strategy.go
Normal file
@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (r *Round) PlayDefaultStrategy() {
|
||||
|
||||
currentPlayer := r.currentPlayer
|
||||
|
||||
// Score our hand as if we pick up the discard and discard it immediately
|
||||
_, score := FindBestGrouping(r.hands[r.currentPlayer], r.round)
|
||||
baseScore := score
|
||||
bestDiscardBasedIdx := -1
|
||||
bestDiscardBasedScore := score
|
||||
|
||||
if score != 0 {
|
||||
|
||||
// Score our hand as if we pick up the discard card and discard each other card
|
||||
|
||||
for cidx := 0; cidx < r.round; cidx++ {
|
||||
// Pick up the last discard card, and discard card@cidx
|
||||
theoretically := forkHand(r.hands[r.currentPlayer])
|
||||
theoretically[cidx] = r.discard[len(r.discard)-1]
|
||||
_, score := FindBestGrouping(theoretically, r.round)
|
||||
|
||||
if score < bestDiscardBasedScore {
|
||||
// TODO if the points are equal or near-equal, prefer not to discard a wild for the next player
|
||||
bestDiscardBasedIdx = cidx
|
||||
bestDiscardBasedScore = score
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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])
|
||||
|
||||
// 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])
|
||||
|
||||
} 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
|
||||
|
||||
r.discard = append(r.discard, unwantedCard) // On top = available for next player
|
||||
|
||||
fmt.Printf("P%d discards %v (position %d)\n", currentPlayer, unwantedCard, bestDiscardBasedIdx)
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// The discard card is not going to make us win or significantly lower our
|
||||
// score
|
||||
// Take from the deck
|
||||
|
||||
newCard := r.d[0]
|
||||
r.d = r.d[1:]
|
||||
r.hands[r.currentPlayer] = append(r.hands[r.currentPlayer], newCard)
|
||||
|
||||
fmt.Printf("P%d taking %v from the deck\n", r.currentPlayer, newCard)
|
||||
|
||||
bestRandomBasedIdx := -1
|
||||
bestRandomBasedScore := (r.round * 50) + 51 // Worse than the worst possible score
|
||||
|
||||
// Look at what we can now drop (hand size + 1 new card). We have to drop something
|
||||
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
|
||||
_, score := FindBestGrouping(theoretically, r.round)
|
||||
|
||||
if score < bestRandomBasedScore {
|
||||
// TODO if the points are equal or near-equal, prefer not to discard a wild for the next player
|
||||
bestRandomBasedIdx = cidx
|
||||
bestRandomBasedScore = score
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Discard our chosen card
|
||||
// Slice tricks: Copy end card into this slot and reslice bounds
|
||||
|
||||
unwantedCard := r.hands[r.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.discard = append(r.discard, unwantedCard) // On top = available for next player
|
||||
|
||||
}
|
||||
|
||||
}
|
67
util.go
Normal file
67
util.go
Normal file
@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
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 {
|
||||
ret = append(ret, idx)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// take takes a single card out of a hand by its index, returning the remaining cards.
|
||||
func take(hand []Card, index int) (Card, []Card) {
|
||||
cc := hand[index]
|
||||
|
||||
rem := make([]Card, 0, len(hand)-1)
|
||||
rem = append(rem, hand[0:index]...)
|
||||
rem = append(rem, hand[index+1:]...)
|
||||
|
||||
return cc, rem
|
||||
}
|
||||
|
||||
func forkPossibilities(in map[int]struct{}) map[int]struct{} {
|
||||
ret := map[int]struct{}{}
|
||||
for prev, _ := range in {
|
||||
ret[prev] = struct{}{}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func forkHand(hand []Card) []Card {
|
||||
ret := make([]Card, len(hand))
|
||||
copy(ret, hand)
|
||||
return ret
|
||||
}
|
||||
|
||||
func flattenPossibility(in map[int]struct{}) []int {
|
||||
flat := make([]int, 0, len(in))
|
||||
for cidx, _ := range in {
|
||||
flat = append(flat, cidx)
|
||||
}
|
||||
sort.Ints(flat)
|
||||
return flat
|
||||
}
|
Loading…
Reference in New Issue
Block a user