go_games_collection/nardy_tui_test.go
2026-07-16 09:53:25 +03:00

304 lines
12 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 (
"fmt"
"strings"
"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)
}
}
func TestNardyViewNamesPlayerColorInWords(t *testing.T) {
mWhite := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
view := mWhite.View()
if !strings.Contains(view, T("color.white")) {
t.Errorf("за белых в подписи должно было явно упоминаться слово 'Белые', получено:\n%s", view)
}
mBlack := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceB)
view = mBlack.View()
if !strings.Contains(view, T("color.black")) {
t.Errorf("за чёрных в подписи должно было явно упоминаться слово 'Чёрные', получено:\n%s", view)
}
}