224 lines
5.1 KiB
Go
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
|
||
|
}
|