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 }