package main import "testing" func TestSenetStartingSetupAlternates(t *testing.T) { g := NewSenetGame(SenetPlayerA) for sq := 1; sq <= 10; sq++ { owner, ok := senetCellOwner(g.Board[sq]) if !ok { t.Fatalf("клетка %d должна быть занята при старте", sq) } wantA := sq%2 == 1 if wantA && owner != SenetPlayerA { t.Errorf("клетка %d (нечётная) должна принадлежать игроку A", sq) } if !wantA && owner != SenetPlayerB { t.Errorf("клетка %d (чётная) должна принадлежать игроку B", sq) } } for sq := 11; sq <= 30; sq++ { if g.Board[sq] != 0 { t.Errorf("клетка %d должна быть пуста при старте, получено %d", sq, g.Board[sq]) } } } func TestSenetSwapOnLoneOpponentBlot(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[5] = senetCellValue(SenetPlayerA) g.Board[8] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 if err := g.Move(5); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } ownerAt8, _ := senetCellOwner(g.Board[8]) ownerAt5, _ := senetCellOwner(g.Board[5]) if ownerAt8 != SenetPlayerA { t.Errorf("клетка 8 должна была стать занятой игроком A, получено %v", g.Board[8]) } if ownerAt5 != SenetPlayerB { t.Errorf("шашка соперника должна была обменяться местами и оказаться на клетке 5, получено %v", g.Board[5]) } } func TestSenetTwoInRowAreProtected(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[5] = senetCellValue(SenetPlayerA) g.Board[20] = senetCellValue(SenetPlayerA) // вторая шашка — есть куда сходить вперёд, так что откат "хода назад" не включится g.Board[8] = senetCellValue(SenetPlayerB) g.Board[9] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 moves := g.LegalMoves() for _, mv := range moves { if mv.From == 5 && !mv.Backward { t.Errorf("нельзя вставать вперёд на защищённую (2+ подряд) шашку соперника") } } } func TestSenetThreeInRowBlockPassage(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[5] = senetCellValue(SenetPlayerA) g.Board[20] = senetCellValue(SenetPlayerA) // вторая шашка с явно легальным ходом — проверка не вакуумна g.Board[6] = senetCellValue(SenetPlayerB) g.Board[7] = senetCellValue(SenetPlayerB) g.Board[8] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 5 // 5+5=10, должно пройти НАСКВОЗЬ через 6-7-8 moves := g.LegalMoves() if len(moves) == 0 { t.Fatalf("ожидался хотя бы один ход (вторая шашка с 20 точно может сходить)") } for _, mv := range moves { if mv.From == 5 { t.Errorf("заграждение из трёх подряд должно полностью блокировать проход, даже если конечная клетка сама по себе свободна") } } } func TestSenetBlockadeDoesNotTurnCorner(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[9] = senetCellValue(SenetPlayerB) g.Board[10] = senetCellValue(SenetPlayerB) g.Board[11] = senetCellValue(SenetPlayerB) if g.senetRunLength(10) >= 3 { t.Errorf("цепочка не должна пересекать угол 10-11, получено %d", g.senetRunLength(10)) } } func TestSenetTrapSquareSendsBackToRebirth(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[24] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 // 24+3=27 — ловушка if err := g.Move(24); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Board[27] != 0 { t.Errorf("клетка 27 должна была остаться пустой после отскока") } owner, ok := senetCellOwner(g.Board[SenetHouseRebirth]) if !ok || owner != SenetPlayerA { t.Errorf("шашка должна была оказаться на клетке 15 (дом возрождения)") } } func TestSenetTrapBouncesToNearestEmptyBeforeRebirth(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[24] = senetCellValue(SenetPlayerA) g.Board[SenetHouseRebirth] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 if err := g.Move(24); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } owner, ok := senetCellOwner(g.Board[SenetHouseRebirth-1]) if !ok || owner != SenetPlayerA { t.Errorf("при занятой клетке 15 шашка должна была отскочить на ближайшую свободную клетку перед ней (14)") } } func TestSenetExactRollRequiredToBearOffFromHouseThree(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[SenetHouseThree] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 2 // House 28 требует ровно 3 if err := g.Move(SenetHouseThree); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.BorneOff[SenetPlayerA] != 0 { t.Errorf("бросок 2 не должен был снять шашку с клетки 28 (нужен именно 3)") } owner, ok := senetCellOwner(g.Board[30]) if !ok || owner != SenetPlayerA { t.Errorf("шашка должна была просто переместиться на клетку 30") } } func TestSenetBearsOffWithExactRoll(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[SenetHouseThree] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 if err := g.Move(SenetHouseThree); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.BorneOff[SenetPlayerA] != 1 { t.Errorf("ожидалось снятие шашки с клетки 28 точным броском 3, BorneOff=%d", g.BorneOff[SenetPlayerA]) } } func TestSenetCannotBearOffWithoutReachingExitSquare(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[20] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 5 moves := g.LegalMoves() if len(moves) != 1 || moves[0].From != 20 { t.Fatalf("ожидался один обычный ход на клетку 25, получено %+v", moves) } g.Move(20) if g.BorneOff[SenetPlayerA] != 0 { t.Errorf("снятие не должно было произойти вдалеке от выходных клеток") } } func TestSenetWinWhenAllFiveBorneOff(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.BorneOff[SenetPlayerA] = SenetPieceCount - 1 g.Board[SenetBoardSize] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 1 if err := g.Move(SenetBoardSize); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.Result == nil || g.Result.Winner != SenetPlayerA { t.Fatalf("ожидалась победа игрока A, получено Result=%+v", g.Result) } } func TestSenetExtraTurnOnRolls1And4And5(t *testing.T) { for _, roll := range []int{1, 4, 5} { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[1] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = roll g.Move(1) if g.CurrentPlayer != SenetPlayerA { t.Errorf("бросок %d должен был дать дополнительный ход тому же игроку", roll) } } } func TestSenetNoExtraTurnOnRolls2And3(t *testing.T) { for _, roll := range []int{2, 3} { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[1] = senetCellValue(SenetPlayerA) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = roll g.Move(1) if g.CurrentPlayer != SenetPlayerB { t.Errorf("бросок %d не должен был давать дополнительный ход", roll) } } } func TestSenetMustMoveBackwardWhenNoForwardMovePossible(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[10] = senetCellValue(SenetPlayerA) g.Board[13] = senetCellValue(SenetPlayerB) g.Board[14] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA g.Rolled = true g.DiceValue = 3 moves := g.LegalMoves() if len(moves) == 0 { t.Fatalf("ожидался ход назад (10-3=7), так как вперёд ходов нет") } for _, mv := range moves { if !mv.Backward { t.Errorf("при отсутствии хода вперёд все предложенные ходы должны быть назад") } } } func TestSenetNoMoveAtAllEndsTurnAfterRoll(t *testing.T) { g := NewSenetGame(SenetPlayerA) g.Board = [SenetBoardSize + 1]int{} g.Board[10] = senetCellValue(SenetPlayerA) g.Board[12] = senetCellValue(SenetPlayerB) g.Board[13] = senetCellValue(SenetPlayerB) g.Board[14] = senetCellValue(SenetPlayerB) g.Board[6] = senetCellValue(SenetPlayerB) g.Board[7] = senetCellValue(SenetPlayerB) g.CurrentPlayer = SenetPlayerA if err := g.Roll(3); err != nil { t.Fatalf("неожиданная ошибка: %v", err) } if g.CurrentPlayer != SenetPlayerB { t.Errorf("при полном отсутствии ходов (ни вперёд, ни назад) очередь должна была перейти сопернику") } }