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 }