package rules import ( "testing" "github.com/stretchr/testify/require" ) func TestSquadRulesetInterface(t *testing.T) { var _ Ruleset = (*SquadRuleset)(nil) } func TestSquadCreateNextBoardStateSanity(t *testing.T) { boardState := &BoardState{} r := SquadRuleset{} _, err := r.CreateNextBoardState(boardState, []SnakeMove{}) require.NoError(t, err) } func TestSquadResurrectSquadBodyCollisionsSanity(t *testing.T) { boardState := &BoardState{} r := SquadRuleset{} err := r.resurrectSquadBodyCollisions(boardState) require.NoError(t, err) } func TestSquadSharedAttributesSanity(t *testing.T) { boardState := &BoardState{} r := SquadRuleset{} err := r.shareSquadAttributes(boardState) require.NoError(t, err) } func TestSquadAllowBodyCollisions(t *testing.T) { testSnakes := []struct { SnakeID string SquadID string EliminatedCause string EliminatedBy string ExpectedCause string ExpectedBy string }{ // Red Squad {"R1", "red", NotEliminated, "", NotEliminated, ""}, {"R2", "red", EliminatedByCollision, "R1", NotEliminated, ""}, // Blue Squad {"B1", "blue", EliminatedByCollision, "R1", EliminatedByCollision, "R1"}, {"B2", "blue", EliminatedBySelfCollision, "B1", EliminatedBySelfCollision, "B1"}, {"B4", "blue", EliminatedByOutOfBounds, "", EliminatedByOutOfBounds, ""}, {"B3", "blue", NotEliminated, "", NotEliminated, ""}, // More Red Squad {"R3", "red", NotEliminated, "", NotEliminated, ""}, {"R4", "red", EliminatedByCollision, "R4", EliminatedByCollision, "R4"}, // this is an error case but worth testing {"R5", "red", EliminatedByCollision, "R4", NotEliminated, ""}, // Green Squad {"G1", "green", EliminatedByStarvation, "x", EliminatedByStarvation, "x"}, // Yellow Squad {"Y1", "yellow", EliminatedByCollision, "B4", EliminatedByCollision, "B4"}, } boardState := &BoardState{} squadMap := make(map[string]string) for _, testSnake := range testSnakes { boardState.Snakes = append(boardState.Snakes, Snake{ ID: testSnake.SnakeID, EliminatedCause: testSnake.EliminatedCause, EliminatedBy: testSnake.EliminatedBy, }) squadMap[testSnake.SnakeID] = testSnake.SquadID } require.Equal(t, len(squadMap), len(boardState.Snakes), "squad map is wrong size, error in test setup") r := SquadRuleset{SquadMap: squadMap, AllowBodyCollisions: true} err := r.resurrectSquadBodyCollisions(boardState) require.NoError(t, err) require.Equal(t, len(boardState.Snakes), len(testSnakes)) for i := 0; i < len(boardState.Snakes); i++ { require.Equal( t, testSnakes[i].ExpectedCause, boardState.Snakes[i].EliminatedCause, "snake %s failed shared eliminated cause", testSnakes[i].SnakeID, ) require.Equal( t, testSnakes[i].ExpectedBy, boardState.Snakes[i].EliminatedBy, "snake %s failed shared eliminated by", testSnakes[i].SnakeID, ) } } func TestSquadAllowBodyCollisionsEliminatedByNotSet(t *testing.T) { boardState := &BoardState{ Snakes: []Snake{ Snake{ID: "1", EliminatedCause: EliminatedByCollision}, Snake{ID: "2"}, }, } r := SquadRuleset{ AllowBodyCollisions: true, SquadMap: map[string]string{ "1": "red", "2": "red", }, } err := r.resurrectSquadBodyCollisions(boardState) require.Error(t, err) } func TestSquadShareSquadHealth(t *testing.T) { testSnakes := []struct { SnakeID string SquadID string Health int32 ExpectedHealth int32 }{ // Red Squad {"R1", "red", 11, 88}, {"R2", "red", 22, 88}, // Blue Squad {"B1", "blue", 33, 333}, {"B2", "blue", 333, 333}, {"B3", "blue", 3, 333}, // More Red Squad {"R3", "red", 77, 88}, {"R4", "red", 88, 88}, // Green Squad {"G1", "green", 100, 100}, // Yellow Squad {"Y1", "yellow", 1, 1}, } boardState := &BoardState{} squadMap := make(map[string]string) for _, testSnake := range testSnakes { boardState.Snakes = append(boardState.Snakes, Snake{ ID: testSnake.SnakeID, Health: testSnake.Health, }) squadMap[testSnake.SnakeID] = testSnake.SquadID } require.Equal(t, len(squadMap), len(boardState.Snakes), "squad map is wrong size, error in test setup") r := SquadRuleset{SharedHealth: true, SquadMap: squadMap} err := r.shareSquadAttributes(boardState) require.NoError(t, err) require.Equal(t, len(boardState.Snakes), len(testSnakes)) for i := 0; i < len(boardState.Snakes); i++ { require.Equal( t, testSnakes[i].ExpectedHealth, boardState.Snakes[i].Health, "snake %s failed shared health", testSnakes[i].SnakeID, ) } } func TestSquadSharedLength(t *testing.T) { testSnakes := []struct { SnakeID string SquadID string Body []Point ExpectedBody []Point }{ // Red Squad {"R1", "red", []Point{{1, 1}}, []Point{{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}}}, {"R2", "red", []Point{{2, 2}, {2, 2}}, []Point{{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}}}, // Blue Squad {"B1", "blue", []Point{{1, 1}, {1, 2}}, []Point{{1, 1}, {1, 2}}}, {"B2", "blue", []Point{{2, 1}}, []Point{{2, 1}, {2, 1}}}, {"B3", "blue", []Point{{3, 3}}, []Point{{3, 3}, {3, 3}}}, // More Red Squad {"R3", "red", []Point{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}, []Point{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}}, {"R4", "red", []Point{{4, 4}}, []Point{{4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}}}, // Green Squad {"G1", "green", []Point{{1, 1}}, []Point{{1, 1}}}, // Yellow Squad {"Y1", "yellow", []Point{{1, 3}, {1, 4}, {1, 5}, {1, 6}}, []Point{{1, 3}, {1, 4}, {1, 5}, {1, 6}}}, } boardState := &BoardState{} squadMap := make(map[string]string) for _, testSnake := range testSnakes { boardState.Snakes = append(boardState.Snakes, Snake{ ID: testSnake.SnakeID, Body: testSnake.Body, }) squadMap[testSnake.SnakeID] = testSnake.SquadID } require.Equal(t, len(squadMap), len(boardState.Snakes), "squad map is wrong size, error in test setup") r := SquadRuleset{SharedLength: true, SquadMap: squadMap} err := r.shareSquadAttributes(boardState) require.NoError(t, err) require.Equal(t, len(boardState.Snakes), len(testSnakes)) for i := 0; i < len(boardState.Snakes); i++ { require.Equal( t, testSnakes[i].ExpectedBody, boardState.Snakes[i].Body, "snake %s failed shared length", testSnakes[i].SnakeID, ) } } func TestSquadSharedElimination(t *testing.T) { testSnakes := []struct { SnakeID string SquadID string EliminatedCause string EliminatedBy string ExpectedCause string ExpectedBy string }{ // Red Squad {"R1", "red", NotEliminated, "", EliminatedBySquad, ""}, {"R2", "red", EliminatedByHeadToHeadCollision, "y", EliminatedByHeadToHeadCollision, "y"}, // Blue Squad {"B1", "blue", EliminatedByOutOfBounds, "z", EliminatedByOutOfBounds, "z"}, {"B2", "blue", NotEliminated, "", EliminatedBySquad, ""}, {"B3", "blue", NotEliminated, "", EliminatedBySquad, ""}, // More Red Squad {"R3", "red", NotEliminated, "", EliminatedBySquad, ""}, {"R4", "red", EliminatedByCollision, "B1", EliminatedByCollision, "B1"}, // Green Squad {"G1", "green", EliminatedByStarvation, "x", EliminatedByStarvation, "x"}, // Yellow Squad {"Y1", "yellow", NotEliminated, "", NotEliminated, ""}, } boardState := &BoardState{} squadMap := make(map[string]string) for _, testSnake := range testSnakes { boardState.Snakes = append(boardState.Snakes, Snake{ ID: testSnake.SnakeID, EliminatedCause: testSnake.EliminatedCause, EliminatedBy: testSnake.EliminatedBy, }) squadMap[testSnake.SnakeID] = testSnake.SquadID } require.Equal(t, len(squadMap), len(boardState.Snakes), "squad map is wrong size, error in test setup") r := SquadRuleset{SharedElimination: true, SquadMap: squadMap} err := r.shareSquadAttributes(boardState) require.NoError(t, err) require.Equal(t, len(boardState.Snakes), len(testSnakes)) for i := 0; i < len(boardState.Snakes); i++ { require.Equal( t, testSnakes[i].ExpectedCause, boardState.Snakes[i].EliminatedCause, "snake %s failed shared eliminated cause", testSnakes[i].SnakeID, ) require.Equal( t, testSnakes[i].ExpectedBy, boardState.Snakes[i].EliminatedBy, "snake %s failed shared eliminated by", testSnakes[i].SnakeID, ) } } func TestSquadSharedAttributesErrorLengthZero(t *testing.T) { boardState := &BoardState{ Snakes: []Snake{ Snake{ID: "1"}, Snake{ID: "2"}, }, } r := SquadRuleset{ SharedLength: true, SquadMap: map[string]string{ "1": "red", "2": "red", }, } err := r.shareSquadAttributes(boardState) require.Error(t, err) } func TestSquadIsGameOver(t *testing.T) { tests := []struct { Snakes []Snake SquadMap map[string]string Expected bool }{ {[]Snake{}, map[string]string{}, true}, {[]Snake{{ID: "R1"}}, map[string]string{"R1": "red"}, true}, { []Snake{{ID: "R1"}, {ID: "R2"}, {ID: "R3"}}, map[string]string{"R1": "red", "R2": "red", "R3": "red"}, true, }, { []Snake{{ID: "R1"}, {ID: "B1"}}, map[string]string{"R1": "red", "B1": "blue"}, false, }, { []Snake{{ID: "R1"}, {ID: "B1"}, {ID: "B2"}, {ID: "G1"}}, map[string]string{"R1": "red", "B1": "blue", "B2": "blue", "G1": "green"}, false, }, { []Snake{ {ID: "R1", EliminatedCause: EliminatedByOutOfBounds}, {ID: "B1", EliminatedCause: EliminatedBySelfCollision, EliminatedBy: "B1"}, {ID: "B2", EliminatedCause: EliminatedByCollision, EliminatedBy: "B2"}, {ID: "G1"}, }, map[string]string{"R1": "red", "B1": "blue", "B2": "blue", "G1": "green"}, true, }, } for _, test := range tests { b := &BoardState{ Height: 11, Width: 11, Snakes: test.Snakes, Food: []Point{}, } r := SquadRuleset{SquadMap: test.SquadMap} actual, err := r.IsGameOver(b) require.NoError(t, err) require.Equal(t, test.Expected, actual) } } func TestRegressionIssue16(t *testing.T) { // This is a specific test case to detect this issue: // https://github.com/BattlesnakeOfficial/rules/issues/16 boardState := &BoardState{ Width: 11, Height: 11, Snakes: []Snake{ {ID: "teamBoi", Health: 10, Body: []Point{{1, 4}, {1, 3}, {0, 3}, {0, 2}, {1, 2}, {2, 2}}}, {ID: "Node-Red-Bellied-Black-Snake", Health: 10, Body: []Point{{1, 8}, {2, 8}, {2, 9}, {3, 9}, {4, 9}, {4, 10}}}, {ID: "Crash Override", Health: 10, Body: []Point{{2, 7}, {2, 6}, {3, 6}, {4, 6}, {4, 5}, {5, 5}, {6, 5}}}, {ID: "Zero Cool", Health: 10, Body: []Point{{6, 5}, {5, 5}, {5, 4}, {5, 3}, {4, 3}, {3, 3}, {3, 4}}}, }, } squadMap := map[string]string{ "teamBoi": "BirdSnakers", "Node-Red-Bellied-Black-Snake": "BirdSnakers", "Crash Override": "Hackers", "Zero Cool": "Hackers", } snakeMoves := []SnakeMove{ {ID: "teamBoi", Move: "down"}, {ID: "Node-Red-Bellied-Black-Snake", Move: "left"}, {ID: "Crash Override", Move: "left"}, {ID: "Zero Cool", Move: "left"}, } require.Equal(t, len(squadMap), len(boardState.Snakes), "squad map is wrong size, error in test setup") r := SquadRuleset{ AllowBodyCollisions: true, SquadMap: squadMap, } nextBoardState, err := r.CreateNextBoardState(boardState, snakeMoves) require.NoError(t, err) require.Equal(t, len(boardState.Snakes), len(nextBoardState.Snakes)) expectedSnakes := []Snake{ {ID: "teamBoi", Body: []Point{{1, 5}, {1, 4}, {1, 3}, {0, 3}, {0, 2}, {1, 2}}}, {ID: "Node-Red-Bellied-Black-Snake", Body: []Point{{0, 8}, {1, 8}, {2, 8}, {2, 9}, {3, 9}, {4, 9}}}, {ID: "Crash Override", Body: []Point{{1, 7}, {2, 7}, {2, 6}, {3, 6}, {4, 6}, {4, 5}, {5, 5}}}, {ID: "Zero Cool", Body: []Point{{5, 5}, {6, 5}, {5, 5}, {5, 4}, {5, 3}, {4, 3}, {3, 3}}, EliminatedCause: EliminatedBySelfCollision, EliminatedBy: "Zero Cool"}, } for i, snake := range nextBoardState.Snakes { require.Equal(t, expectedSnakes[i].ID, snake.ID, snake.ID) require.Equal(t, expectedSnakes[i].Body, snake.Body, snake.ID) require.Equal(t, expectedSnakes[i].EliminatedCause, snake.EliminatedCause, snake.ID) require.Equal(t, expectedSnakes[i].EliminatedBy, snake.EliminatedBy, snake.ID) } }