99 lines
4.3 KiB
Go
99 lines
4.3 KiB
Go
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
|
||
}
|
||
}
|
||
}
|
||
}
|