package main import "testing" func TestThousandModelDealsThreePlayers(t *testing.T) { m := NewThousandModel() if len(m.game.Players) != 3 { t.Errorf("ожидалось 3 игрока, получено %d", len(m.game.Players)) } } func TestThousandModelBidViaKeys(t *testing.T) { m := NewThousandModel() m.game.CurrentBidderIdx = m.humanIdx m.bidAmount = 100 next, _ := m.Update(key("enter")) m = next.(ThousandModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if m.game.HighBid != 100 { t.Fatalf("ожидалась ставка 100, получено %d", m.game.HighBid) } } func TestThousandModelPassRejectedForFirstBidder(t *testing.T) { m := NewThousandModel() m.game.CurrentBidderIdx = m.humanIdx m.game.firstBidderIdx = m.humanIdx m.game.bidsMade = 0 next, _ := m.Update(key("p")) m = next.(ThousandModel) if !m.isError { t.Fatalf("первый ходящий не может пасовать, ошибка ожидалась") } } func TestThousandModelDiscardViaKeys(t *testing.T) { m := NewThousandModel() m.game.CurrentBidderIdx = m.humanIdx m.bidAmount = 100 next, _ := m.Update(key("enter")) m = next.(ThousandModel) if m.isError { t.Fatalf("неожиданная ошибка ставки: %s", m.message) } if err := m.game.Pass(); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if err := m.game.Pass(); err != nil { t.Fatalf("ошибка подготовки: %v", err) } if m.game.Phase != ThousandPhaseDiscard { t.Fatalf("ожидалась фаза снoса") } if m.game.DeclarerIdx != m.humanIdx { t.Fatalf("человек должен был стать declarer") } m.cursor = 0 next, _ = m.Update(key("enter")) m = next.(ThousandModel) if m.isError { t.Fatalf("неожиданная ошибка при первом сносе: %s", m.message) } if len(m.game.Players[m.humanIdx].Hand) != 9 { t.Fatalf("после первого сноса должно остаться 9 карт, получено %d", len(m.game.Players[m.humanIdx].Hand)) } } func TestThousandModelQReturnsToMenu(t *testing.T) { m := NewThousandModel() next, _ := m.Update(key("q")) m = next.(ThousandModel) if !m.backToMenu { t.Fatalf("ожидался флаг backToMenu после 'q'") } if m.quitting { t.Fatalf("'q' не должен закрывать всё приложение") } } // TestThousandModelFullAutoPlaythrough прогоняет TUI-модель от // начала до конца нескольких раздач, где "человек" отыгрывает свои // решения через реальные нажатия клавиш (используя ту же логику, // что и бот). func TestThousandModelFullAutoPlaythrough(t *testing.T) { m := NewThousandModel() bot := ThousandBot{} const maxHands = 30 for hand := 0; hand < maxHands; hand++ { const maxSteps = 3000 steps := 0 for m.game.Phase != ThousandPhaseHandOver && steps < maxSteps { steps++ if m.actorForPhase() != m.humanIdx { next, _ := m.Update(botMoveMsg{}) m = next.(ThousandModel) continue } switch m.game.Phase { case ThousandPhaseBidding: if amount, ok := bot.DecideBid(m.game); ok { m.bidAmount = amount next, _ := m.Update(key("enter")) m = next.(ThousandModel) } else { next, _ := m.Update(key("p")) m = next.(ThousandModel) } case ThousandPhaseDiscard: discards := bot.DecideDiscards(m.game.Players[m.humanIdx].Hand) for _, card := range discards { for i, hc := range m.currentHand() { if hc == card { m.cursor = i break } } next, _ := m.Update(key("enter")) m = next.(ThousandModel) } case ThousandPhaseTrick: card := bot.DecidePlay(m.game) for i, hc := range m.currentHand() { if hc == card { m.cursor = i break } } next, _ := m.Update(key("enter")) m = next.(ThousandModel) } } if steps >= maxSteps { t.Fatalf("раздача %d: не завершилась через TUI за %d шагов (фаза %v)", hand, maxSteps, m.game.Phase) } if m.game.Result == nil { t.Fatalf("раздача %d: завершилась без результата", hand) } if m.game.Result.MatchOver { return } next, _ := m.Update(key("n")) m = next.(ThousandModel) } }