2024-01-07 00:09:47 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-01-07 01:51:06 +00:00
|
|
|
"fmt"
|
2024-01-07 00:09:47 +00:00
|
|
|
|
|
|
|
"github.com/mxschmitt/golang-combinations"
|
|
|
|
)
|
|
|
|
|
|
|
|
func MakeBooks(hand []Card, wildFace int) [][]int {
|
|
|
|
// Return all the possible single books that could be made from this hand.
|
|
|
|
// Caller should call this tree-recursively to find multiple grouping options.
|
|
|
|
pairings := [][]int{}
|
|
|
|
|
|
|
|
// Check for books
|
|
|
|
for i := 3; i < 14; i++ {
|
|
|
|
book := []int{}
|
|
|
|
for idx, c := range hand {
|
2024-01-07 01:51:06 +00:00
|
|
|
if c.Masked() {
|
|
|
|
continue // Doesn't match anything
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Joker() || c.Face() == i || c.Face() == wildFace {
|
2024-01-07 00:09:47 +00:00
|
|
|
book = append(book, idx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(book) == 3 {
|
|
|
|
// That's an option
|
|
|
|
pairings = append(pairings, book)
|
|
|
|
|
|
|
|
} else if len(book) > 3 {
|
|
|
|
// For length over 3, all combinations of this are an option
|
|
|
|
|
|
|
|
for booksz := 3; booksz < len(book)+1; booksz++ {
|
|
|
|
pairings = append(pairings, combinations.Combinations(book, booksz)...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Those are all the pairings we found
|
|
|
|
return pairings
|
|
|
|
}
|
|
|
|
|
2024-01-07 02:33:45 +00:00
|
|
|
func continueRun(hand []Card, wildFace int, requireSuit Suit, searchNextFace int, takenIndexes []int) [][]int {
|
2024-01-07 00:09:47 +00:00
|
|
|
|
2024-01-07 02:33:45 +00:00
|
|
|
possibilities := [][]int{
|
2024-01-07 00:09:47 +00:00
|
|
|
takenIndexes,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do you have any XX|wild
|
2024-01-07 02:33:45 +00:00
|
|
|
posns := search(hand, wildFace, NewCardFrom(searchNextFace, requireSuit))
|
2024-01-07 00:09:47 +00:00
|
|
|
|
|
|
|
// For each match:
|
|
|
|
for _, midx := range posns {
|
|
|
|
|
|
|
|
// Take out that card
|
2024-01-07 02:33:45 +00:00
|
|
|
branchTaken := make([]int, len(takenIndexes)+1)
|
|
|
|
copy(branchTaken, takenIndexes)
|
|
|
|
branchTaken[len(branchTaken)-1] = midx
|
|
|
|
|
|
|
|
branchTakenHand := forkHand(hand)
|
|
|
|
branchTakenHand[midx] = NewMasked()
|
2024-01-07 00:09:47 +00:00
|
|
|
|
|
|
|
// Try and continue the run upwards to the next card, using only the remaining parts of the hand
|
|
|
|
// (We will never find K+1)
|
2024-01-07 02:33:45 +00:00
|
|
|
continuations := continueRun(branchTakenHand, wildFace, requireSuit, searchNextFace+1, branchTaken)
|
2024-01-07 00:09:47 +00:00
|
|
|
|
|
|
|
// We found those possibilities for this run
|
|
|
|
possibilities = append(possibilities, continuations...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return possibilities
|
|
|
|
}
|
|
|
|
|
|
|
|
func MakeRuns(hand []Card, wildFace int) [][]int {
|
|
|
|
pairings := [][]int{}
|
|
|
|
|
|
|
|
for s := 0; s < 5; s++ {
|
|
|
|
|
|
|
|
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
|
2024-01-07 02:33:45 +00:00
|
|
|
found := continueRun(hand, wildFace, Suit(s), f, make([]int, 0))
|
2024-01-07 00:09:47 +00:00
|
|
|
|
|
|
|
// For each possible run we could make starting here:
|
|
|
|
for _, ff := range found {
|
|
|
|
if len(ff) < 3 {
|
|
|
|
continue // Short runs are no good
|
|
|
|
}
|
2024-01-07 02:33:45 +00:00
|
|
|
pairings = append(pairings, ff)
|
2024-01-07 00:09:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pairings
|
|
|
|
}
|
|
|
|
|
|
|
|
func MakeMultiGroups(hand []Card, wildface int) [][][]int {
|
|
|
|
// Depth-first recurse to find all groups in the hand
|
|
|
|
|
|
|
|
ret := [][][]int{}
|
|
|
|
|
|
|
|
poss := [][]int{}
|
|
|
|
poss = append(poss, MakeBooks(hand, wildface)...)
|
|
|
|
poss = append(poss, MakeRuns(hand, wildface)...)
|
|
|
|
|
|
|
|
for _, p := range poss {
|
|
|
|
// One groupup would be: to take that possibility alone
|
|
|
|
ret = append(ret, [][]int{p})
|
|
|
|
|
|
|
|
// Can we do anything more?
|
|
|
|
|
|
|
|
cloneHand := forkHand(hand)
|
|
|
|
for _, midx := range p {
|
2024-01-07 01:51:06 +00:00
|
|
|
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++ {
|
2024-01-07 03:00:20 +00:00
|
|
|
if !cloneHand[cidx].Masked() {
|
2024-01-07 01:51:06 +00:00
|
|
|
anyUnpaired = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !anyUnpaired {
|
|
|
|
return [][][]int{[][]int{p}} // Shut out all other possibilities, this is a winner
|
2024-01-07 00:09:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
chposs := MakeMultiGroups(cloneHand, wildface)
|
|
|
|
// Additional groupups would be: p plus chposs
|
|
|
|
for _, p2 := range chposs {
|
|
|
|
groupup := [][]int{p}
|
|
|
|
groupup = append(groupup, p2...)
|
|
|
|
ret = append(ret, groupup)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func ScoreAsUnpaired(hand []Card) int {
|
|
|
|
score := 0
|
|
|
|
for _, c := range hand {
|
|
|
|
if c.Joker() {
|
|
|
|
score += 50
|
2024-01-07 01:51:06 +00:00
|
|
|
} else if c.Masked() {
|
2024-01-07 00:09:47 +00:00
|
|
|
// skip (already accounted for)
|
|
|
|
} else {
|
|
|
|
score += c.Face()
|
|
|
|
}
|
|
|
|
}
|
2024-01-07 01:51:06 +00:00
|
|
|
|
2024-01-07 00:09:47 +00:00
|
|
|
return score
|
|
|
|
}
|
|
|
|
|
|
|
|
func FindBestGrouping(hand []Card, wildface int) ([][]int, int) {
|
|
|
|
groupings := MakeMultiGroups(hand, wildface)
|
|
|
|
|
2024-01-07 02:33:45 +00:00
|
|
|
if len(groupings) > 1000 {
|
|
|
|
fmt.Printf("Found %d possible groupings\n", len(groupings))
|
|
|
|
}
|
2024-01-07 01:51:06 +00:00
|
|
|
|
2024-01-07 00:09:47 +00:00
|
|
|
bestScore := 50 * len(hand) // Worst possible score
|
|
|
|
bestGroupIdx := -1
|
|
|
|
for ggidx, grouping := range groupings {
|
|
|
|
|
2024-01-07 01:51:06 +00:00
|
|
|
cloneHand := forkHand(hand)
|
2024-01-07 00:09:47 +00:00
|
|
|
for _, group := range grouping {
|
|
|
|
for _, cidx := range group {
|
2024-01-07 01:51:06 +00:00
|
|
|
cloneHand[cidx] = NewMasked() // invalid
|
2024-01-07 00:09:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
score := ScoreAsUnpaired(cloneHand)
|
2024-01-07 01:51:06 +00:00
|
|
|
if score == 0 {
|
|
|
|
return groupings[ggidx], 0 // Early exit
|
|
|
|
}
|
|
|
|
|
2024-01-07 00:09:47 +00:00
|
|
|
if score < bestScore {
|
|
|
|
bestScore = score
|
|
|
|
bestGroupIdx = ggidx
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if bestGroupIdx == -1 {
|
2024-01-07 01:51:06 +00:00
|
|
|
// No pairings could be found, score all remaining cards as ungrouped
|
|
|
|
return [][]int{}, ScoreAsUnpaired(hand)
|
2024-01-07 00:09:47 +00:00
|
|
|
}
|
|
|
|
return groupings[bestGroupIdx], bestScore
|
|
|
|
}
|