279 lines
9.9 KiB
Go
279 lines
9.9 KiB
Go
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("при полном отсутствии ходов (ни вперёд, ни назад) очередь должна была перейти сопернику")
|
||
}
|
||
}
|