352 lines
13 KiB
Go
352 lines
13 KiB
Go
package main
|
||
|
||
import "testing"
|
||
|
||
func TestOneOhOneDealsCorrectly(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
for i, p := range g.Players {
|
||
want := 5
|
||
if i == g.DealerIdx {
|
||
want = 4
|
||
}
|
||
if len(p.Hand) != want {
|
||
t.Errorf("игрок %d: ожидалось %d карт, получено %d", i, want, len(p.Hand))
|
||
}
|
||
}
|
||
if len(g.Discard) != 1 {
|
||
t.Fatalf("ожидалась 1 карта в сбросе (стартовая), получено %d", len(g.Discard))
|
||
}
|
||
if len(g.Stock) != 36-14-1 {
|
||
t.Fatalf("ожидалось %d карт в колоде добора, получено %d", 36-14-1, len(g.Stock))
|
||
}
|
||
if g.CurrentPlayerIdx != g.nextActive(g.DealerIdx) {
|
||
t.Errorf("первым должен ходить игрок слева от сдающего")
|
||
}
|
||
}
|
||
|
||
func TestPlayCardMatchingSuit(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.topDiscard() != (c(Ten, Clubs)) {
|
||
t.Errorf("верхняя карта сброса должна была смениться")
|
||
}
|
||
}
|
||
|
||
func TestPlayCardMatchingRank(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Nine, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Nine, Hearts)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestPlayCardRejectsMismatch(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Hearts)); err != ErrOOOInvalidPlay {
|
||
t.Errorf("ожидалась ошибка ErrOOOInvalidPlay, получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestDrawRejectedWhenPlayableCardExists(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Nine, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.Draw(); err != ErrOOOMustPlay {
|
||
t.Errorf("ожидалась ошибка ErrOOOMustPlay, получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestDrawAutoPlaysWhenDrawnCardMatches(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Hearts)} // не подходит
|
||
g.Stock = []Card{c(Nine, Diamonds), c(Two, Clubs)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.Draw(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.topDiscard() != (c(Nine, Diamonds)) {
|
||
t.Errorf("взятая подходящая карта должна была сразу сыграться")
|
||
}
|
||
if hasCard(g.Players[0].Hand, c(Nine, Diamonds)) {
|
||
t.Errorf("сыгранной карты не должно остаться в руке")
|
||
}
|
||
}
|
||
|
||
func TestAceSkipsNextPlayer(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ace, Clubs), c(Two, Diamonds)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.Players[2].Hand = []Card{c(Jack, Spades)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ace, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.CurrentPlayerIdx != 2 {
|
||
t.Errorf("туз должен был пропустить игрока 1, ожидался ход игрока 2, получено %d", g.CurrentPlayerIdx)
|
||
}
|
||
if len(g.Players[1].Hand) != 1 {
|
||
t.Errorf("пропущенный игрок не должен был брать штрафные карты")
|
||
}
|
||
}
|
||
|
||
func TestSevenGivesTwoPenaltyCardsAndSkips(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Seven, Clubs), c(Two, Diamonds)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.Players[2].Hand = []Card{c(Jack, Spades)}
|
||
g.Stock = []Card{c(Three, Hearts), c(Four, Hearts), c(Five, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Seven, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if len(g.Players[1].Hand) != 3 {
|
||
t.Errorf("игрок 1 должен был взять 2 штрафные карты (1+2=3), получено %d карт", len(g.Players[1].Hand))
|
||
}
|
||
if g.CurrentPlayerIdx != 2 {
|
||
t.Errorf("ход должен был перейти к игроку 2, получено %d", g.CurrentPlayerIdx)
|
||
}
|
||
}
|
||
|
||
func TestKingOfSpadesGivesFourPenaltyCards(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Spades)}
|
||
g.Players[0].Hand = []Card{c(King, Spades), c(Two, Diamonds)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.Stock = make([]Card, 0, 4)
|
||
for i := 0; i < 4; i++ {
|
||
g.Stock = append(g.Stock, c(Rank(2+i), Hearts))
|
||
}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(King, Spades)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if len(g.Players[1].Hand) != 5 {
|
||
t.Errorf("игрок 1 должен был взять 4 штрафные карты (1+4=5), получено %d", len(g.Players[1].Hand))
|
||
}
|
||
}
|
||
|
||
func TestOtherKingsHaveNoSpecialEffect(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
g.Discard = []Card{c(Nine, Hearts)}
|
||
g.Players[0].Hand = []Card{c(King, Hearts), c(Two, Diamonds)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.Players[2].Hand = []Card{c(Jack, Spades)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(King, Hearts)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.CurrentPlayerIdx != 1 {
|
||
t.Errorf("некозырной король не должен ничего пропускать, ожидался ход игрока 1, получено %d", g.CurrentPlayerIdx)
|
||
}
|
||
if len(g.Players[1].Hand) != 1 {
|
||
t.Errorf("игрок 1 не должен был брать штрафные карты")
|
||
}
|
||
}
|
||
|
||
func TestRoundEndScoresRemainingCards(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.Players[1].Hand = []Card{c(King, Hearts), c(Ace, Diamonds)} // 4+11=15
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Phase != OneOhOnePhaseRoundOver {
|
||
t.Fatalf("раунд должен был завершиться победой игрока 0")
|
||
}
|
||
if g.Result.Winner != 0 {
|
||
t.Fatalf("ожидался победитель 0, получен %d", g.Result.Winner)
|
||
}
|
||
if g.Result.ScoreDeltas[0] != 0 {
|
||
t.Errorf("победитель без дамы должен получить 0 очков за раунд, получено %d", g.Result.ScoreDeltas[0])
|
||
}
|
||
if g.Result.ScoreDeltas[1] != 15 {
|
||
t.Errorf("ожидалось 15 штрафных очков у проигравшего, получено %d", g.Result.ScoreDeltas[1])
|
||
}
|
||
if g.Players[1].Score != 15 {
|
||
t.Errorf("общий счёт игрока 1 должен был стать 15, получено %d", g.Players[1].Score)
|
||
}
|
||
}
|
||
|
||
func TestQueenBonusOnWin(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Queen, Clubs)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Queen, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if !g.Result.QueenBonus {
|
||
t.Fatalf("ожидался бонус за даму")
|
||
}
|
||
if g.Result.ScoreDeltas[0] != -20 {
|
||
t.Errorf("ожидался бонус -20 за некозырную даму, получено %d", g.Result.ScoreDeltas[0])
|
||
}
|
||
}
|
||
|
||
func TestQueenOfHeartsGivesDoubleBonus(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Hearts)}
|
||
g.Players[0].Hand = []Card{c(Queen, Hearts)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Queen, Hearts)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Result.ScoreDeltas[0] != -40 {
|
||
t.Errorf("ожидался бонус -40 за червовую даму, получено %d", g.Result.ScoreDeltas[0])
|
||
}
|
||
}
|
||
|
||
func TestExactly101ResetsToZero(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Players[1].Score = 90 // 90+11(туз)=101
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.Players[1].Hand = []Card{c(Ace, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Players[1].Score != 0 {
|
||
t.Errorf("ровно 101 должно было обнулить счёт, получено %d", g.Players[1].Score)
|
||
}
|
||
if len(g.Result.Reset) != 1 || g.Result.Reset[0] != 1 {
|
||
t.Errorf("ожидался Reset=[1], получено %v", g.Result.Reset)
|
||
}
|
||
if g.Players[1].Out {
|
||
t.Errorf("игрок с обнулённым счётом не должен выбывать")
|
||
}
|
||
}
|
||
|
||
func TestOver101Eliminates(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
g.Players[1].Score = 95 // 95+11=106 > 101
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.Players[1].Hand = []Card{c(Ace, Hearts)}
|
||
g.Players[2].Hand = []Card{c(Jack, Spades)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if !g.Players[1].Out {
|
||
t.Errorf("игрок с 106 очками должен был выбыть")
|
||
}
|
||
if g.Result.MatchOver {
|
||
t.Errorf("партия не должна была завершиться (остались 2 активных игрока)")
|
||
}
|
||
}
|
||
|
||
func TestMatchEndsWhenOneActivePlayerRemains(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Players[1].Score = 95
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.Players[1].Hand = []Card{c(Ace, Hearts)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if !g.Result.MatchOver {
|
||
t.Fatalf("партия должна была завершиться (остался 1 активный игрок)")
|
||
}
|
||
if g.Result.MatchWinner != 0 {
|
||
t.Errorf("ожидался победитель партии 0, получено %d", g.Result.MatchWinner)
|
||
}
|
||
}
|
||
|
||
func TestNextRoundDealerIsRoundWinner(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs)}
|
||
g.Players[0].Hand = []Card{c(Ten, Clubs)}
|
||
g.Players[1].Hand = []Card{c(Ten, Hearts)}
|
||
g.Players[2].Hand = []Card{c(Jack, Spades)}
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
if err := g.PlayCard(c(Ten, Clubs)); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.DealerIdx != 0 {
|
||
t.Fatalf("сдающим следующего раунда должен стать победитель (0), получено %d", g.DealerIdx)
|
||
}
|
||
|
||
g.NextRound()
|
||
if g.Phase != OneOhOnePhasePlay {
|
||
t.Fatalf("новый раунд должен был начаться")
|
||
}
|
||
for i, p := range g.Players {
|
||
want := 5
|
||
if i == g.DealerIdx {
|
||
want = 4
|
||
}
|
||
if len(p.Hand) != want {
|
||
t.Errorf("игрок %d: ожидалось %d карт в новом раунде, получено %d", i, want, len(p.Hand))
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestStockReshufflesFromDiscardWhenEmpty(t *testing.T) {
|
||
g := NewOneOhOneGame([]string{"A", "B"}, -1)
|
||
g.Discard = []Card{c(Nine, Clubs), c(Ten, Diamonds), c(Jack, Diamonds)}
|
||
g.Stock = nil
|
||
g.Players[0].Hand = []Card{c(Eight, Clubs)} // не подходит ни по масти (clubs vs diamonds), ни по рангу (8 vs J)
|
||
g.CurrentPlayerIdx = 0
|
||
g.Phase = OneOhOnePhasePlay
|
||
|
||
if g.stockExhausted() {
|
||
t.Fatalf("колода не должна считаться исчерпанной — под верхней картой сброса есть ещё карты")
|
||
}
|
||
if err := g.Draw(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if len(g.Discard) > 2 {
|
||
t.Errorf("после перетасовки в сбросе не должно накопиться старых карт, получено %d карт", len(g.Discard))
|
||
}
|
||
}
|