201 lines
6.8 KiB
Go
201 lines
6.8 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|