go_games_collection/nardy_bot_test.go

212 lines
7.7 KiB
Go
Raw Permalink 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"
)
func playNardyFullGame(t *testing.T, g *NardyGameState, botWhite, botBlack NardyBot, rnd *rand.Rand, maxTurns int) {
t.Helper()
for turn := 0; turn < maxTurns && g.Phase != NardyPhaseOver; turn++ {
var bot NardyBot
if g.CurrentPlayer == NardyWhite {
bot = botWhite
} else {
bot = botBlack
}
if err := bot.PlayFullTurn(g, rnd); err != nil {
t.Fatalf("ход %d: неожиданная ошибка бота: %v", turn, err)
}
}
if g.Phase != NardyPhaseOver {
t.Fatalf("партия не завершилась за %d ходов — похоже на зависание. BorneOff=%v Bar=%v", maxTurns, g.BorneOff, g.Bar)
}
}
func TestNardyPipCountBasic(t *testing.T) {
g := NewNardyGame(NardyShort, NardyWhite)
g.Board = [24]NardyPoint{}
g.Board[0] = NardyPoint{Owner: NardyWhite, Count: 1} // rel=0 -> 1 очко
g.Board[5] = NardyPoint{Owner: NardyWhite, Count: 2} // rel=5 -> 6 очков каждая = 12
if got := nardyPipCount(g, NardyWhite); got != 13 {
t.Errorf("ожидался pip count 13 (1+6+6), получено %d", got)
}
}
func TestNardyPipCountIncludesBar(t *testing.T) {
g := NewNardyGame(NardyShort, NardyWhite)
g.Board = [24]NardyPoint{}
g.Bar[NardyWhite] = 1
if got := nardyPipCount(g, NardyWhite); got != 25 {
t.Errorf("шашка на баре должна считаться как 25 очков, получено %d", got)
}
}
func TestMediumBotPrefersHitWhenEquallyExposedOtherwise(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
g := NewNardyGame(NardyShort, NardyWhite)
g.Board = [24]NardyPoint{}
// единственная шашка белых способна пойти либо на открытую
// пустую точку (тоже остаётся одиноким блотом), либо на точку с
// блотом чёрных (тоже остаётся блотом, но со сбитой шашкой
// соперника) — оба варианта одинаково уязвимы сами по себе,
// значит удар должен явно перевешивать по выгоде
g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
g.Board[5] = NardyPoint{Owner: NardyBlack, Count: 1} // блот чёрных, досягаем костью 5
g.CurrentPlayer = NardyWhite
g.Phase = NardyPhaseMove
g.DiceLeft = []int{5}
bot := NardyBot{Difficulty: NardyDifficultyMedium}
seq := bot.DecideSequence(g, rnd)
if len(seq) != 1 || seq[0].From != 10 || seq[0].Die != 5 {
t.Errorf("при прочих равных средний уровень должен был предпочесть удар, получено %+v", seq)
}
}
func TestNardyEvaluateRewardsHittingAllElseEqual(t *testing.T) {
setup := func() *NardyGameState {
g := NewNardyGame(NardyShort, NardyWhite)
g.Board = [24]NardyPoint{}
return g
}
// сценарий А: белая шашка одна на точке 5 (блот) — просто дошла туда без удара
gNoHit := setup()
gNoHit.Board[5] = NardyPoint{Owner: NardyWhite, Count: 1}
scoreNoHit := nardyEvaluate(gNoHit, NardyWhite)
// сценарий Б: та же позиция, но на баре у чёрных сидит сбитая
// шашка (как будто именно она стояла на точке 5 и была сбита) —
// изолированно проверяем эффект наличия чужой шашки на баре
gHit := setup()
gHit.Board[5] = NardyPoint{Owner: NardyWhite, Count: 1}
gHit.Bar[NardyBlack] = 1
scoreHit := nardyEvaluate(gHit, NardyWhite)
if scoreHit <= scoreNoHit {
t.Errorf("наличие шашки соперника на баре должно было увеличивать оценку позиции для белых: без удара=%v, с шашкой соперника на баре=%v", scoreNoHit, scoreHit)
}
}
func TestHardBotSequenceUsesMaximalDice(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
g := NewNardyGame(NardyShort, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyHard}
g.Roll(6, 4)
needed := g.nardyMaxDiceUsableFrom(g.DiceLeft)
seq := bot.DecideSequence(g, rnd)
if len(seq) != needed {
t.Errorf("последовательность жёсткого уровня должна была использовать максимум костей (%d), получено %d ходов: %+v", needed, len(seq), seq)
}
}
func TestEasyBotSequenceOnlyLegalMoves(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
g := NewNardyGame(NardyLong, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyEasy}
g.Roll(3, 5)
seq := bot.DecideSequence(g, rnd)
state := g.cloneForSearch()
for _, mv := range seq {
legal := state.LegalMovesNow()
found := false
for _, l := range legal {
if l == mv {
found = true
}
}
if !found {
t.Fatalf("ход %+v не входит в число легальных на этом шаге: %+v", mv, legal)
}
state.applyMoveRaw(mv)
state.removeDieOnce(mv.Die)
}
}
func TestPlayFullTurnRollsWhenNeeded(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
g := NewNardyGame(NardyShort, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyEasy}
if g.Phase != NardyPhaseRoll {
t.Fatalf("новая партия должна начинаться в фазе броска")
}
if err := bot.PlayFullTurn(g, rnd); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.CurrentPlayer != NardyBlack {
t.Errorf("после полного хода бота очередь должна была перейти к чёрным")
}
}
func TestNardyBotFullGamesShortEasyVsEasy(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 5; i++ {
g := NewNardyGame(NardyShort, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyEasy}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesShortMediumVsMedium(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 5; i++ {
g := NewNardyGame(NardyShort, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyMedium}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesShortHardVsHard(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 3; i++ {
g := NewNardyGame(NardyShort, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyHard}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesLongEasyVsEasy(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 5; i++ {
g := NewNardyGame(NardyLong, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyEasy}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesLongMediumVsMedium(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 5; i++ {
g := NewNardyGame(NardyLong, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyMedium}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesLongHardVsHard(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < 3; i++ {
g := NewNardyGame(NardyLong, NardyWhite)
bot := NardyBot{Difficulty: NardyDifficultyHard}
playNardyFullGame(t, g, bot, bot, rnd, 2000)
}
}
func TestNardyBotFullGamesMixedDifficulties(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
combos := []struct {
white, black NardyDifficulty
}{
{NardyDifficultyEasy, NardyDifficultyMedium},
{NardyDifficultyMedium, NardyDifficultyHard},
{NardyDifficultyEasy, NardyDifficultyHard},
}
for _, variant := range []NardyVariant{NardyShort, NardyLong} {
for _, combo := range combos {
g := NewNardyGame(variant, NardyWhite)
playNardyFullGame(t, g, NardyBot{Difficulty: combo.white}, NardyBot{Difficulty: combo.black}, rnd, 2000)
}
}
}