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 []int) [][]int { possibilities := [][]int{ takenIndexes, } // Do you have any XX|wild posns := search(hand, wildFace, NewCardFrom(searchNextFace, requireSuit)) // For each match: for _, midx := range posns { // Take out that card 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(branchTakenHand, 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, 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, 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) if len(groupings) > 1000 { 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 }