320 lines
12 KiB
Go
320 lines
12 KiB
Go
package main
|
||
|
||
import "testing"
|
||
|
||
func TestShortNardyStartingSetup(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
// белые: 2 на 23(abs), 5 на 12(abs), 3 на 7(abs), 5 на 5(abs)
|
||
checkPoint := func(abs int, owner NardyPlayer, count int) {
|
||
t.Helper()
|
||
pt := g.Board[abs]
|
||
if pt.Count != count || (count > 0 && pt.Owner != owner) {
|
||
t.Errorf("точка %d: ожидалось %v x%d, получено %v x%d", abs, owner, count, pt.Owner, pt.Count)
|
||
}
|
||
}
|
||
checkPoint(23, NardyWhite, 2)
|
||
checkPoint(12, NardyWhite, 5)
|
||
checkPoint(7, NardyWhite, 3)
|
||
checkPoint(5, NardyWhite, 5)
|
||
// чёрные — зеркально: rel 23 -> abs 23-23=0, rel12->abs11, rel7->abs16, rel5->abs18
|
||
checkPoint(0, NardyBlack, 2)
|
||
checkPoint(11, NardyBlack, 5)
|
||
checkPoint(16, NardyBlack, 3)
|
||
checkPoint(18, NardyBlack, 5)
|
||
|
||
total := 0
|
||
for _, pt := range g.Board {
|
||
total += pt.Count
|
||
}
|
||
if total != 30 {
|
||
t.Errorf("на доске должно быть 30 шашек суммарно, получено %d", total)
|
||
}
|
||
}
|
||
|
||
func TestLongNardyStartingSetup(t *testing.T) {
|
||
g := NewNardyGame(NardyLong, NardyWhite)
|
||
if g.Board[23].Count != 15 || g.Board[23].Owner != NardyWhite {
|
||
t.Errorf("все 15 белых шашек должны стоять на абсолютной точке 23, получено %+v", g.Board[23])
|
||
}
|
||
// чёрные: rel23 -> abs (23+12)%24=11
|
||
if g.Board[11].Count != 15 || g.Board[11].Owner != NardyBlack {
|
||
t.Errorf("все 15 чёрных шашек должны стоять на абсолютной точке 11, получено %+v", g.Board[11])
|
||
}
|
||
}
|
||
|
||
func TestCanLandShortNardyBlocksOnTwoOrMore(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 2}
|
||
if g.canLand(NardyWhite, 10) {
|
||
t.Errorf("точка с двумя и более шашками соперника должна быть заблокирована")
|
||
}
|
||
}
|
||
|
||
func TestCanLandShortNardyAllowsHittingBlot(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
|
||
if !g.canLand(NardyWhite, 10) {
|
||
t.Errorf("одинокую шашку соперника (блот) должно быть можно сбить")
|
||
}
|
||
}
|
||
|
||
func TestCanLandLongNardyBlocksEvenSingleChecker(t *testing.T) {
|
||
g := NewNardyGame(NardyLong, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
|
||
if g.canLand(NardyWhite, 10) {
|
||
t.Errorf("в длинных нардах даже одна шашка соперника должна блокировать точку полностью")
|
||
}
|
||
}
|
||
|
||
func TestHittingSendsBlotToBar(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{5}
|
||
|
||
if err := g.ApplyMove(NardyMove{From: 15, Die: 5}); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Bar[NardyBlack] != 1 {
|
||
t.Errorf("сбитая шашка чёрных должна была уйти на бар, Bar[Black]=%d", g.Bar[NardyBlack])
|
||
}
|
||
if g.Board[10].Owner != NardyWhite || g.Board[10].Count != 1 {
|
||
t.Errorf("на точке 10 теперь должна стоять белая шашка, получено %+v", g.Board[10])
|
||
}
|
||
}
|
||
|
||
func TestMustEnterFromBarBeforeOtherMoves(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Bar[NardyWhite] = 1
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{5}
|
||
|
||
moves := g.LegalMovesNow()
|
||
for _, mv := range moves {
|
||
if !mv.FromBar {
|
||
t.Errorf("пока на баре есть шашка, разрешён только ход с бара, получен обычный ход %+v", mv)
|
||
}
|
||
}
|
||
if len(moves) == 0 {
|
||
t.Fatalf("ожидался хотя бы один легальный ход (вход с бара)")
|
||
}
|
||
}
|
||
|
||
func TestBarEntryBlockedByOpponentPoint(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Bar[NardyWhite] = 1
|
||
entryAbs := 24 - 3
|
||
g.Board[entryAbs] = NardyPoint{Owner: NardyBlack, Count: 2}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{3}
|
||
|
||
if err := g.ApplyMove(NardyMove{FromBar: true, Die: 3}); err != ErrNardyIllegalMove {
|
||
t.Errorf("вход с бара на заблокированную точку должен быть отклонён, получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestBearOffRequiresAllCheckersHome(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[3] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1} // шашка ещё вне дома
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{4}
|
||
|
||
moves := g.LegalMovesNow()
|
||
for _, mv := range moves {
|
||
if mv.BearOff {
|
||
t.Errorf("снятие с доски не должно быть доступно, пока не все шашки дома")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBearOffExactMatch(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[3] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{4}
|
||
|
||
if err := g.ApplyMove(NardyMove{From: 3, Die: 4, BearOff: true}); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.BorneOff[NardyWhite] != 1 {
|
||
t.Errorf("ожидалось снятие одной шашки, BorneOff=%d", g.BorneOff[NardyWhite])
|
||
}
|
||
}
|
||
|
||
func TestBearOffExcessRejectedWithCheckerBeyond(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[2] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[4] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{6}
|
||
|
||
moves := g.LegalMovesNow()
|
||
for _, mv := range moves {
|
||
if mv.BearOff && mv.From == 2 {
|
||
t.Errorf("избыточное снятие с точки 2 костью 6 не должно быть разрешено, пока есть шашка на точке 4 (дальше)")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBearOffExcessAllowedWithoutCheckerBeyond(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[2] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{6}
|
||
|
||
if err := g.ApplyMove(NardyMove{From: 2, Die: 6, BearOff: true}); err != nil {
|
||
t.Fatalf("избыточное снятие должно быть разрешено, если нет шашек дальше: %v", err)
|
||
}
|
||
if g.BorneOff[NardyWhite] != 1 {
|
||
t.Errorf("ожидалось снятие шашки, получено BorneOff=%d", g.BorneOff[NardyWhite])
|
||
}
|
||
}
|
||
|
||
func TestOnlyOneCheckerFromHeadPerTurn(t *testing.T) {
|
||
g := NewNardyGame(NardyLong, NardyWhite)
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{3, 4}
|
||
|
||
moves := g.LegalMovesNow()
|
||
if len(moves) == 0 {
|
||
t.Fatalf("ожидался хотя бы один легальный ход")
|
||
}
|
||
if err := g.ApplyMove(moves[0]); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
for _, mv := range g.LegalMovesNow() {
|
||
if mv.From == 23 {
|
||
t.Errorf("вторая шашка с головы в одном ходу быть взята не должна")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestDoublesGiveFourDiceValues(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Roll(4, 4)
|
||
if len(g.DiceLeft) != 4 {
|
||
t.Fatalf("дубль должен давать 4 значения кости, получено %d: %v", len(g.DiceLeft), g.DiceLeft)
|
||
}
|
||
for _, d := range g.DiceLeft {
|
||
if d != 4 {
|
||
t.Errorf("все четыре значения должны быть равны 4, получено %v", g.DiceLeft)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestMustUseMaximumPossibleDice(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
// шашка на rel=10: ходом 5 попадает на rel=5 (открыто), ходом 3 —
|
||
// на rel=7 (заблокировано двумя чёрными). Если сыграть 5 первой,
|
||
// потом с rel=5 можно сыграть 3 -> rel=2 (открыто) = используются
|
||
// ОБЕ кости. Если сыграть 3 первой — ход блокирован сразу.
|
||
g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[7] = NardyPoint{Owner: NardyBlack, Count: 2}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{5, 3}
|
||
|
||
moves := g.LegalMovesNow()
|
||
if len(moves) != 1 || moves[0].Die != 5 || moves[0].From != 10 {
|
||
t.Fatalf("единственный легальный первый ход должен быть 10->5 (используя большую кость 5), получено %+v", moves)
|
||
}
|
||
}
|
||
|
||
func TestMustPlayLargerDieWhenOnlyOnePlayable(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[8] = NardyPoint{Owner: NardyBlack, Count: 2}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{5, 2}
|
||
|
||
moves := g.LegalMovesNow()
|
||
if len(moves) != 1 || moves[0].Die != 5 {
|
||
t.Fatalf("должна была быть разрешена только большая кость (5), получено %+v", moves)
|
||
}
|
||
}
|
||
|
||
func TestNoLegalMoveAtAll(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1} // единственная шашка
|
||
g.Board[4] = NardyPoint{Owner: NardyBlack, Count: 2} // блокирует единственное направление (10-6=4) при дубле 6-6
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{6, 6, 6, 6}
|
||
|
||
moves := g.LegalMovesNow()
|
||
if len(moves) != 0 {
|
||
t.Errorf("ожидалось отсутствие легальных ходов, получено %+v", moves)
|
||
}
|
||
}
|
||
|
||
func TestNoLegalMoveAfterRollEndsTurnImmediately(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.Board[4] = NardyPoint{Owner: NardyBlack, Count: 2}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseRoll
|
||
|
||
g.Roll(6, 6)
|
||
if g.Phase != NardyPhaseRoll || g.CurrentPlayer != NardyBlack {
|
||
t.Errorf("при отсутствии ходов очки должны были сгореть, а ход перейти к чёрным сразу; получено Phase=%v CurrentPlayer=%v", g.Phase, g.CurrentPlayer)
|
||
}
|
||
}
|
||
|
||
func TestWinWhenAllCheckersBorneOff(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[0] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.BorneOff[NardyWhite] = 14
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{1}
|
||
|
||
if err := g.ApplyMove(NardyMove{From: 0, Die: 1, BearOff: true}); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Phase != NardyPhaseOver || g.Result == nil || g.Result.Winner != NardyWhite {
|
||
t.Fatalf("ожидалась победа белых, получено Phase=%v Result=%+v", g.Phase, g.Result)
|
||
}
|
||
}
|
||
|
||
func TestTurnSwitchesPlayerWhenDiceExhausted(t *testing.T) {
|
||
g := NewNardyGame(NardyShort, NardyWhite)
|
||
g.Board = [24]NardyPoint{}
|
||
g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
|
||
g.CurrentPlayer = NardyWhite
|
||
g.Phase = NardyPhaseMove
|
||
g.DiceLeft = []int{5}
|
||
|
||
if err := g.ApplyMove(NardyMove{From: 15, Die: 5}); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.CurrentPlayer != NardyBlack || g.Phase != NardyPhaseRoll {
|
||
t.Errorf("после использования всех костей ход должен был перейти к чёрным (фаза броска), получено CurrentPlayer=%v Phase=%v", g.CurrentPlayer, g.Phase)
|
||
}
|
||
}
|