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)
			}
		}
	}
}
