package main import ( "strings" "testing" ) func TestBlackjackModelDealsCorrectPlayerCount(t *testing.T) { for n := 1; n <= 6; n++ { m := NewBlackjackModel(n, 100, 10) if len(m.game.Players) != n { t.Errorf("ожидалось %d игроков, получено %d", n, len(m.game.Players)) } } } func TestBlackjackModelClampsPlayerCount(t *testing.T) { m := NewBlackjackModel(0, 100, 10) if len(m.game.Players) != 1 { t.Errorf("ожидалось ограничение снизу до 1 игрока, получено %d", len(m.game.Players)) } m = NewBlackjackModel(10, 100, 10) if len(m.game.Players) != 6 { t.Errorf("ожидалось ограничение сверху до 6 игроков, получено %d", len(m.game.Players)) } } func TestBlackjackModelHitViaKeys(t *testing.T) { m := NewBlackjackModel(1, 100, 10) resetForManualBout(m.game) m.game.Players[0].Hand = []Card{c(King, Clubs), c(Queen, Hearts)} m.game.CurrentPlayerIdx = 0 m.game.Phase = BJPhasePlayerTurn m.game.Deck = &Deck{cards: []Card{c(Five, Diamonds)}} next, _ := m.Update(key("h")) m = next.(BlackjackModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if m.game.Players[0].Status != BJStatusBusted { t.Fatalf("ожидался перебор, статус: %v", m.game.Players[0].Status) } } func TestBlackjackModelStandViaKeys(t *testing.T) { m := NewBlackjackModel(1, 100, 10) resetForManualBout(m.game) m.game.CurrentPlayerIdx = 0 m.game.Phase = BJPhasePlayerTurn next, _ := m.Update(key("s")) m = next.(BlackjackModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if m.game.Phase != BJPhaseRoundOver { t.Fatalf("с одним игроком после stand раунд должен был сразу завершиться") } } func TestBlackjackModelDoubleViaKeys(t *testing.T) { m := NewBlackjackModel(1, 100, 10) resetForManualBout(m.game) m.game.Players[0].Hand = []Card{c(Five, Clubs), c(Six, Hearts)} m.game.CurrentPlayerIdx = 0 m.game.Phase = BJPhasePlayerTurn m.game.Deck = &Deck{cards: []Card{c(King, Diamonds)}} next, _ := m.Update(key("d")) m = next.(BlackjackModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if m.game.Players[0].Bet != 20 { t.Fatalf("ставка должна была удвоиться, получено %d", m.game.Players[0].Bet) } } func TestBlackjackModelQReturnsToMenu(t *testing.T) { m := NewBlackjackModel(2, 100, 10) next, _ := m.Update(key("q")) m = next.(BlackjackModel) if !m.backToMenu { t.Fatalf("ожидался флаг backToMenu после 'q'") } if m.quitting { t.Fatalf("'q' не должен закрывать всё приложение") } } func TestBlackjackModelNewRoundCarriesBalance(t *testing.T) { m := NewBlackjackModel(1, 100, 10) resetForManualBout(m.game) m.game.CurrentPlayerIdx = 0 m.game.Phase = BJPhasePlayerTurn next, _ := m.Update(key("s")) m = next.(BlackjackModel) if m.game.Phase != BJPhaseRoundOver { t.Fatalf("раунд должен был завершиться") } balanceAfterRound := m.game.Players[0].Balance next, _ = m.Update(key("n")) m = next.(BlackjackModel) // с одним игроком новый раунд может мгновенно разрешиться сам, // если случайно выпадет блэкджек с раздачи — это ожидаемое // поведение, а не баг, поэтому проверяем именно перенесённый // капитал (preRoundBalance), а не итоговый Balance if m.game.preRoundBalance[0] != balanceAfterRound { t.Fatalf("капитал должен был перенестись в новый раунд: ожидалось %d, получено %d", balanceAfterRound, m.game.preRoundBalance[0]) } } // TestBlackjackModelFullAutoPlaythrough прогоняет TUI-модель от // начала до конца для человека, отыгрывающего по той же логике, что // и бот, но через реальные нажатия клавиш — для нескольких // вариантов числа игроков и нескольких раундов подряд. func TestBlackjackModelFullAutoPlaythrough(t *testing.T) { bot := BlackjackBot{} for n := 1; n <= 6; n++ { m := NewBlackjackModel(n, 1000, 10) for round := 0; round < 20; round++ { const maxSteps = 200 steps := 0 for m.game.Phase != BJPhaseRoundOver && steps < maxSteps { steps++ if m.game.CurrentPlayerIdx != m.humanIdx { next, _ := m.Update(botMoveMsg{}) m = next.(BlackjackModel) continue } action := bot.DecideAction(m.game) var k string switch action { case "double": k = "d" case "hit": k = "h" default: k = "s" } next, _ := m.Update(key(k)) m = next.(BlackjackModel) } if steps >= maxSteps { t.Fatalf("игроков %d, раунд %d: раунд через TUI не завершился за %d шагов", n, round, maxSteps) } if m.game.Result == nil { t.Fatalf("игроков %d, раунд %d: раунд завершился без результата", n, round) } next, _ := m.Update(key("n")) m = next.(BlackjackModel) } } } func TestBlackjackModelShowsMessageWhenBetReduced(t *testing.T) { m := NewBlackjackModel(1, 100, 10) for m.game.Phase == BJPhasePlayerTurn { if err := m.game.Stand(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } } m.game.Players[m.humanIdx].Balance = 4 // меньше полной ставки 10, но больше нуля next, _ := m.Update(key("n")) m = next.(BlackjackModel) if !m.game.RoundBetReduced[m.humanIdx] { t.Fatalf("ожидался флаг урезанной ставки") } if m.game.Players[m.humanIdx].Bet != 4 { t.Errorf("ставка должна была урезаться до 4, получено %d", m.game.Players[m.humanIdx].Bet) } if m.message == "" { t.Errorf("ожидалось информационное сообщение об урезанной ставке") } if m.isError { t.Errorf("сообщение об урезанной ставке не должно быть ошибкой") } } func TestBlackjackModelBankruptBlocksNextRoundAndShowsMessage(t *testing.T) { m := NewBlackjackModel(1, 100, 10) for m.game.Phase == BJPhasePlayerTurn { if err := m.game.Stand(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } } m.game.Players[m.humanIdx].Balance = 0 handsBefore := len(m.game.Players[m.humanIdx].Hand) if !m.humanIsBankrupt() { t.Fatalf("ожидалось, что humanIsBankrupt() вернёт true при нулевом балансе") } view := m.View() if !strings.Contains(view, T("blackjack.bankrupt")) { t.Errorf("экран должен был показать сообщение о банкротстве, получено:\n%s", view) } next, _ := m.Update(key("n")) m = next.(BlackjackModel) if m.game.Phase != BJPhaseRoundOver { t.Errorf("нажатие 'n' при банкротстве не должно было начинать новый раунд") } if len(m.game.Players[m.humanIdx].Hand) != handsBefore { t.Errorf("рука не должна была измениться — новый раунд не должен был начаться") } }