package main import "testing" func TestStalemateProtectionTerminatesGame(t *testing.T) { g := NewDurakGame([]string{"A", "B"}, -1) g.Talon = nil g.TrumpSuit = Spades g.AttackerIdx = 0 g.DefenderIdx = 1 // у обоих всегда есть по карте, а защищающийся никогда не может // отбиться (у него всегда некозырная карта младше атакующей) — // имитируем принудительное "взятие" много раз подряд напрямую for i := 0; i < maxTakesWithoutBito+5 && g.Phase != DurakPhaseGameOver; i++ { g.Players[0].Hand = []Card{c(Eight, Clubs), c(Nine, Diamonds)} // запасная карта, чтобы не выбыть g.Players[1].Hand = []Card{c(Six, Hearts)} g.attackCap = g.currentAttackCap() g.Phase = DurakPhaseAttack if err := g.ThrowIn(c(Eight, Clubs)); err != nil { t.Fatalf("шаг %d: неожиданная ошибка подкидывания: %v", i, err) } if err := g.Take(); err != nil { t.Fatalf("шаг %d: неожиданная ошибка взятия: %v", i, err) } if g.Phase != DurakPhaseGameOver { // перед следующей итерацией восстанавливаем роли и стол // вручную — в реальной игре это происходит по правилам g.AttackerIdx, g.DefenderIdx = 0, 1 } } if g.Phase != DurakPhaseGameOver { t.Fatalf("партия должна была принудительно завершиться из-за защиты от зацикливания") } if g.Result == nil || !g.Result.Stalemate { t.Fatalf("ожидался результат Stalemate, получено: %+v", g.Result) } } // TestDurakBotFullGames прогоняет много полных партий силами // DurakBot для разного числа игроков (2-6) и проверяет, что игра // всегда корректно завершается: без паники, без зависания, и с // валидным результатом (ровно один проигравший либо ничья). func TestDurakBotFullGames(t *testing.T) { bot := DurakBot{} for numPlayers := 2; numPlayers <= 6; numPlayers++ { for game := 0; game < 30; game++ { names := make([]string, numPlayers) for i := range names { names[i] = "Bot" } g := NewDurakGame(names, -1) const maxSteps = 10000 steps := 0 for ; steps < maxSteps && g.Phase != DurakPhaseGameOver; steps++ { if err := bot.PlayFullTurn(g); err != nil { t.Fatalf("игроков %d, игра %d, шаг %d: ошибка бота: %v", numPlayers, game, steps, err) } } if steps >= maxSteps { t.Fatalf("игроков %d, игра %d: партия не завершилась за %d шагов (возможное зависание)", numPlayers, game, maxSteps) } if g.Result == nil { t.Fatalf("игроков %d, игра %d: партия завершилась без результата", numPlayers, game) } if !g.Result.NoLoser && !g.Result.Stalemate { if g.Result.Loser < 0 || g.Result.Loser >= numPlayers { t.Fatalf("игроков %d, игра %d: некорректный индекс проигравшего %d", numPlayers, game, g.Result.Loser) } } // суммарно карты не должны теряться и не должны // дублироваться: в талоне + на руках должно остаться // ровно то, что не ушло в "бито" (отбой). Проверяем // более простой инвариант — отсутствие дубликатов среди // всех карт, которые сейчас на руках и в талоне. seen := map[Card]bool{} for _, p := range g.Players { for _, c := range p.Hand { if seen[c] { t.Fatalf("игроков %d, игра %d: карта %v встретилась дважды", numPlayers, game, c) } seen[c] = true } } for _, c := range g.Talon { if seen[c] { t.Fatalf("игроков %d, игра %d: карта %v встретилась дважды (в талоне и на руке)", numPlayers, game, c) } seen[c] = true } } } }