go_games_collection/nardy_game_test.go

320 lines
12 KiB
Go
Raw Permalink 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 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)
}
}