343 lines
12 KiB
Go
343 lines
12 KiB
Go
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 TestMoveTableauToTableauNPartialMove(t *testing.T) {
|
||
g := NewKlondikeGame()
|
||
g.Tableau[0] = []KlondikeCard{
|
||
{Card: c(King, Spades), FaceUp: true},
|
||
{Card: c(Queen, Diamonds), FaceUp: true},
|
||
{Card: c(Jack, Spades), FaceUp: true},
|
||
}
|
||
g.Tableau[1] = []KlondikeCard{
|
||
{Card: c(King, Clubs), FaceUp: true},
|
||
}
|
||
|
||
if err := g.MoveTableauToTableauN(0, 1, 2); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if len(g.Tableau[0]) != 1 || g.Tableau[0][0].Card != c(King, Spades) {
|
||
t.Errorf("в колонне 0 должен был остаться только K♠, получено %v", g.Tableau[0])
|
||
}
|
||
if len(g.Tableau[1]) != 3 {
|
||
t.Fatalf("в колонне 1 ожидалось 3 карты, получено %d", len(g.Tableau[1]))
|
||
}
|
||
if g.Tableau[1][1].Card != c(Queen, Diamonds) || g.Tableau[1][2].Card != c(Jack, Spades) {
|
||
t.Errorf("перенесённые карты должны были сохранить порядок, получено %v", g.Tableau[1])
|
||
}
|
||
}
|
||
|
||
func TestMoveTableauToTableauNRejectsCountBeyondMovableRun(t *testing.T) {
|
||
g := NewKlondikeGame()
|
||
g.Tableau[0] = []KlondikeCard{
|
||
{Card: c(Nine, Clubs), FaceUp: true}, // не в последовательности с тем, что ниже
|
||
{Card: c(King, Spades), FaceUp: true},
|
||
{Card: c(Queen, Diamonds), FaceUp: true},
|
||
}
|
||
g.Tableau[1] = []KlondikeCard{}
|
||
|
||
// movableRun здесь всего 2 (K♠-Q♦), запросить 3 нельзя
|
||
if err := g.MoveTableauToTableauN(0, 1, 3); err == nil {
|
||
t.Errorf("ожидалась ошибка при запросе большего числа карт, чем есть в корректной последовательности")
|
||
}
|
||
}
|
||
|
||
func TestMoveTableauToTableauNRejectsZeroOrNegative(t *testing.T) {
|
||
g := NewKlondikeGame()
|
||
g.Tableau[0] = []KlondikeCard{{Card: c(King, Spades), FaceUp: true}}
|
||
g.Tableau[1] = []KlondikeCard{}
|
||
|
||
if err := g.MoveTableauToTableauN(0, 1, 0); err == nil {
|
||
t.Errorf("ожидалась ошибка при count=0")
|
||
}
|
||
}
|
||
|
||
func TestMoveTableauToTableauNRejectsInvalidDestinationForPartialRun(t *testing.T) {
|
||
g := NewKlondikeGame()
|
||
g.Tableau[0] = []KlondikeCard{
|
||
{Card: c(King, Spades), FaceUp: true},
|
||
{Card: c(Queen, Diamonds), FaceUp: true},
|
||
{Card: c(Jack, Spades), FaceUp: true},
|
||
}
|
||
// девятка не примет валета (не смежный ранг)
|
||
g.Tableau[1] = []KlondikeCard{
|
||
{Card: c(Nine, Hearts), FaceUp: true},
|
||
}
|
||
|
||
if err := g.MoveTableauToTableauN(0, 1, 1); err == nil {
|
||
t.Errorf("ожидалась ошибка — валет не кладётся на девятку")
|
||
}
|
||
if len(g.Tableau[0]) != 3 {
|
||
t.Errorf("при отклонённом ходе колонна-источник не должна была измениться")
|
||
}
|
||
}
|
||
|
||
func TestMoveTableauToTableauStillMovesFullRunByDefault(t *testing.T) {
|
||
g := NewKlondikeGame()
|
||
g.Tableau[0] = []KlondikeCard{
|
||
{Card: c(King, Spades), FaceUp: true},
|
||
{Card: c(Queen, Diamonds), FaceUp: true},
|
||
{Card: c(Jack, Spades), FaceUp: true},
|
||
}
|
||
g.Tableau[1] = []KlondikeCard{}
|
||
|
||
if err := g.MoveTableauToTableau(0, 1); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if len(g.Tableau[0]) != 0 {
|
||
t.Errorf("старое поведение по умолчанию должно было перенести всю последовательность целиком")
|
||
}
|
||
if len(g.Tableau[1]) != 3 {
|
||
t.Errorf("ожидалось 3 карты в колонне назначения, получено %d", len(g.Tableau[1]))
|
||
}
|
||
}
|
||
|
||
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 фундамента на короле должны означать победу")
|
||
}
|
||
}
|