212 lines
7.7 KiB
Go
212 lines
7.7 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|