go_games_collection/meld.go
2026-07-04 13:51:04 +03:00

178 lines
5.3 KiB
Go
Raw Permalink 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"
// 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
}