go_games_collection/poker_hand.go
2026-07-10 09:12:06 +03:00

188 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}