crowns/search.go
2024-01-07 14:51:06 +13:00

191 lines
4.4 KiB
Go

package main
import (
"fmt"
"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 {
if c.Masked() {
continue // Doesn't match anything
}
if c.Joker() || c.Face() == i || c.Face() == wildFace {
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
}
func continueRun(hand []Card, wildFace int, requireSuit Suit, searchNextFace int, takenIndexes map[int]struct{}) []map[int]struct{} {
possibilities := []map[int]struct{}{
takenIndexes,
}
// Do you have any XX|wild
posns := searchExcept(hand, wildFace, NewCardFrom(searchNextFace, requireSuit), takenIndexes)
// For each match:
for _, midx := range posns {
// Take out that card
branchTaken := forkPossibilities(takenIndexes)
branchTaken[midx] = struct{}{}
// 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)
// 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
found := continueRun(hand, wildFace, Suit(s), f, map[int]struct{}{})
// 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))
}
}
}
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 {
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)
// 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
} 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 := forkHand(hand)
for _, group := range grouping {
for _, cidx := range group {
cloneHand[cidx] = NewMasked() // invalid
}
}
score := ScoreAsUnpaired(cloneHand)
if score == 0 {
return groupings[ggidx], 0 // Early exit
}
if score < bestScore {
bestScore = score
bestGroupIdx = ggidx
}
}
if bestGroupIdx == -1 {
// No pairings could be found, score all remaining cards as ungrouped
return [][]int{}, ScoreAsUnpaired(hand)
}
return groupings[bestGroupIdx], bestScore
}