go_games_collection/go_bot_test.go
2026-07-07 09:12:09 +03:00

138 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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