package main import ( "math/rand" "testing" ) // klondikeAutoStep пытается совершить один разумный ход: сначала // проверяет, можно ли отправить видимую карту (из отбоя или любой // колонны) на фундамент; если нет — пробует случайный допустимый ход // между колоннами или из отбоя в колонну; если и это невозможно — // добирает из колоды. Возвращает false, если вообще ничего сделать // нельзя (колода и отбой пусты, и нет легальных ходов) — сигнал // закончить прогон. func klondikeAutoStep(g *KlondikeGameState, rnd *rand.Rand) bool { for col := 0; col < 7; col++ { if g.MoveTableauToFoundation(col) == nil { return true } } if g.MoveWasteToFoundation() == nil { return true } order := rnd.Perm(7) for _, from := range order { destOrder := rnd.Perm(7) for _, to := range destOrder { if from == to { continue } if g.MoveTableauToTableau(from, to) == nil { return true } } } for _, to := range rnd.Perm(7) { if g.MoveWasteToTableau(to) == nil { return true } } if g.DrawStock() == nil { return true } return false } func TestKlondikeAutoPlayManyGamesNoCorruption(t *testing.T) { rnd := rand.New(rand.NewSource(1)) for game := 0; game < 200; game++ { g := NewKlondikeGame() const maxSteps = 3000 stalledStreak := 0 for step := 0; step < maxSteps; step++ { progressed := klondikeAutoStep(g, rnd) if !progressed { stalledStreak++ if stalledStreak > 3 { break } } else { stalledStreak = 0 } if g.Won { break } seen := map[Card]bool{} total := 0 checkAdd := func(c Card) { if seen[c] { t.Fatalf("игра %d, шаг %d: карта %v встретилась дважды", game, step, c) } seen[c] = true total++ } for col := 0; col < 7; col++ { for _, kc := range g.Tableau[col] { checkAdd(kc.Card) } } for _, c := range g.Stock { checkAdd(c) } for _, c := range g.Waste { checkAdd(c) } for s, r := range g.Foundations { for rank := 1; rank <= r; rank++ { checkAdd(Card{Suit: Suit(s), Rank: Rank(rank)}) } } if total != 52 { t.Fatalf("игра %d, шаг %d: ожидалось 52 карты в игре, получено %d", game, step, total) } } } }