package main import ( "math/rand" "testing" ) // goRandomLegalMove находит случайную легальную точку для текущего // игрока (или сигнализирует, что стоит пасовать, если ничего не // нашлось за разумное число попыток). func goRandomLegalMove(g *GoGameState, rnd *rand.Rand) (GoPos, bool) { size := g.Size order := rnd.Perm(size * size) for _, idx := range order { pos := GoPos{Row: idx / size, Col: idx % size} if g.IsLegal(pos) { return pos, true } } return GoPos{}, false } func TestGoRandomGamesNoCorruption(t *testing.T) { rnd := rand.New(rand.NewSource(1)) for _, size := range []int{9, 13, 19} { for game := 0; game < 10; game++ { g := NewGoGame(size) const maxMoves = 500 moves := 0 for ; moves < maxMoves && g.Result == nil; moves++ { pos, ok := goRandomLegalMove(g, rnd) if !ok { if err := g.Pass(); err != nil { t.Fatalf("размер %d, игра %d: неожиданная ошибка паса: %v", size, game, err) } continue } if err := g.Play(pos); err != nil { t.Fatalf("размер %d, игра %d, ход %d: неожиданная ошибка легального хода %v: %v", size, game, moves, pos, err) } } if g.Result == nil { g.Pass() g.Pass() } if g.Result == nil { t.Fatalf("размер %d, игра %d: партия так и не завершилась", size, game) } if g.Result.BlackScore < 0 || g.Result.WhiteScore < GoKomi { t.Errorf("размер %d, игра %d: подозрительный счёт %+v", size, game, g.Result) } } } } func TestGoLegalMoveEnumerationPerformance19x19(t *testing.T) { g := NewGoGame(19) rnd := rand.New(rand.NewSource(2)) for i := 0; i < 60; i++ { pos, ok := goRandomLegalMove(g, rnd) if !ok { break } if err := g.Play(pos); err != nil { t.Fatalf("шаг %d: неожиданная ошибка: %v", i, err) } } } // TestGoBotFullGames прогоняет несколько полных партий бота против // самого себя на каждом размере доски и уровне сложности, проверяя // отсутствие паник, зависаний и корректность итогового результата. // TestGoPlayedStonePersistsAcrossManyGames — по мотивам реального // наблюдения пользователя ("бот съел свой камень, когда окружил"): // после ЛЮБОГО успешного (не отклонённого как самоубийство или // нарушение ко) хода поставленный камень обязан остаться на доске // своим цветом — если бы он тут же исчез, это означало бы, что либо // сам ход был на самом деле самоубийственным (баг проверки суицида), // либо взятие каким-то образом задело группу самого ходящего вместо // вражеской. Прогоняет много партий бот-против-бота на всех // размерах доски и уровнях сложности, проверяя инвариант после // КАЖДОГО хода. func TestGoPlayedStonePersistsAcrossManyGames(t *testing.T) { rnd := rand.New(rand.NewSource(7)) for _, size := range []int{9, 13, 19} { for _, diff := range []GoDifficulty{GoDifficultyEasy, GoDifficultyMedium, GoDifficultyHard} { bot := GoBot{Difficulty: diff} g := NewGoGame(size) const maxMoves = 300 for move := 0; move < maxMoves && g.Result == nil; move++ { mover := g.Turn pos, ok := bot.DecideMove(g, rnd) if !ok { if err := g.Pass(); err != nil { t.Fatalf("размер %d, сложность %d, ход %d: неожиданная ошибка паса: %v", size, diff, move, err) } continue } if err := g.Play(pos); err != nil { t.Fatalf("размер %d, сложность %d, ход %d: неожиданная ошибка хода в %v: %v", size, diff, move, pos, err) } if g.Board[pos.Row][pos.Col] != mover { t.Fatalf("размер %d, сложность %d, ход %d: после успешного хода цвета %v в точку %v там оказалось %v — камень исчез или сменил цвет сразу после собственного хода", size, diff, move, mover, pos, g.Board[pos.Row][pos.Col]) } } } } } func TestGoBotFullGames(t *testing.T) { rnd := rand.New(rand.NewSource(3)) for _, size := range []int{9, 13, 19} { for _, diff := range []GoDifficulty{GoDifficultyEasy, GoDifficultyMedium, GoDifficultyHard} { bot := GoBot{Difficulty: diff} g := NewGoGame(size) const maxMoves = 800 moves := 0 for ; moves < maxMoves && g.Result == nil; moves++ { if _, err := bot.PlayFullTurn(g, rnd); err != nil { t.Fatalf("размер %d, сложность %d, ход %d: неожиданная ошибка: %v", size, diff, moves, err) } } if g.Result == nil { t.Fatalf("размер %d, сложность %d: партия не завершилась за %d ходов", size, diff, maxMoves) } if g.Result.WhiteScore < GoKomi { t.Errorf("размер %d, сложность %d: подозрительный счёт белых %v (меньше коми)", size, diff, g.Result.WhiteScore) } } } }