178 lines
5.3 KiB
Go
178 lines
5.3 KiB
Go
package main
|
||
|
||
import "sort"
|
||
|
||
// IsSet проверяет, что переданные карты образуют валидный сет:
|
||
// 3 и более карты одного ранга, все с разными мастями (иначе была
|
||
// бы дублирующаяся карта, что невозможно в одной колоде, но
|
||
// проверка на всякий случай — например, при игре с несколькими
|
||
// колодами).
|
||
func IsSet(cards []Card) bool {
|
||
if len(cards) < 3 {
|
||
return false
|
||
}
|
||
rank := cards[0].Rank
|
||
seenSuits := make(map[Suit]bool, len(cards))
|
||
for _, c := range cards {
|
||
if c.Rank != rank {
|
||
return false
|
||
}
|
||
if seenSuits[c.Suit] {
|
||
return false
|
||
}
|
||
seenSuits[c.Suit] = true
|
||
}
|
||
return true
|
||
}
|
||
|
||
// IsSequence проверяет, что карты образуют валидный сиквенс:
|
||
// 3 и более карты одной масти подряд по рангу. Туз может считаться
|
||
// низким (A-2-3) или высоким (Q-K-A), но не одновременно и то, и
|
||
// другое (K-A-2 невалиден).
|
||
func IsSequence(cards []Card) bool {
|
||
if len(cards) < 3 {
|
||
return false
|
||
}
|
||
suit := cards[0].Suit
|
||
ranks := make([]int, 0, len(cards))
|
||
for _, c := range cards {
|
||
if c.Suit != suit {
|
||
return false
|
||
}
|
||
ranks = append(ranks, int(c.Rank))
|
||
}
|
||
sort.Ints(ranks)
|
||
|
||
// нет дубликатов рангов
|
||
for i := 1; i < len(ranks); i++ {
|
||
if ranks[i] == ranks[i-1] {
|
||
return false
|
||
}
|
||
}
|
||
|
||
if isConsecutive(ranks) {
|
||
return true
|
||
}
|
||
|
||
// вариант с тузом как старшей картой: заменяем 1 (Ace) на 14
|
||
// и пробуем снова.
|
||
if ranks[0] == int(Ace) {
|
||
aceHigh := make([]int, len(ranks))
|
||
copy(aceHigh, ranks[1:])
|
||
aceHigh[len(aceHigh)-1] = 14
|
||
sort.Ints(aceHigh)
|
||
return isConsecutive(aceHigh)
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
func isConsecutive(sortedRanks []int) bool {
|
||
for i := 1; i < len(sortedRanks); i++ {
|
||
if sortedRanks[i] != sortedRanks[i-1]+1 {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
// FindAllSets группирует карты руки по рангу и возвращает те
|
||
// группы, что образуют валидный сет (3+ карты).
|
||
func FindAllSets(hand []Card) [][]Card {
|
||
byRank := make(map[Rank][]Card)
|
||
for _, c := range hand {
|
||
byRank[c.Rank] = append(byRank[c.Rank], c)
|
||
}
|
||
var sets [][]Card
|
||
for _, group := range byRank {
|
||
if IsSet(group) {
|
||
sets = append(sets, group)
|
||
}
|
||
}
|
||
return sets
|
||
}
|
||
|
||
// FindAllSequences группирует карты руки по масти, находит все
|
||
// максимальные последовательности подряд идущих рангов длиной 3+
|
||
// и возвращает их. Учитывает и туз-снизу, и туз-сверху отдельно,
|
||
// если рука позволяет оба варианта (сама карта туза при этом
|
||
// используется только один раз в каждом найденном варианте).
|
||
func FindAllSequences(hand []Card) [][]Card {
|
||
bySuit := make(map[Suit][]Card)
|
||
for _, c := range hand {
|
||
bySuit[c.Suit] = append(bySuit[c.Suit], c)
|
||
}
|
||
|
||
var result [][]Card
|
||
for _, cards := range bySuit {
|
||
result = append(result, findRunsInSuit(cards)...)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// findRunsInSuit находит все максимальные подряд идущие отрезки
|
||
// (длиной 3+) внутри одной масти, включая отдельную попытку с
|
||
// тузом как старшей картой.
|
||
func findRunsInSuit(cards []Card) [][]Card {
|
||
byRank := make(map[int]Card)
|
||
for _, c := range cards {
|
||
byRank[int(c.Rank)] = c
|
||
}
|
||
|
||
var runs [][]Card
|
||
runs = append(runs, extractRuns(byRank, 1, 13, false)...)
|
||
|
||
if _, hasAce := byRank[int(Ace)]; hasAce {
|
||
runs = append(runs, extractRuns(byRank, 2, 14, true)...)
|
||
}
|
||
return runs
|
||
}
|
||
|
||
// extractRuns проходит по диапазону [lo, hi] и собирает
|
||
// максимальные подряд идущие последовательности длиной 3+.
|
||
// Если aceHigh=true, ранг 14 интерпретируется как туз.
|
||
func extractRuns(byRank map[int]Card, lo, hi int, aceHigh bool) [][]Card {
|
||
var runs [][]Card
|
||
var current []Card
|
||
|
||
flush := func() {
|
||
if len(current) >= 3 {
|
||
runCopy := make([]Card, len(current))
|
||
copy(runCopy, current)
|
||
runs = append(runs, runCopy)
|
||
}
|
||
current = nil
|
||
}
|
||
|
||
for r := lo; r <= hi; r++ {
|
||
rank := r
|
||
if aceHigh && r == 14 {
|
||
rank = int(Ace)
|
||
}
|
||
c, ok := byRank[rank]
|
||
if ok {
|
||
current = append(current, c)
|
||
} else {
|
||
flush()
|
||
}
|
||
}
|
||
flush()
|
||
return runs
|
||
}
|
||
|
||
// HandValue возвращает суммарную очковую стоимость карт на руке —
|
||
// используется и для расчёта штрафа проигравшего, и для проверки
|
||
// возможности объявить Тонк.
|
||
func HandValue(hand []Card) int {
|
||
total := 0
|
||
for _, c := range hand {
|
||
total += c.Value()
|
||
}
|
||
return total
|
||
}
|
||
|
||
// CanDeclareTonk проверяет, можно ли объявить Тонк при данной руке:
|
||
// суммарная стоимость карт должна быть не больше threshold.
|
||
func CanDeclareTonk(hand []Card, threshold int) bool {
|
||
return HandValue(hand) <= threshold
|
||
}
|