package main import ( "fmt" "testing" ) func TestNardyModelRollViaKey(t *testing.T) { m := NewNardyModel(NardyShort, NardyDifficultyEasy) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) } } }