package main import "testing" func TestNewCheckersGameSetup(t *testing.T) { g := NewCheckersGame() whiteCount, blackCount := 0, 0 for r := 0; r < 8; r++ { for c := 0; c < 8; c++ { p := g.Board[r][c] if p == nil { continue } if (r+c)%2 == 0 { t.Errorf("шашка на светлой клетке (%d,%d)", r, c) } if p.Color == CheckersWhite { whiteCount++ } else { blackCount++ } } } if whiteCount != 12 || blackCount != 12 { t.Errorf("ожидалось по 12 шашек, получено white=%d black=%d", whiteCount, blackCount) } if g.Turn != CheckersWhite { t.Errorf("первый ход должен быть за белыми") } } func TestSimpleManMoveForwardOnly(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[5][2] = &CheckersPiece{Color: CheckersWhite} if err := g.Move(CheckersPos{5, 2}, CheckersPos{4, 1}); err != nil { t.Fatalf("неожиданная ошибка хода вперёд: %v", err) } } func TestSimpleManCannotMoveBackward(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[5][2] = &CheckersPiece{Color: CheckersWhite} if err := g.Move(CheckersPos{5, 2}, CheckersPos{6, 1}); err != ErrCheckersInvalidMove { t.Errorf("простая шашка не должна ходить назад, ожидалась ErrCheckersInvalidMove, получено: %v", err) } } func TestManCapturesBackward(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[4][3] = &CheckersPiece{Color: CheckersWhite} g.Board[5][4] = &CheckersPiece{Color: CheckersBlack} if err := g.Move(CheckersPos{4, 3}, CheckersPos{6, 5}); err != nil { t.Fatalf("шашка должна была взять назад: %v", err) } if g.Board[5][4] != nil { t.Errorf("взятая шашка должна была исчезнуть с доски") } if g.Board[6][5] == nil { t.Errorf("шашка должна была оказаться на клетке приземления") } } func TestCaptureIsMandatory(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[5][2] = &CheckersPiece{Color: CheckersWhite} g.Board[4][3] = &CheckersPiece{Color: CheckersBlack} g.Board[5][6] = &CheckersPiece{Color: CheckersWhite} if err := g.Move(CheckersPos{5, 6}, CheckersPos{4, 5}); err != ErrCheckersMustCapture { t.Errorf("простой ход должен быть запрещён при доступном взятии, получено: %v", err) } if err := g.Move(CheckersPos{5, 2}, CheckersPos{3, 4}); err != nil { t.Fatalf("взятие должно было пройти: %v", err) } } func TestMultiJumpMustContinueWithSamePiece(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[6][1] = &CheckersPiece{Color: CheckersWhite} g.Board[5][2] = &CheckersPiece{Color: CheckersBlack} g.Board[3][4] = &CheckersPiece{Color: CheckersBlack} g.Board[6][7] = &CheckersPiece{Color: CheckersWhite} // другая белая шашка — ходить ею сейчас нельзя if err := g.Move(CheckersPos{6, 1}, CheckersPos{4, 3}); err != nil { t.Fatalf("первое взятие должно было пройти: %v", err) } if g.ChainPos == nil { t.Fatalf("ожидалось продолжение серии взятий той же шашкой") } if g.Turn != CheckersWhite { t.Errorf("ход не должен был перейти сопернику в середине серии") } if err := g.Move(CheckersPos{6, 7}, CheckersPos{5, 6}); err != ErrCheckersMustContinue { t.Errorf("ход другой шашкой в середине серии взятий должен быть запрещён, получено: %v", err) } if err := g.Move(CheckersPos{4, 3}, CheckersPos{2, 5}); err != nil { t.Fatalf("второе взятие в серии должно было пройти: %v", err) } if g.ChainPos != nil { t.Errorf("серия должна была завершиться — больше взятий нет") } if g.Turn != CheckersBlack { t.Errorf("ход должен был перейти чёрным после завершения серии") } if g.Board[5][2] != nil || g.Board[3][4] != nil { t.Errorf("обе взятые шашки должны были исчезнуть с доски") } } func TestPromotionToKing(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[1][2] = &CheckersPiece{Color: CheckersWhite} if err := g.Move(CheckersPos{1, 2}, CheckersPos{0, 1}); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if !g.Board[0][1].King { t.Errorf("шашка должна была стать дамкой на последней горизонтали") } } func TestPromotionDuringCaptureContinuesAsKing(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[2][1] = &CheckersPiece{Color: CheckersWhite} g.Board[1][2] = &CheckersPiece{Color: CheckersBlack} g.Board[2][5] = &CheckersPiece{Color: CheckersBlack} if err := g.Move(CheckersPos{2, 1}, CheckersPos{0, 3}); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if !g.Board[0][3].King { t.Fatalf("шашка должна была стать дамкой при взятии на последней горизонтали") } if g.ChainPos == nil { t.Fatalf("если дальнейшее взятие возможно дамкой — серия должна продолжиться") } } func TestKingFlyingMove(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[4][4] = &CheckersPiece{Color: CheckersWhite, King: true} if err := g.Move(CheckersPos{4, 4}, CheckersPos{1, 1}); err != nil { t.Fatalf("дамка должна летать на любое расстояние по диагонали: %v", err) } } func TestKingFlyingCaptureWithChoiceOfLanding(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[7][0] = &CheckersPiece{Color: CheckersWhite, King: true} g.Board[4][3] = &CheckersPiece{Color: CheckersBlack} options := g.captureMoveOptions(CheckersPos{7, 0}) if len(options) == 0 { t.Fatalf("ожидались варианты взятия дамкой") } landed := false for _, opt := range options { if opt.To == (CheckersPos{2, 5}) { landed = true } } if !landed { t.Errorf("среди вариантов должна быть клетка (2,5)") } if err := g.Move(CheckersPos{7, 0}, CheckersPos{1, 6}); err != nil { t.Fatalf("выбор дальней клетки приземления должен быть допустим: %v", err) } if g.Board[4][3] != nil { t.Errorf("взятая шашка должна была исчезнуть") } } func TestKingCannotJumpOverTwoPiecesInARow(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[7][0] = &CheckersPiece{Color: CheckersWhite, King: true} g.Board[4][3] = &CheckersPiece{Color: CheckersBlack} g.Board[3][4] = &CheckersPiece{Color: CheckersBlack} options := g.captureMoveOptions(CheckersPos{7, 0}) for _, opt := range options { if opt.To.Row <= 3 { t.Errorf("нельзя приземлиться за второй подряд фигурой, но получен вариант %v", opt.To) } } } func TestGameOverWhenNoLegalMoves(t *testing.T) { g := &CheckersGameState{Turn: CheckersBlack} g.Board[0][7] = &CheckersPiece{Color: CheckersBlack} g.Board[1][6] = &CheckersPiece{Color: CheckersWhite} // (2,5) занята — иначе чёрная шашка могла бы взять белую назад // через прыжок (0,7)->(2,5), ведь простые шашки в русских шашках // бьют в любом направлении, включая назад g.Board[2][5] = &CheckersPiece{Color: CheckersWhite} g.Board[5][0] = &CheckersPiece{Color: CheckersWhite} g.Turn = CheckersWhite if err := g.Move(CheckersPos{5, 0}, CheckersPos{4, 1}); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Result == nil { t.Fatalf("партия должна была завершиться — у чёрных нет ходов") } if g.Result.Winner != CheckersWhite { t.Errorf("ожидался победитель — белые") } } func TestDrawByNoCaptureLimit(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite, MovesSinceCapture: maxCheckersMovesWithoutCapture - 1} g.Board[5][2] = &CheckersPiece{Color: CheckersWhite} if err := g.Move(CheckersPos{5, 2}, CheckersPos{4, 1}); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Result == nil || !g.Result.Draw { t.Fatalf("ожидалась ничья по лимиту ходов без взятия") } } func TestCannotMoveOpponentPiece(t *testing.T) { g := &CheckersGameState{Turn: CheckersWhite} g.Board[2][1] = &CheckersPiece{Color: CheckersBlack} if err := g.Move(CheckersPos{2, 1}, CheckersPos{3, 2}); err != ErrCheckersNoPiece { t.Errorf("ожидалась ошибка хода чужой шашкой, получено: %v", err) } }