go_games_collection/durak_tui_test.go

291 lines
11 KiB
Go
Raw Permalink 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 "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)
}
}
}