go_games_collection/klondike_game_test.go

343 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 фундамента на короле должны означать победу")
}
}