289 lines
11 KiB
Go
289 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"testing"
|
||
)
|
||
|
||
func TestNardyModelRollViaKey(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
|
||
next, _ := m.Update(key("r"))
|
||
m = next.(NardyModel)
|
||
if m.game.Phase != NardyPhaseMove && m.game.Phase != NardyPhaseRoll {
|
||
t.Fatalf("после броска фаза должна быть Move (или снова Roll, если ходов не нашлось), получено %v", m.game.Phase)
|
||
}
|
||
if m.game.Dice == [2]int{} {
|
||
t.Errorf("после броска кости не должны оставаться нулевыми")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelApplyMoveViaDigitKey(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
m.game.Roll(3, 4)
|
||
if m.game.CurrentPlayer != NardyWhite {
|
||
t.Fatalf("тест предполагает, что сейчас ход человека")
|
||
}
|
||
groupsBefore := nardySourceGroups(m.game.LegalMovesNow())
|
||
if len(groupsBefore) == 0 {
|
||
t.Fatalf("ожидались легальные ходы после броска 3,4 в начальной позиции")
|
||
}
|
||
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(NardyModel)
|
||
if m.isError {
|
||
t.Fatalf("неожиданная ошибка: %s", m.message)
|
||
}
|
||
// после выбора источника — либо ход сразу применился (если у
|
||
// источника был единственный вариант), либо открылся подрежим
|
||
// выбора кости
|
||
if !m.choosingDie && len(m.game.DiceLeft) != 1 {
|
||
t.Errorf("ожидалось либо применение хода (осталась 1 кость), либо подрежим выбора кости, получено DiceLeft=%v choosingDie=%v", m.game.DiceLeft, m.choosingDie)
|
||
}
|
||
}
|
||
|
||
func TestNardyModelTwoStepDieSelection(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
m.game.Board = [24]NardyPoint{}
|
||
// одна шашка, с которой можно сходить и костью 3, и костью 5 —
|
||
// источник один, но два варианта кости
|
||
m.game.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
m.game.CurrentPlayer = NardyWhite
|
||
m.game.Phase = NardyPhaseMove
|
||
m.game.DiceLeft = []int{3, 5}
|
||
|
||
groups := nardySourceGroups(m.game.LegalMovesNow())
|
||
if len(groups) != 1 || len(groups[0]) != 2 {
|
||
t.Fatalf("тест предполагает один источник с двумя вариантами кости, получено %+v", groups)
|
||
}
|
||
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(NardyModel)
|
||
if !m.choosingDie {
|
||
t.Fatalf("ожидался подрежим выбора кости, раз у источника два варианта")
|
||
}
|
||
if len(m.pendingSourceMoves) != 2 {
|
||
t.Errorf("ожидалось 2 ожидающих варианта кости, получено %d", len(m.pendingSourceMoves))
|
||
}
|
||
|
||
next, _ = m.Update(key("1"))
|
||
m = next.(NardyModel)
|
||
if m.choosingDie {
|
||
t.Errorf("подрежим выбора кости должен был закрыться после выбора")
|
||
}
|
||
if len(m.game.DiceLeft) != 1 {
|
||
t.Errorf("после выбора кости должна была остаться одна неиспользованная, получено %v", m.game.DiceLeft)
|
||
}
|
||
}
|
||
|
||
func TestNardyModelRejectsMoveKeyDuringRollPhase(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(NardyModel)
|
||
if m.game.Phase != NardyPhaseRoll {
|
||
t.Errorf("нажатие цифры в фазе броска не должно было ничего изменить")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelRejectsRollKeyDuringMovePhase(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
m.game.Roll(3, 4)
|
||
diceBefore := m.game.Dice
|
||
|
||
next, _ := m.Update(key("r"))
|
||
m = next.(NardyModel)
|
||
if m.game.Dice != diceBefore {
|
||
t.Errorf("повторный бросок в фазе хода не должен был происходить")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelBotPlaysWhenNotHumanTurn(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyBlack)
|
||
m.humanColor = NardyWhite
|
||
|
||
next, _ := m.Update(botMoveMsg{})
|
||
m = next.(NardyModel)
|
||
if m.game.CurrentPlayer != NardyWhite {
|
||
t.Errorf("после полного хода бота очередь должна была перейти к человеку (белым)")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelIgnoresBotMoveMsgOnHumanTurn(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
|
||
next, _ := m.Update(botMoveMsg{})
|
||
m = next.(NardyModel)
|
||
if m.game.Phase != NardyPhaseRoll {
|
||
t.Errorf("botMoveMsg не должно было ничего менять, пока сейчас ход человека")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelQReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
next, _ := m.Update(key("q"))
|
||
m = next.(NardyModel)
|
||
if !m.backToMenu {
|
||
t.Errorf("ожидался флаг backToMenu после 'q'")
|
||
}
|
||
if m.quitting {
|
||
t.Errorf("'q' не должно завершать приложение целиком")
|
||
}
|
||
}
|
||
|
||
func TestNardyModelActionsIgnoredAfterGameOver(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
m.game = NewNardyGame(NardyShort, NardyWhite)
|
||
m.humanColor = NardyWhite
|
||
m.game.Phase = NardyPhaseOver
|
||
m.game.Result = &NardyResult{Winner: NardyWhite}
|
||
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(NardyModel)
|
||
if m.game.BorneOff[NardyWhite] != 0 {
|
||
t.Errorf("действия после завершения партии не должны приниматься")
|
||
}
|
||
}
|
||
|
||
func TestNardyMoveLabelFormatsCorrectly(t *testing.T) {
|
||
label := nardyMoveLabel(NardyMove{From: 10, Die: 5})
|
||
want := Tf("nardy.move_label", "11", "6", 5)
|
||
if label != want {
|
||
t.Errorf("ожидалась метка %q, получено %q", want, label)
|
||
}
|
||
}
|
||
|
||
func TestNardyMoveLabelBearOff(t *testing.T) {
|
||
label := nardyMoveLabel(NardyMove{From: 3, Die: 4, BearOff: true})
|
||
want := Tf("nardy.move_label", "4", "снятие", 4)
|
||
if label != want {
|
||
t.Errorf("ожидалась метка %q, получено %q", want, label)
|
||
}
|
||
}
|
||
|
||
func TestNardyMoveLabelFromBar(t *testing.T) {
|
||
// вход костью 3: entryRel = 24-3 = 21, традиционный пункт = 22
|
||
label := nardyMoveLabel(NardyMove{FromBar: true, Die: 3})
|
||
want := Tf("nardy.move_label", "с бара", "22", 3)
|
||
if label != want {
|
||
t.Errorf("ожидалась метка %q, получено %q", want, label)
|
||
}
|
||
}
|
||
|
||
func TestNardyModelFullAutoPlaythrough(t *testing.T) {
|
||
for _, variant := range []NardyVariant{NardyShort, NardyLong} {
|
||
m := NewNardyModel(variant, NardyDifficultyMedium, ColorChoiceA)
|
||
humanBot := NardyBot{Difficulty: NardyDifficultyMedium}
|
||
|
||
steps := 0
|
||
for m.game.Phase != NardyPhaseOver && steps < 20000 {
|
||
steps++
|
||
if m.game.CurrentPlayer != m.humanColor {
|
||
next, _ := m.Update(botMoveMsg{})
|
||
m = next.(NardyModel)
|
||
continue
|
||
}
|
||
switch {
|
||
case m.game.Phase == NardyPhaseRoll:
|
||
next, _ := m.Update(key("r"))
|
||
m = next.(NardyModel)
|
||
case m.choosingDie:
|
||
seq := humanBot.DecideSequence(m.game, m.rng)
|
||
if len(seq) == 0 {
|
||
t.Fatalf("бот-имитатор не выбрал ход в подрежиме выбора кости")
|
||
}
|
||
idx := -1
|
||
for i, mv := range m.pendingSourceMoves {
|
||
if mv == seq[0] {
|
||
idx = i
|
||
break
|
||
}
|
||
}
|
||
if idx < 0 {
|
||
// выбранный ботом ход относится к другому источнику —
|
||
// имитатор человека просто берёт первый доступный вариант
|
||
// кости у уже выбранного источника (эквивалентная по
|
||
// корректности проверка сквозного прохождения партии)
|
||
idx = 0
|
||
}
|
||
next, _ := m.Update(key(fmt.Sprintf("%d", idx+1)))
|
||
m = next.(NardyModel)
|
||
if m.isError {
|
||
t.Fatalf("неожиданная ошибка при выборе кости: %s", m.message)
|
||
}
|
||
case m.game.Phase == NardyPhaseMove:
|
||
groups := nardySourceGroups(m.game.LegalMovesNow())
|
||
if len(groups) == 0 {
|
||
t.Fatalf("LegalMovesNow пуст в фазе Move для хода человека — не должно происходить")
|
||
}
|
||
if len(groups) > 9 {
|
||
t.Fatalf("число различных источников хода превысило 9: %d", len(groups))
|
||
}
|
||
seq := humanBot.DecideSequence(m.game, m.rng)
|
||
if len(seq) == 0 {
|
||
t.Fatalf("бот-имитатор человека не выбрал ни одного хода")
|
||
}
|
||
idx := -1
|
||
for i, group := range groups {
|
||
if group[0].FromBar == seq[0].FromBar && group[0].From == seq[0].From {
|
||
idx = i
|
||
break
|
||
}
|
||
}
|
||
if idx < 0 {
|
||
idx = 0 // любой источник корректен для проверки сквозного прохождения
|
||
}
|
||
next, _ := m.Update(key(fmt.Sprintf("%d", idx+1)))
|
||
m = next.(NardyModel)
|
||
if m.isError {
|
||
t.Fatalf("неожиданная ошибка при выборе источника: %s", m.message)
|
||
}
|
||
}
|
||
}
|
||
if m.game.Phase != NardyPhaseOver {
|
||
t.Fatalf("партия (вариант %v) не завершилась за %d шагов", variant, steps)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestNewNardyModelRespectsColorChoiceWhite(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
|
||
if m.humanColor != NardyWhite {
|
||
t.Errorf("при выборе ColorChoiceA человек должен был играть белыми, получено %v", m.humanColor)
|
||
}
|
||
}
|
||
|
||
func TestNewNardyModelRespectsColorChoiceBlack(t *testing.T) {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceB)
|
||
if m.humanColor != NardyBlack {
|
||
t.Errorf("при выборе ColorChoiceB человек должен был играть чёрными, получено %v", m.humanColor)
|
||
}
|
||
}
|
||
|
||
func TestNewNardyModelRandomColorGivesValidColor(t *testing.T) {
|
||
seenWhite, seenBlack := false, false
|
||
for i := 0; i < 50 && !(seenWhite && seenBlack); i++ {
|
||
m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceRandom)
|
||
if m.humanColor == NardyWhite {
|
||
seenWhite = true
|
||
} else {
|
||
seenBlack = true
|
||
}
|
||
}
|
||
if !seenWhite || !seenBlack {
|
||
t.Errorf("случайный выбор цвета должен был давать оба варианта за 50 попыток: белые=%v чёрные=%v", seenWhite, seenBlack)
|
||
}
|
||
}
|