package main

import "errors"

// ПРИМЕЧАНИЕ О ПРАВИЛАХ: у "Тысячи" очень много вариантов и
// договорённостей (бочка, золотой кон, самосвал, тузовый марьяж,
// игра втёмную, "расписать игру" и т.д.). Реализован базовый,
// самый распространённый вариант ИМЕННО НА ТРОИХ игроков (у игры
// вдвоём/вчетвером заметно другая механика прикупа и сдающего —
// сознательно не реализована). Набор правил:
//   - Колода 24 карты (9,В,Д,К,10,Т в каждой масти). Старшинство:
//     9 < В < Д < К < 10 < Т (да, десятка сильнее короля, но
//     слабее туза — особенность именно этой игры).
//   - Раздача: по 7 карт каждому, 3 карты — в прикуп.
//   - Торги: первый ходящий (слева от сдающего) обязан назвать
//     не менее 100 очков. Далее по кругу каждый либо поднимает
//     ставку (шаг 5, до 120 без марьяжа на руке, либо до
//     120+стоимость марьяжа, если марьяж есть), либо пасует.
//     Спасовавший больше не участвует в этих торгах. Торги
//     заканчиваются, когда остаётся один игрок — он берёт прикуп.
//   - Победитель торгов забирает прикуп (10 карт на руке), отдаёт
//     по одной карте каждому из двух соперников (по одной,
//     соперникам становится по 8 карт, у него самого остаётся 8) и
//     ходит первым.
//   - Взятки: обязательно ходить в масть; если её нет — козырем
//     (если он уже объявлен); если и его нет — чем угодно. Взятку
//     берёт старшая карта хода, либо старший козырь, если он был
//     подложен.
//   - Марьяж: если у игрока, который сейчас ходит (не в первой
//     взятке раздачи), есть на руке дама и король одной масти, он
//     может объявить марьяж, походив с одной из этих карт — тогда
//     эта масть становится (или меняется на) козырь до конца
//     раздачи, а стоимость марьяжа сразу добавляется к очкам
//     игрока за эту раздачу. Стоимость: пики 40, трефы 60, бубны
//     80, червы 100.
//   - Подсчёт очков по итогам раздачи: если объявивший торг игрок
//     набрал (взятками и марьяжами) не меньше своей ставки — ему
//     засчитывается ставка целиком; если меньше — ставка вычитается
//     из его счёта. Остальные добавляют себе ровно то, что
//     заработали (взятки+марьяжи), без штрафов.
//   - Партия идёт до 1000 очков. "Бочка", "золотой кон",
//     "самосвал", тузовый марьяж и игра "втёмную" — не реализованы.

// ThousandPlayer — состояние одного игрока в партии 1000.
type ThousandPlayer struct {
	Name       string
	Hand       []Card
	IsHuman    bool
	Score      int // суммарный счёт по итогам сыгранных раздач
	HandPoints int // очки ВЗЯТОК (без марьяжей), набранные в ТЕКУЩЕЙ раздаче
	Passed     bool

	// HandMarriagePoints — очки марьяжей (обычных и тузового) за
	// текущую раздачу, отдельно от HandPoints. Разделены специально:
	// удвоение золотого кона и игры "в тёмную" марьяжей не касается —
	// удваиваются только очки взяток (см. resolveHandEnd).
	HandMarriagePoints  int
	AceMarriageDeclared bool // объявлен ли уже тузовый марьяж в текущей раздаче

	// OnBarrel / BarrelAttempts — состояние "бочки" (см. applyBarrelRules).
	OnBarrel       bool
	BarrelAttempts int
}

// ThousandPhase — фаза текущей раздачи.
type ThousandPhase int

const (
	ThousandPhaseBidding ThousandPhase = iota
	ThousandPhaseDiscard
	ThousandPhaseTrick
	ThousandPhaseHandOver
)

var (
	ErrThousandWrongPhase       = errors.New("недопустимое действие для текущей фазы")
	ErrThousandCardNotInHand    = errors.New("карты нет на руке игрока")
	ErrThousandBidTooLow        = errors.New("ставка должна быть выше текущей минимум на 5")
	ErrThousandBlindTooLate     = errors.New("играть втёмную можно только на самом первом ходе торгов")
	ErrThousandBidTooHigh       = errors.New("ставка превышает разрешённый максимум (120 плюс марьяж на руке)")
	ErrThousandMustBid          = errors.New("первый ходящий обязан назвать ставку не менее 100 и не может пасовать")
	ErrThousandInvalidFollow    = errors.New("нужно ходить в масть или козырем, если они есть на руке")
	ErrThousandAlreadyDiscarded = errors.New("уже отдано нужное количество карт")
)

// thousandRankOrder — старшинство рангов в Тысяче: девятка младше
// всех, туз старше всех, но десятка стоит МЕЖДУ королём и тузом.
var thousandRankOrder = map[Rank]int{
	Nine: 0, Jack: 1, Queen: 2, King: 3, Ten: 4, Ace: 5,
}

// thousandMarriageValue — стоимость козырного марьяжа по масти.
var thousandMarriageValue = map[Suit]int{
	Spades: 40, Clubs: 60, Diamonds: 80, Hearts: 100,
}

// NewThousandDeck создаёт колоду для Тысячи: 24 карты (9,В,Д,К,10,Т
// в каждой масти).
func NewThousandDeck() *Deck {
	ranks := []Rank{Nine, Jack, Queen, King, Ten, Ace}
	d := &Deck{cards: make([]Card, 0, 24)}
	for s := Clubs; s <= Spades; s++ {
		for _, r := range ranks {
			d.cards = append(d.cards, Card{Suit: s, Rank: r})
		}
	}
	return d
}

// thousandCardValue — очковая стоимость карты (та же шкала, что и
// в 101: туз 11, десятка 10, король 4, дама 3, валет 2, девятка 0).
func thousandCardValue(r Rank) int {
	return oneOhOneCardValue(r)
}

// hasMarriage проверяет, есть ли на руке король и дама указанной
// масти.
func hasMarriage(hand []Card, suit Suit) bool {
	hasKing, hasQueen := false, false
	for _, c := range hand {
		if c.Suit != suit {
			continue
		}
		if c.Rank == King {
			hasKing = true
		}
		if c.Rank == Queen {
			hasQueen = true
		}
	}
	return hasKing && hasQueen
}

// hasAllAces проверяет, есть ли на руке все четыре туза — условие
// тузового марьяжа.
func hasAllAces(hand []Card) bool {
	found := map[Suit]bool{}
	for _, c := range hand {
		if c.Rank == Ace {
			found[c.Suit] = true
		}
	}
	return len(found) == 4
}

// bestMarriageValue возвращает наибольшую стоимость марьяжа,
// имеющегося на руке (0, если марьяжей нет).
func bestMarriageValue(hand []Card) int {
	best := 0
	for s := Clubs; s <= Spades; s++ {
		if hasMarriage(hand, s) {
			if v := thousandMarriageValue[s]; v > best {
				best = v
			}
		}
	}
	return best
}

type thousandTrickPlay struct {
	PlayerIdx int
	Card      Card
}

// ThousandHandResult — итог одной завершившейся раздачи.
type ThousandHandResult struct {
	DeclarerIdx int
	FinalBid    int
	Made        bool // выполнил ли объявивший торг свою ставку
	ScoreDeltas []int
	MatchOver   bool
	MatchWinner int
}

// ThousandGameState — полное состояние партии в 1000 (ровно на 3
// игроков).
type ThousandGameState struct {
	Players []*ThousandPlayer
	Kitty   []Card

	DealerIdx int
	Phase     ThousandPhase
	Result    *ThousandHandResult

	// --- Торги ---
	CurrentBidderIdx int
	HighBid          int
	HighBidderIdx    int
	firstBidderIdx   int
	bidsMade         int

	// --- Прикуп/снос ---
	DeclarerIdx    int
	FinalBid       int
	discardsGiven  int
	discardTargets []int // получатели 1-й и 2-й отданной карты

	// --- Взятка ---
	TrumpSuit    Suit
	TrumpSet     bool
	LeaderIdx    int
	CurrentTrick []thousandTrickPlay
	TricksPlayed int

	// HandNumber — порядковый номер текущей раздачи за весь матч
	// (считается с 1). Первые ThousandGoldenHands раздач матча —
	// "золотой кон" (см. resolveHandEnd).
	HandNumber int

	// PlayedBlind — текущая раздача объявлена и разыгрывается
	// "в тёмную" (см. DeclareBlind).
	PlayedBlind bool
}

// MatchTarget — партия идёт до этой суммы очков.
const ThousandMatchTarget = 1000

// ПРИМЕЧАНИЕ О НОВЫХ ПРАВИЛАХ: "бочка", "золотой кон", "самосвал",
// "тузовый марьяж" и "игра втёмную" — народные договорённости с
// заметными региональными расхождениями (единого свода правил не
// существует). Здесь выбран один конкретный, наиболее часто
// встречающийся вариант каждой — подробности см. в комментариях
// у соответствующих функций и в тексте правил игры.
const (
	// ThousandBarrelThreshold — при достижении этой суммы очков
	// игрок "садится на бочку" (см. applyBarrelRules).
	ThousandBarrelThreshold = 880
	// ThousandBarrelMaxAttempts — сколько раз подряд сидящий на
	// бочке может провалить свою заявку, прежде чем слетит с неё
	// со штрафом.
	ThousandBarrelMaxAttempts = 3
	// ThousandBarrelPenalty — штраф при неудачном слёте с бочки
	// (по исчерпании попыток или из-за того, что кто-то другой
	// одновременно тоже добрался до бочки).
	ThousandBarrelPenalty = 120

	// ThousandGoldenHands — первые столько раздач матча считаются
	// "золотым коном" (см. resolveHandEnd).
	ThousandGoldenHands = 3

	// ThousandSamosvalThreshold — при выходе счёта за ±это значение
	// счёт обнуляется ("самосвал").
	ThousandSamosvalThreshold = 555

	// ThousandAceMarriageValue — стоимость тузового марьяжа (все
	// четыре туза).
	ThousandAceMarriageValue = 200
)

// NewThousandGame создаёт новую партию на ровно 3 игроков.
func NewThousandGame(names []string, humanIdx int) *ThousandGameState {
	if len(names) != 3 {
		panic("1000 поддерживается только на троих игроков")
	}
	players := make([]*ThousandPlayer, 3)
	for i, name := range names {
		players[i] = &ThousandPlayer{Name: name, IsHuman: i == humanIdx}
	}
	g := &ThousandGameState{Players: players, DealerIdx: 0}
	g.dealNewHand()
	return g
}

func (g *ThousandGameState) dealNewHand() {
	g.HandNumber++
	deck := NewThousandDeck()
	deck.Shuffle()

	for _, p := range g.Players {
		p.Hand = nil
		p.HandPoints = 0
		p.HandMarriagePoints = 0
		p.AceMarriageDeclared = false
		p.Passed = false
	}

	for i := 0; i < 3; i++ {
		idx := (g.DealerIdx + 1 + i) % 3
		g.Players[idx].Hand = append(g.Players[idx].Hand, deck.cards[:7]...)
		deck.cards = deck.cards[7:]
	}
	g.Kitty = append([]Card{}, deck.cards[:3]...)

	g.firstBidderIdx = (g.DealerIdx + 1) % 3
	g.CurrentBidderIdx = g.firstBidderIdx
	g.HighBid = 0
	g.HighBidderIdx = -1
	g.bidsMade = 0
	g.DeclarerIdx = -1
	g.FinalBid = 0
	g.discardsGiven = 0
	g.discardTargets = []int{(g.firstBidderIdx)}
	g.TrumpSet = false
	g.CurrentTrick = nil
	g.TricksPlayed = 0
	g.Result = nil
	g.PlayedBlind = false
	g.Phase = ThousandPhaseBidding
}

func (g *ThousandGameState) current() *ThousandPlayer {
	return g.Players[g.CurrentBidderIdx]
}

func (g *ThousandGameState) maxBidFor(playerIdx int) int {
	hand := g.Players[playerIdx].Hand
	max := 120 + bestMarriageValue(hand)
	if hasAllAces(hand) {
		max += ThousandAceMarriageValue
	}
	return max
}

func (g *ThousandGameState) nextBidder(from int) int {
	for i := 1; i <= 3; i++ {
		idx := (from + i) % 3
		if !g.Players[idx].Passed {
			return idx
		}
	}
	return from
}

func (g *ThousandGameState) activeBidderCount() int {
	n := 0
	for _, p := range g.Players {
		if !p.Passed {
			n++
		}
	}
	return n
}

// Bid — текущий игрок в торгах называет ставку.
// DeclareBlind — первый ходящий в торгах может, не называя обычную
// ставку и не дожидаясь конкурентных торгов, сразу объявить игру
// "в тёмную": фиксированная заявка 120 без торгов. Награда и штраф
// за эту раздачу удваиваются (см. resolveHandEnd), а очки соперников
// от взяток в этой раздаче — тоже удваиваются (марьяжи — нет).
// Доступно только пока никто ещё не сделал ни одной ставки.
func (g *ThousandGameState) DeclareBlind() error {
	if g.Phase != ThousandPhaseBidding {
		return ErrThousandWrongPhase
	}
	if g.bidsMade != 0 || g.CurrentBidderIdx != g.firstBidderIdx {
		return ErrThousandBlindTooLate
	}
	g.PlayedBlind = true
	g.HighBid = 120
	g.HighBidderIdx = g.firstBidderIdx
	g.bidsMade++
	g.finishBidding()
	return nil
}

func (g *ThousandGameState) Bid(amount int) error {
	if g.Phase != ThousandPhaseBidding {
		return ErrThousandWrongPhase
	}
	if amount%5 != 0 {
		return ErrThousandBidTooLow
	}
	minRequired := g.HighBid + 5
	if g.bidsMade == 0 {
		minRequired = 100
	}
	if amount < minRequired {
		return ErrThousandBidTooLow
	}
	if amount > g.maxBidFor(g.CurrentBidderIdx) {
		return ErrThousandBidTooHigh
	}

	g.HighBid = amount
	g.HighBidderIdx = g.CurrentBidderIdx
	g.bidsMade++
	g.advanceOrFinishBidding()
	return nil
}

// Pass — текущий игрок в торгах отказывается от дальнейшего
// участия. Самый первый ходящий обязан назвать ставку и не может
// пасовать.
func (g *ThousandGameState) Pass() error {
	if g.Phase != ThousandPhaseBidding {
		return ErrThousandWrongPhase
	}
	if g.bidsMade == 0 && g.CurrentBidderIdx == g.firstBidderIdx {
		return ErrThousandMustBid
	}
	g.Players[g.CurrentBidderIdx].Passed = true
	g.advanceOrFinishBidding()
	return nil
}

func (g *ThousandGameState) advanceOrFinishBidding() {
	if g.activeBidderCount() <= 1 {
		g.finishBidding()
		return
	}
	g.CurrentBidderIdx = g.nextBidder(g.CurrentBidderIdx)
}

func (g *ThousandGameState) finishBidding() {
	g.DeclarerIdx = g.HighBidderIdx
	g.FinalBid = g.HighBid
	g.Players[g.DeclarerIdx].Hand = append(g.Players[g.DeclarerIdx].Hand, g.Kitty...)
	g.discardTargets = []int{
		(g.DeclarerIdx + 1) % 3,
		(g.DeclarerIdx + 2) % 3,
	}
	g.discardsGiven = 0
	g.Phase = ThousandPhaseDiscard
}

// DiscardCard — объявивший торг игрок (declarer) отдаёт одну карту
// следующему по очереди сопернику. Вызывается дважды подряд.
func (g *ThousandGameState) DiscardCard(card Card) error {
	if g.Phase != ThousandPhaseDiscard {
		return ErrThousandWrongPhase
	}
	if g.discardsGiven >= 2 {
		return ErrThousandAlreadyDiscarded
	}
	p := g.Players[g.DeclarerIdx]
	if !hasCard(p.Hand, card) {
		return ErrThousandCardNotInHand
	}
	p.Hand = removeCard(p.Hand, card)
	target := g.discardTargets[g.discardsGiven]
	g.Players[target].Hand = append(g.Players[target].Hand, card)
	g.discardsGiven++

	if g.discardsGiven == 2 {
		g.LeaderIdx = g.DeclarerIdx
		g.Phase = ThousandPhaseTrick
	}
	return nil
}

func (g *ThousandGameState) ledSuit() (Suit, bool) {
	if len(g.CurrentTrick) == 0 {
		return 0, false
	}
	return g.CurrentTrick[0].Card.Suit, true
}

func containsSuit(hand []Card, suit Suit) bool {
	for _, c := range hand {
		if c.Suit == suit {
			return true
		}
	}
	return false
}

// isValidPlay проверяет, разрешено ли сыграть card в текущей
// взятке для игрока с рукой hand.
func (g *ThousandGameState) isValidPlay(hand []Card, card Card) bool {
	led, ok := g.ledSuit()
	if !ok {
		return true // ходит первым — любая карта разрешена (кроме марьяжа см. отдельно)
	}
	if containsSuit(hand, led) {
		return card.Suit == led
	}
	if g.TrumpSet && containsSuit(hand, g.TrumpSuit) {
		return card.Suit == g.TrumpSuit
	}
	return true
}

// PlayCard — текущий игрок в раскладе взятки играет карту. Если
// это первая карта взятки (игрок ходит), король или дама масти, по
// которой на руке есть парная карта, и это не самая первая взятка
// раздачи — автоматически объявляется марьяж (козырь меняется на
// эту масть, очки марьяжа сразу начисляются).
func (g *ThousandGameState) PlayCard(card Card) error {
	if g.Phase != ThousandPhaseTrick {
		return ErrThousandWrongPhase
	}
	actor := g.currentTrickActor()
	p := g.Players[actor]
	if !hasCard(p.Hand, card) {
		return ErrThousandCardNotInHand
	}

	leading := len(g.CurrentTrick) == 0
	if !leading {
		if !g.isValidPlay(p.Hand, card) {
			return ErrThousandInvalidFollow
		}
	} else if (card.Rank == King || card.Rank == Queen) && g.TricksPlayed > 0 && hasMarriage(p.Hand, card.Suit) {
		p.HandMarriagePoints += thousandMarriageValue[card.Suit]
		g.TrumpSuit = card.Suit
		g.TrumpSet = true
	} else if card.Rank == Ace && g.TricksPlayed > 0 && !p.AceMarriageDeclared && hasAllAces(p.Hand) {
		// Тузовый марьяж: игрок, уже взявший хотя бы одну взятку (не
		// на первом ходе раздачи — как и обычный марьяж), заходит с
		// туза, имея на руках все четыре. Козырь при этом НЕ
		// меняется (в отличие от обычного марьяжа) — это отдельный
		// вариант правил, региональные соглашения расходятся в том,
		// должен ли последний туз тузового марьяжа тоже назначать
		// козырь; здесь выбран более простой и чаще встречающийся
		// вариант без смены козыря.
		p.HandMarriagePoints += ThousandAceMarriageValue
		p.AceMarriageDeclared = true
	}

	p.Hand = removeCard(p.Hand, card)
	g.CurrentTrick = append(g.CurrentTrick, thousandTrickPlay{PlayerIdx: actor, Card: card})

	if len(g.CurrentTrick) == 3 {
		g.resolveTrick()
	}
	return nil
}

// currentTrickActor возвращает индекс игрока, чья очередь класть
// карту в текущей взятке.
func (g *ThousandGameState) currentTrickActor() int {
	return (g.LeaderIdx + len(g.CurrentTrick)) % 3
}

func (g *ThousandGameState) resolveTrick() {
	led := g.CurrentTrick[0].Card.Suit
	best := g.CurrentTrick[0]
	bestIsTrump := g.TrumpSet && best.Card.Suit == g.TrumpSuit

	for _, play := range g.CurrentTrick[1:] {
		isTrump := g.TrumpSet && play.Card.Suit == g.TrumpSuit
		switch {
		case isTrump && !bestIsTrump:
			best = play
			bestIsTrump = true
		case isTrump == bestIsTrump && play.Card.Suit == best.Card.Suit && thousandRankOrder[play.Card.Rank] > thousandRankOrder[best.Card.Rank]:
			best = play
		case !isTrump && !bestIsTrump && play.Card.Suit == led && best.Card.Suit != led:
			best = play
		}
	}

	points := 0
	for _, play := range g.CurrentTrick {
		points += thousandCardValue(play.Card.Rank)
	}
	g.Players[best.PlayerIdx].HandPoints += points

	g.LeaderIdx = best.PlayerIdx
	g.CurrentTrick = nil
	g.TricksPlayed++

	if g.TricksPlayed == 8 {
		g.resolveHandEnd()
	}
}

func (g *ThousandGameState) resolveHandEnd() {
	declarer := g.Players[g.DeclarerIdx]
	declarerTotal := declarer.HandPoints + declarer.HandMarriagePoints
	made := declarerTotal >= g.FinalBid

	deltas := make([]int, 3)

	declarerDelta := g.FinalBid
	if !made {
		declarerDelta = -g.FinalBid
	}
	if g.PlayedBlind {
		declarerDelta *= 2 // удвоенная награда/штраф за игру "в тёмную"
	}
	deltas[g.DeclarerIdx] = declarerDelta

	for i, p := range g.Players {
		if i == g.DeclarerIdx {
			continue
		}
		multiplier := 1
		if g.HandNumber <= ThousandGoldenHands {
			multiplier *= 2 // золотой кон — первые несколько раздач матча
		}
		if g.PlayedBlind {
			multiplier *= 2 // очки соперников удваиваются и при игре втёмную
		}
		// марьяжи никогда не удваиваются — ни золотым коном, ни тёмной
		deltas[i] = p.HandPoints*multiplier + p.HandMarriagePoints
	}

	g.applyBarrelAdjustments(deltas, made)

	for i, p := range g.Players {
		p.Score += deltas[i]
	}

	g.applySamosval()
	g.updateBarrelSeating()

	result := &ThousandHandResult{
		DeclarerIdx: g.DeclarerIdx,
		FinalBid:    g.FinalBid,
		Made:        made,
		ScoreDeltas: deltas,
	}

	bestIdx := -1
	for i, p := range g.Players {
		if p.Score >= ThousandMatchTarget {
			if bestIdx == -1 || p.Score > g.Players[bestIdx].Score {
				bestIdx = i
			}
		}
	}
	if bestIdx != -1 {
		result.MatchOver = true
		result.MatchWinner = bestIdx
	}

	g.Result = result
	g.Phase = ThousandPhaseHandOver
}

// applyBarrelAdjustments переписывает уже посчитанные "обычные"
// дельты для игроков, сидящих на бочке (см. ThousandBarrelThreshold):
//   - объявивший игру и сидящий на бочке при провале своей заявки не
//     теряет FinalBid обычным образом — вместо этого засчитывается
//     попытка; после ThousandBarrelMaxAttempts подряд неудачных
//     попыток он слетает с бочки со штрафом ThousandBarrelPenalty;
//   - сидящий на бочке, но не объявлявший игру в этой раздаче, вообще
//     не получает очков от чужих взяток — сидя на бочке, можно расти
//     только через собственную выполненную заявку.
func (g *ThousandGameState) applyBarrelAdjustments(deltas []int, made bool) {
	for i, p := range g.Players {
		if !p.OnBarrel {
			continue
		}
		if i == g.DeclarerIdx {
			if made {
				continue // обычная (уже посчитанная) прибавка верна как есть
			}
			p.BarrelAttempts++
			if p.BarrelAttempts >= ThousandBarrelMaxAttempts {
				deltas[i] = -ThousandBarrelPenalty
				p.OnBarrel = false
				p.BarrelAttempts = 0
			} else {
				deltas[i] = 0 // попытка провалена, но лимит ещё не исчерпан — счёт не меняется
			}
		} else {
			deltas[i] = 0
		}
	}
}

// applySamosval обнуляет счёт игрока, если по итогам раздачи он
// оказался ТОЧНО равен ±ThousandSamosvalThreshold — это редкое
// нумерологическое совпадение ("555"), а не общий потолок счёта;
// если бы обнуление срабатывало при ЛЮБОМ превышении, партия
// никогда не смогла бы дойти до цели в 1000 очков.
func (g *ThousandGameState) applySamosval() {
	for _, p := range g.Players {
		if p.Score == ThousandSamosvalThreshold || p.Score == -ThousandSamosvalThreshold {
			p.Score = 0
			p.OnBarrel = false
			p.BarrelAttempts = 0
		}
	}
}

// updateBarrelSeating сажает на бочку игрока, чей счёт по итогам
// этой раздачи впервые достиг ThousandBarrelThreshold (но ещё не
// выиграл матч). Одновременно на бочке может сидеть только один
// игрок — если садится новый, прежний слетает со штрафом.
func (g *ThousandGameState) updateBarrelSeating() {
	for i, p := range g.Players {
		if p.OnBarrel || p.Score < ThousandBarrelThreshold || p.Score >= ThousandMatchTarget {
			continue
		}
		for j, other := range g.Players {
			if j != i && other.OnBarrel {
				other.OnBarrel = false
				other.BarrelAttempts = 0
				other.Score -= ThousandBarrelPenalty
			}
		}
		p.OnBarrel = true
		p.BarrelAttempts = 0
	}
}

// NextHand начинает новую раздачу (сдающий переходит по кругу),
// сохраняя счёт игроков.
func (g *ThousandGameState) NextHand() {
	if g.Result != nil && g.Result.MatchOver {
		return
	}
	g.DealerIdx = (g.DealerIdx + 1) % 3
	g.dealNewHand()
}
