package main import "sort" // PokerHandCategory — категория покерной комбинации (чем больше, // тем сильнее). type PokerHandCategory int const ( PokerHighCard PokerHandCategory = iota PokerPair PokerTwoPair PokerThreeOfAKind PokerStraight PokerFlush PokerFullHouse PokerFourOfAKind PokerStraightFlush ) // PokerHandValue — сравнимая оценка одной пятикарточной комбинации: // категория плюс до пяти значений для сравнения кикеров, в порядке // значимости (не все пять слотов задействованы каждой категорией — // неиспользуемые остаются нулевыми, что не мешает сравнению, так // как категория и более значимые слоты уже решают дело раньше). type PokerHandValue struct { Category PokerHandCategory Tiebreak [5]int } // ComparePokerHands возвращает 1, если a сильнее b, -1, если b // сильнее a, и 0 при полном равенстве (сплит банка). func ComparePokerHands(a, b PokerHandValue) int { if a.Category != b.Category { if a.Category > b.Category { return 1 } return -1 } for i := 0; i < 5; i++ { if a.Tiebreak[i] != b.Tiebreak[i] { if a.Tiebreak[i] > b.Tiebreak[i] { return 1 } return -1 } } return 0 } // pokerRankValue переводит ранг карты в покерное значение (2-14, // туз всегда старший) — в отличие от Rank.Value(), используемого // для Тонка/Дурака, где туз равен 1. func pokerRankValue(r Rank) int { if r == Ace { return 14 } return int(r) } // EvaluateBest5 перебирает все сочетания по 5 карт из переданных // (обычно 5 или 7 — например, 2 карты в руке + 5 общих) и возвращает // оценку наилучшей возможной комбинации. func EvaluateBest5(cards []Card) PokerHandValue { if len(cards) == 5 { return evaluate5(cards) } best := PokerHandValue{} first := true combinations5(cards, func(combo []Card) { v := evaluate5(combo) if first || ComparePokerHands(v, best) > 0 { best = v first = false } }) return best } // combinations5 вызывает fn для каждого сочетания по 5 карт из cards. func combinations5(cards []Card, fn func(combo []Card)) { n := len(cards) idx := make([]int, 5) for i := range idx { idx[i] = i } for { combo := make([]Card, 5) for i, ix := range idx { combo[i] = cards[ix] } fn(combo) // следующая комбинация индексов (стандартный алгоритм перебора сочетаний) i := 4 for i >= 0 && idx[i] == n-5+i { i-- } if i < 0 { return } idx[i]++ for j := i + 1; j < 5; j++ { idx[j] = idx[j-1] + 1 } } } // evaluate5 оценивает ровно пять карт. func evaluate5(cards []Card) PokerHandValue { values := make([]int, 5) suitCounts := map[Suit]int{} for i, c := range cards { values[i] = pokerRankValue(c.Rank) suitCounts[c.Suit]++ } sort.Sort(sort.Reverse(sort.IntSlice(values))) isFlush := len(suitCounts) == 1 straightHigh, isStraight := detectStraight(values) if isStraight && isFlush { return PokerHandValue{Category: PokerStraightFlush, Tiebreak: [5]int{straightHigh}} } // группируем по количеству повторений ранга countByRank := map[int]int{} for _, v := range values { countByRank[v]++ } type rankCount struct{ rank, count int } var groups []rankCount for r, c := range countByRank { groups = append(groups, rankCount{r, c}) } sort.Slice(groups, func(i, j int) bool { if groups[i].count != groups[j].count { return groups[i].count > groups[j].count } return groups[i].rank > groups[j].rank }) switch { case groups[0].count == 4: kicker := groups[1].rank return PokerHandValue{Category: PokerFourOfAKind, Tiebreak: [5]int{groups[0].rank, kicker}} case groups[0].count == 3 && groups[1].count == 2: return PokerHandValue{Category: PokerFullHouse, Tiebreak: [5]int{groups[0].rank, groups[1].rank}} case isFlush: var tb [5]int copy(tb[:], values) return PokerHandValue{Category: PokerFlush, Tiebreak: tb} case isStraight: return PokerHandValue{Category: PokerStraight, Tiebreak: [5]int{straightHigh}} case groups[0].count == 3: kickers := []int{groups[1].rank, groups[2].rank} return PokerHandValue{Category: PokerThreeOfAKind, Tiebreak: [5]int{groups[0].rank, kickers[0], kickers[1]}} case groups[0].count == 2 && groups[1].count == 2: hiPair, loPair := groups[0].rank, groups[1].rank if loPair > hiPair { hiPair, loPair = loPair, hiPair } return PokerHandValue{Category: PokerTwoPair, Tiebreak: [5]int{hiPair, loPair, groups[2].rank}} case groups[0].count == 2: return PokerHandValue{Category: PokerPair, Tiebreak: [5]int{groups[0].rank, groups[1].rank, groups[2].rank, groups[3].rank}} default: var tb [5]int copy(tb[:], values) return PokerHandValue{Category: PokerHighCard, Tiebreak: tb} } } // detectStraight проверяет пять ОТСОРТИРОВАННЫХ ПО УБЫВАНИЮ // покерных значений на стрит, включая особый случай "колеса" // (A-5-4-3-2, где туз считается младшей картой, а старшая карта // стрита — пятёрка). func detectStraight(sortedDesc []int) (high int, ok bool) { // особый случай колеса: A,5,4,3,2 if sortedDesc[0] == 14 && sortedDesc[1] == 5 && sortedDesc[2] == 4 && sortedDesc[3] == 3 && sortedDesc[4] == 2 { return 5, true } for i := 0; i < 4; i++ { if sortedDesc[i]-sortedDesc[i+1] != 1 { return 0, false } } return sortedDesc[0], true }