go_games_collection/oneohone_tui_test.go
2026-07-10 09:12:06 +03:00

201 lines
6.8 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 (
"fmt"
"strings"
"testing"
)
func TestOneOhOneModelDealsCorrectPlayerCount(t *testing.T) {
for n := 2; n <= 6; n++ {
m := NewOneOhOneModel(n)
if len(m.game.Players) != n {
t.Errorf("ожидалось %d игроков, получено %d", n, len(m.game.Players))
}
}
}
func TestOneOhOneModelClampsPlayerCount(t *testing.T) {
m := NewOneOhOneModel(1)
if len(m.game.Players) != 2 {
t.Errorf("ожидалось ограничение снизу до 2, получено %d", len(m.game.Players))
}
m = NewOneOhOneModel(10)
if len(m.game.Players) != 6 {
t.Errorf("ожидалось ограничение сверху до 6, получено %d", len(m.game.Players))
}
}
func TestOneOhOneModelPlayCardViaKeys(t *testing.T) {
m := NewOneOhOneModel(2)
m.game.Discard = []Card{c(Nine, Clubs)}
m.game.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
m.game.DeclaredSuit = nil
m.game.Players[m.humanIdx].Hand = []Card{c(Ten, Clubs)}
m.game.CurrentPlayerIdx = m.humanIdx
m.game.Phase = OneOhOnePhasePlay
m.cursor = 0
next, _ := m.Update(key("enter"))
m = next.(OneOhOneModel)
if m.isError {
t.Fatalf("неожиданная ошибка: %s", m.message)
}
}
func TestOneOhOneModelPendingEightVisibleInView(t *testing.T) {
m := NewOneOhOneModel(3)
suit := Diamonds
m.game.PendingEightSuit = &suit
view := m.View()
if !strings.Contains(view, suit.String()) {
t.Errorf("требование покрыть восьмёрку должно быть видно на экране, получено:\n%s", view)
}
}
func TestOneOhOneModelNoPendingEightLineWhenNil(t *testing.T) {
m := NewOneOhOneModel(3)
m.game.PendingEightSuit = nil
view := m.View()
if strings.Contains(view, "покрыть восьмёрку") {
t.Errorf("строка требования покрытия не должна показываться, когда PendingEightSuit == nil")
}
}
func TestOneOhOneModelDeclaredSuitVisibleInView(t *testing.T) {
m := NewOneOhOneModel(3)
suit := Hearts
m.game.DeclaredSuit = &suit
view := m.View()
if !strings.Contains(view, suit.String()) {
t.Errorf("объявленная масть должна быть видна на экране, получено:\n%s", view)
}
}
func TestOneOhOneModelNoDeclaredSuitLineWhenNil(t *testing.T) {
m := NewOneOhOneModel(3)
m.game.DeclaredSuit = nil
view := m.View()
if strings.Contains(view, "назвала масть") {
t.Errorf("строка объявленной масти не должна показываться, когда DeclaredSuit == nil")
}
}
func TestOneOhOneModelBotDeclaredSuitVisibleAfterQueen(t *testing.T) {
m := NewOneOhOneModel(3)
// подстраиваем ситуацию: сейчас ход бота, у него на руке дама,
// и сразу после розыгрыша должна открыться фаза выбора масти
botIdx := m.game.CurrentPlayerIdx
if botIdx == m.humanIdx {
botIdx = (botIdx + 1) % len(m.game.Players)
m.game.CurrentPlayerIdx = botIdx
}
m.game.Players[botIdx].Hand = []Card{c(Queen, Spades), c(Two, Clubs)}
m.game.Discard = []Card{c(King, Spades)}
m.game.PendingEightSuit = nil
m.game.DeclaredSuit = nil
next, _ := m.Update(botMoveMsg{})
m = next.(OneOhOneModel)
if m.game.Phase != OneOhOnePhaseChooseSuit {
t.Fatalf("ожидалась фаза выбора масти после розыгрыша дамы, получено %v", m.game.Phase)
}
next, _ = m.Update(botMoveMsg{})
m = next.(OneOhOneModel)
if m.game.DeclaredSuit == nil {
t.Fatalf("бот должен был назвать масть")
}
view := m.View()
if !strings.Contains(view, m.game.DeclaredSuit.String()) {
t.Errorf("названная ботом масть должна быть видна человеку на экране, получено:\n%s", view)
}
}
func TestOneOhOneModelDrawViaKeys(t *testing.T) {
m := NewOneOhOneModel(2)
m.game.Discard = []Card{c(Nine, Clubs)}
m.game.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
m.game.DeclaredSuit = nil
m.game.Players[m.humanIdx].Hand = []Card{c(Ten, Hearts)} // не подходит
m.game.Stock = []Card{c(Two, Diamonds)}
m.game.CurrentPlayerIdx = m.humanIdx
m.game.Phase = OneOhOnePhasePlay
next, _ := m.Update(key("d"))
m = next.(OneOhOneModel)
if m.isError {
t.Fatalf("неожиданная ошибка: %s", m.message)
}
if len(m.game.Players[m.humanIdx].Hand) != 2 {
t.Fatalf("рука должна была вырасти на 1 карту, получено %d карт", len(m.game.Players[m.humanIdx].Hand))
}
}
func TestOneOhOneModelQReturnsToMenu(t *testing.T) {
m := NewOneOhOneModel(2)
next, _ := m.Update(key("q"))
m = next.(OneOhOneModel)
if !m.backToMenu {
t.Fatalf("ожидался флаг backToMenu после 'q'")
}
if m.quitting {
t.Fatalf("'q' не должен закрывать всё приложение")
}
}
// TestOneOhOneModelFullAutoPlaythrough прогоняет TUI-модель от
// начала до конца матча, где "человек" отыгрывает свои ходы через
// реальные нажатия клавиш (используя ту же логику, что и бот).
func TestOneOhOneModelFullAutoPlaythrough(t *testing.T) {
for n := 2; n <= 4; n++ {
m := NewOneOhOneModel(n)
const maxRounds = 100
round := 0
for ; round < maxRounds; round++ {
const maxSteps = 200000
steps := 0
for m.game.Phase != OneOhOnePhaseRoundOver && steps < maxSteps {
steps++
if m.game.CurrentPlayerIdx != m.humanIdx {
next, _ := m.Update(botMoveMsg{})
m = next.(OneOhOneModel)
continue
}
if m.game.Phase == OneOhOnePhaseChooseSuit {
suit := m.bot.DecideSuit(m.game)
next, _ := m.Update(key(fmt.Sprintf("%d", int(suit)+1)))
m = next.(OneOhOneModel)
continue
}
if card, ok := m.bot.DecideAction(m.game); ok {
for i, hc := range m.currentHand() {
if hc == card {
m.cursor = i
break
}
}
next, _ := m.Update(key("enter"))
m = next.(OneOhOneModel)
} else {
next, _ := m.Update(key("d"))
m = next.(OneOhOneModel)
}
}
if steps >= maxSteps {
t.Fatalf("игроков %d, раунд %d: раунд через TUI не завершился за %d шагов", n, round, maxSteps)
}
if m.game.Result == nil {
t.Fatalf("игроков %d, раунд %d: раунд завершился без результата", n, round)
}
if m.game.Result.MatchOver {
break
}
next, _ := m.Update(key("n"))
m = next.(OneOhOneModel)
}
if round >= maxRounds {
t.Fatalf("игроков %d: матч не завершился за %d раундов", n, maxRounds)
}
}
}