crowns/search.go
2024-01-07 13:09:47 +13:00

224 lines
5.1 KiB
Go

package main
import (
"sort"
"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.Face() == i || c.Joker() || 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 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{}{
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] = Card(-1) // can't match with anything
}
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 == Card(-1) {
// skip (already accounted for)
} else {
score += c.Face()
}
}
return score
}
func FindBestGrouping(hand []Card, wildface int) ([][]int, int) {
groupings := MakeMultiGroups(hand, wildface)
bestScore := 50 * len(hand) // Worst possible score
bestGroupIdx := -1
for ggidx, grouping := range groupings {
cloneHand := make([]Card, len(hand))
for _, group := range grouping {
for _, cidx := range group {
cloneHand[cidx] = Card(-1) // invalid
}
}
score := ScoreAsUnpaired(cloneHand)
if score < bestScore {
bestScore = score
bestGroupIdx = ggidx
}
}
if bestGroupIdx == -1 {
return [][]int{}, bestScore // No pairings could be found, full points
}
return groupings[bestGroupIdx], bestScore
}