go_games_collection/durak_tui_test.go
2026-07-04 13:51:04 +03:00

241 lines
8.2 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 "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 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)
}
}
}