package main import "testing" func TestNewKlondikeGameDealsCorrectly(t *testing.T) { g := NewKlondikeGame() total := 0 for col := 0; col < 7; col++ { if len(g.Tableau[col]) != col+1 { t.Errorf("колонна %d: ожидалось %d карт, получено %d", col, col+1, len(g.Tableau[col])) } for i, kc := range g.Tableau[col] { wantFaceUp := i == len(g.Tableau[col])-1 if kc.FaceUp != wantFaceUp { t.Errorf("колонна %d, карта %d: ожидалось FaceUp=%v, получено %v", col, i, wantFaceUp, kc.FaceUp) } } total += len(g.Tableau[col]) } if total != 28 { t.Fatalf("ожидалось 28 карт в колоннах (1+2+..+7), получено %d", total) } if len(g.Stock) != 52-28 { t.Fatalf("ожидалось %d карт в колоде добора, получено %d", 52-28, len(g.Stock)) } } func TestDrawStockMovesToWaste(t *testing.T) { g := NewKlondikeGame() stockBefore := len(g.Stock) top := g.Stock[len(g.Stock)-1] if err := g.DrawStock(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if len(g.Stock) != stockBefore-1 { t.Fatalf("колода добора должна была уменьшиться на 1") } if len(g.Waste) != 1 || g.Waste[0] != top { t.Fatalf("верхняя карта колоды должна была оказаться в отбое") } } func TestDrawStockRecyclesWasteWhenEmpty(t *testing.T) { g := NewKlondikeGame() g.Stock = []Card{c(Two, Hearts), c(Three, Hearts)} g.Waste = nil if err := g.DrawStock(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if err := g.DrawStock(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if len(g.Stock) != 0 { t.Fatalf("колода добора должна была опустеть, осталось %d", len(g.Stock)) } if len(g.Waste) != 2 { t.Fatalf("в отбое должно быть 2 карты, получено %d", len(g.Waste)) } if err := g.DrawStock(); err != nil { t.Fatalf("неожиданная ошибка пересдачи: %v", err) } // пересдача (переворот отбоя в колоду) — самостоятельное // действие; она не добирает карту автоматически в этом же вызове if len(g.Stock) != 2 || len(g.Waste) != 0 { t.Fatalf("после пересдачи ожидалось 2 карты в колоде и 0 в отбое, получено stock=%d waste=%d", len(g.Stock), len(g.Waste)) } if err := g.DrawStock(); err != nil { t.Fatalf("неожиданная ошибка добора после пересдачи: %v", err) } if len(g.Stock) != 1 || len(g.Waste) != 1 { t.Fatalf("после добора ожидалось 1 карта в колоде и 1 в отбое, получено stock=%d waste=%d", len(g.Stock), len(g.Waste)) } } func TestDrawStockErrorsWhenBothEmpty(t *testing.T) { g := NewKlondikeGame() g.Stock = nil g.Waste = nil if err := g.DrawStock(); err != ErrKlondikeEmptySource { t.Errorf("ожидалась ErrKlondikeEmptySource, получено: %v", err) } } func TestCanPlaceOnTableauDescendingAlternating(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}} if !g.canPlaceOnTableau(0, c(Six, Spades)) { t.Errorf("чёрная шестёрка должна ложиться на красную семёрку") } if g.canPlaceOnTableau(0, c(Six, Hearts)) { t.Errorf("красная шестёрка не должна ложиться на красную семёрку (нужен другой цвет)") } if g.canPlaceOnTableau(0, c(Five, Spades)) { t.Errorf("пятёрка не должна ложиться на семёрку (не на 1 меньше)") } } func TestCanPlaceOnEmptyTableauOnlyKing(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = nil if !g.canPlaceOnTableau(0, c(King, Clubs)) { t.Errorf("король должен ложиться в пустую колонну") } if g.canPlaceOnTableau(0, c(Queen, Clubs)) { t.Errorf("дама не должна ложиться в пустую колонну") } } func TestMoveWasteToTableau(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}} g.Waste = []Card{c(Six, Spades)} if err := g.MoveWasteToTableau(0); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if len(g.Waste) != 0 { t.Errorf("отбой должен был опустеть") } if len(g.Tableau[0]) != 2 { t.Fatalf("ожидалось 2 карты в колонне, получено %d", len(g.Tableau[0])) } } func TestMoveWasteToFoundation(t *testing.T) { g := NewKlondikeGame() g.Waste = []Card{c(Ace, Hearts)} if err := g.MoveWasteToFoundation(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Foundations[Hearts] != int(Ace) { t.Errorf("фундамент червей должен был получить туза") } g.Waste = []Card{c(Three, Hearts)} if err := g.MoveWasteToFoundation(); err != ErrKlondikeInvalidFoundation { t.Errorf("ожидалась ошибка (нужна сначала двойка), получено: %v", err) } } func TestMoveTableauToFoundationFlipsNextCard(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{ {Card: c(Five, Clubs), FaceUp: false}, {Card: c(Ace, Hearts), FaceUp: true}, } if err := g.MoveTableauToFoundation(0); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if len(g.Tableau[0]) != 1 { t.Fatalf("в колонне должна была остаться 1 карта") } if !g.Tableau[0][0].FaceUp { t.Errorf("следующая карта в колонне должна была открыться") } } func TestMoveTableauToTableauMovesValidRun(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{ {Card: c(Nine, Clubs), FaceUp: false}, {Card: c(Seven, Hearts), FaceUp: true}, {Card: c(Six, Spades), FaceUp: true}, {Card: c(Five, Hearts), FaceUp: true}, } g.Tableau[1] = []KlondikeCard{ {Card: c(Eight, Spades), FaceUp: true}, } if err := g.MoveTableauToTableau(0, 1); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if len(g.Tableau[0]) != 1 { t.Fatalf("в исходной колонне должна была остаться 1 карта (девятка рубашкой), получено %d", len(g.Tableau[0])) } if !g.Tableau[0][0].FaceUp { t.Errorf("оставшаяся девятка должна была автоматически открыться — в этом весь смысл пасьянса") } if len(g.Tableau[1]) != 4 { t.Fatalf("в целевой колонне должно было стать 4 карты, получено %d", len(g.Tableau[1])) } } func TestMoveTableauToTableauRejectsInvalidDestination(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}} g.Tableau[1] = []KlondikeCard{{Card: c(King, Spades), FaceUp: true}} if err := g.MoveTableauToTableau(0, 1); err != ErrKlondikeInvalidTableau { t.Errorf("семёрка не должна ложиться на короля, ожидалась ошибка, получено: %v", err) } } func TestMovableRunStopsAtBrokenSequence(t *testing.T) { g := NewKlondikeGame() g.Tableau[0] = []KlondikeCard{ {Card: c(Nine, Clubs), FaceUp: true}, {Card: c(Six, Spades), FaceUp: true}, {Card: c(Five, Hearts), FaceUp: true}, } run := g.movableRun(0) if run != 2 { t.Errorf("ожидалась цепочка длиной 2 (6-5), получено %d", run) } } func TestUndoRestoresPreviousState(t *testing.T) { g := NewKlondikeGame() g.Waste = []Card{c(Ace, Hearts)} if err := g.MoveWasteToFoundation(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Foundations[Hearts] != int(Ace) { t.Fatalf("ход должен был примениться") } if err := g.Undo(); err != nil { t.Fatalf("неожиданная ошибка отмены: %v", err) } if g.Foundations[Hearts] != 0 { t.Errorf("после отмены фундамент должен был вернуться в исходное состояние") } if len(g.Waste) != 1 { t.Errorf("после отмены карта должна была вернуться в отбой") } } func TestUndoErrorsWhenNoHistory(t *testing.T) { g := NewKlondikeGame() if err := g.Undo(); err != ErrKlondikeNoUndo { t.Errorf("ожидалась ErrKlondikeNoUndo, получено: %v", err) } } func TestWinDetection(t *testing.T) { g := NewKlondikeGame() for s := Clubs; s <= Spades; s++ { g.Foundations[s] = int(King) } g.Foundations[Hearts] = int(Queen) g.Waste = []Card{c(King, Hearts)} if err := g.MoveWasteToFoundation(); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if !g.Won { t.Errorf("все 4 фундамента на короле должны означать победу") } }