package main import "testing" func TestDurakModelDealsCorrectPlayerCount(t *testing.T) { for n := 2; n <= 6; n++ { m := NewDurakModel(n) if len(m.game.Players) != n { t.Errorf("ожидалось %d игроков, получено %d", n, len(m.game.Players)) } } } func TestDurakModelClampsPlayerCount(t *testing.T) { m := NewDurakModel(1) if len(m.game.Players) != 2 { t.Errorf("ожидалось ограничение снизу до 2 игроков, получено %d", len(m.game.Players)) } m = NewDurakModel(10) if len(m.game.Players) != 6 { t.Errorf("ожидалось ограничение сверху до 6 игроков, получено %d", len(m.game.Players)) } } func TestDurakModelAutoPassesHumanWithNothingToThrow(t *testing.T) { m := NewDurakModel(3) m.humanIdx = 2 // человек — третий игрок (индекс 2), сам атакующий setupBout(m.game, 2, 1, Spades, map[int][]Card{ 0: {dc(Ten, Diamonds)}, // сторонний бот, роли не играет 1: {dc(Seven, Clubs), dc(Jack, Diamonds)}, // защищающийся 2: {dc(Six, Clubs), dc(Nine, Hearts)}, // атакует шестёркой, девятка не подходит по рангу }) if err := m.game.ThrowIn(dc(Six, Clubs)); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if err := m.game.Defend(dc(Seven, Clubs)); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if m.game.Phase != DurakPhaseThrowIn || m.game.CurrentThrower() != m.humanIdx { t.Fatalf("тест предполагает, что сейчас очередь человека подкидывать (атакующий ходит первым в очереди)") } m.maybeScheduleBot() if m.game.CurrentThrower() == m.humanIdx && m.game.Phase == DurakPhaseThrowIn { t.Errorf("человека без подходящих карт должно было автоматически пропустить (пас), а не оставить ждать ввода") } } func TestDurakModelDoesNotAutoPassHumanWithThrowableCard(t *testing.T) { m := NewDurakModel(3) m.humanIdx = 2 setupBout(m.game, 2, 1, Spades, map[int][]Card{ 0: {dc(Ten, Diamonds)}, 1: {dc(Seven, Clubs), dc(Jack, Diamonds)}, 2: {dc(Six, Clubs), dc(Six, Hearts)}, // вторая шестёрка подходит по рангу }) if err := m.game.ThrowIn(dc(Six, Clubs)); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if err := m.game.Defend(dc(Seven, Clubs)); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if m.game.Phase != DurakPhaseThrowIn || m.game.CurrentThrower() != m.humanIdx { t.Fatalf("тест предполагает, что сейчас очередь человека подкидывать") } m.maybeScheduleBot() if m.game.CurrentThrower() != m.humanIdx || m.game.Phase != DurakPhaseThrowIn { t.Errorf("у человека есть подходящая карта — его очередь не должна была автоматически пропуститься") } } func TestDurakModelThrowInViaKeys(t *testing.T) { m := NewDurakModel(2) setupBout(m.game, m.humanIdx, 1-m.humanIdx, m.game.TrumpSuit, map[int][]Card{ m.humanIdx: {c(Six, Clubs)}, }) m.cursor = 0 next, _ := m.Update(key("enter")) m = next.(DurakModel) if m.isError { t.Fatalf("неожиданная ошибка подкидывания: %s", m.message) } if m.game.Phase != DurakPhaseDefend { t.Fatalf("ожидалась фаза защиты после подкидывания") } if len(m.game.Table) != 1 { t.Fatalf("ожидалась 1 карта на столе") } } func TestDurakModelDefendViaKeys(t *testing.T) { m := NewDurakModel(2) setupBout(m.game, 1-m.humanIdx, m.humanIdx, Spades, map[int][]Card{ 1 - m.humanIdx: {c(Six, Clubs)}, m.humanIdx: {c(Seven, Clubs), c(Ten, Diamonds)}, // запасная карта, чтобы cap>1 }) if err := m.game.ThrowIn(c(Six, Clubs)); err != nil { t.Fatalf("не удалось подготовить стол: %v", err) } m.cursor = 0 next, _ := m.Update(key("enter")) m = next.(DurakModel) if m.isError { t.Fatalf("неожиданная ошибка защиты: %s", m.message) } if len(m.game.Table) == 0 || m.game.Table[0].Defend == nil { t.Fatalf("карта на столе должна быть отбита") } } func TestDurakModelTakeViaKeys(t *testing.T) { m := NewDurakModel(2) setupBout(m.game, 1-m.humanIdx, m.humanIdx, Spades, map[int][]Card{ 1 - m.humanIdx: {c(Eight, Clubs)}, m.humanIdx: {c(Six, Hearts)}, // не бьёт }) if err := m.game.ThrowIn(c(Eight, Clubs)); err != nil { t.Fatalf("не удалось подготовить стол: %v", err) } next, _ := m.Update(key("t")) m = next.(DurakModel) if m.isError { t.Fatalf("неожиданная ошибка взятия: %s", m.message) } if !hasCard(m.game.Players[m.humanIdx].Hand, c(Eight, Clubs)) { t.Fatalf("после взятия карта со стола должна была оказаться в руке") } } func TestDurakModelPassViaKeys(t *testing.T) { m := NewDurakModel(2) // у человека (игрока 0) достаточно карт на руке защищающегося, // чтобы после защиты лимит атаки не был исчерпан и наступила // фаза подкидывания, где единственный потенциальный подкидывающий // (при 2 игроках) — сам атакующий, то есть человек setupBout(m.game, m.humanIdx, 1-m.humanIdx, Spades, map[int][]Card{ m.humanIdx: {c(Six, Clubs), c(Nine, Hearts)}, 1 - m.humanIdx: {c(Seven, Clubs), c(Ten, Diamonds)}, }) if err := m.game.ThrowIn(c(Six, Clubs)); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if err := m.game.Defend(c(Seven, Clubs)); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if m.game.Phase != DurakPhaseThrowIn { t.Fatalf("ожидалась фаза подкидывания, получено %v", m.game.Phase) } next, _ := m.Update(key("p")) m = next.(DurakModel) if m.isError { t.Fatalf("неожиданная ошибка паса: %s", m.message) } if len(m.game.Table) != 0 { t.Fatalf("стол должен был очиститься после единственного паса (2 игрока)") } } func TestDurakModelOtherPlayerThrowInViaKeys(t *testing.T) { // человек — третий игрок (не атакующий и не защищающийся), // проверяем, что именно ему через TUI дают возможность подкинуть m := NewDurakModel(3) humanIdx := m.humanIdx var otherIdx int for i := range m.game.Players { if i != humanIdx { otherIdx = i break } } var thirdIdx int for i := range m.game.Players { if i != humanIdx && i != otherIdx { thirdIdx = i break } } setupBout(m.game, otherIdx, thirdIdx, Spades, map[int][]Card{ otherIdx: {c(Six, Clubs)}, thirdIdx: {c(Seven, Clubs), c(Ten, Diamonds), c(Jack, Diamonds)}, humanIdx: {c(Six, Hearts)}, }) if err := m.game.ThrowIn(c(Six, Clubs)); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if err := m.game.Defend(c(Seven, Clubs)); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if m.game.CurrentThrower() != otherIdx { t.Fatalf("первым в очереди подкидывающих должен быть атакующий раунда") } // атакующий (бот) пасует программно, чтобы дошла очередь до человека if err := m.game.Pass(); err != nil { t.Fatalf("ошибка подготовки паса: %v", err) } if m.game.CurrentThrower() != humanIdx { t.Fatalf("ожидалось, что очередь дошла до человека (%d), получено %d", humanIdx, m.game.CurrentThrower()) } m.cursor = 0 next, _ := m.Update(key("enter")) m = next.(DurakModel) if m.isError { t.Fatalf("неожиданная ошибка подкидывания человеком: %s", m.message) } if len(m.game.Table) != 2 { t.Fatalf("ожидалось 2 карты на столе после подкидывания человеком") } } func TestDurakModelQReturnsToMenu(t *testing.T) { m := NewDurakModel(2) next, _ := m.Update(key("q")) m = next.(DurakModel) if !m.backToMenu { t.Fatalf("ожидался флаг backToMenu после 'q'") } if m.quitting { t.Fatalf("'q' не должен закрывать всё приложение") } } // TestDurakModelFullAutoPlaythrough прогоняет TUI-модель Дурака от // начала до конца, где "человек" отыгрывает свои ходы через // реальные нажатия клавиш (используя ту же логику, что и DurakBot), // а не напрямую через DurakGameState — проверяем весь путь через // Update целиком для нескольких вариантов числа игроков. func TestDurakModelFullAutoPlaythrough(t *testing.T) { for n := 2; n <= 6; n++ { m := NewDurakModel(n) const maxSteps = 10000 steps := 0 for m.game.Phase != DurakPhaseGameOver && steps < maxSteps { steps++ if m.currentActor() != m.humanIdx { next, _ := m.Update(botMoveMsg{}) m = next.(DurakModel) continue } selectCard := func(card Card) { for i, hc := range m.currentHand() { if hc == card { m.cursor = i break } } } switch m.game.Phase { case DurakPhaseAttack: card, _ := m.bot.DecideAttack(m.game) selectCard(card) next, _ := m.Update(key("enter")) m = next.(DurakModel) case DurakPhaseDefend: if card, ok := m.bot.DecideDefend(m.game); ok { selectCard(card) next, _ := m.Update(key("enter")) m = next.(DurakModel) } else { next, _ := m.Update(key("t")) m = next.(DurakModel) } case DurakPhaseThrowIn: if card, ok := m.bot.DecideThrowOrPass(m.game); ok { selectCard(card) next, _ := m.Update(key("enter")) m = next.(DurakModel) } else { next, _ := m.Update(key("p")) m = next.(DurakModel) } } } if steps >= maxSteps { t.Fatalf("игроков %d: партия через TUI не завершилась за %d шагов", n, steps) } if m.game.Result == nil { t.Fatalf("игроков %d: партия завершилась без результата", n) } } }