From 5aec70de2b400425f6398e20781f004e4d03d965 Mon Sep 17 00:00:00 2001 From: Brad Van Vugt <1531419+bvanvugt@users.noreply.github.com> Date: Tue, 21 Jul 2020 17:11:12 -0700 Subject: [PATCH] Prioritize self-collisions over other collisions. Fixes #16. --- squad_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ standard.go | 20 ++++++++++++------- standard_test.go | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/squad_test.go b/squad_test.go index 7b9bfd4..5b51b80 100644 --- a/squad_test.go +++ b/squad_test.go @@ -338,3 +338,54 @@ func TestSquadIsGameOver(t *testing.T) { require.Equal(t, test.Expected, actual) } } + +func TestIssue16Regression(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) + } +} diff --git a/standard.go b/standard.go index 9bb84d6..a3fe9ef 100644 --- a/standard.go +++ b/standard.go @@ -349,15 +349,21 @@ func (r *StandardRuleset) maybeEliminateSnakes(b *BoardState) error { continue } - // Always check body collisions before head-to-heads + // Check for self-collisions first + if r.snakeHasBodyCollided(snake, snake) { + snake.EliminatedCause = EliminatedBySelfCollision + snake.EliminatedBy = snake.ID + continue + } + + // Check for body collisions with other snakes second for _, otherIndex := range snakeIndicesByLength { other := &b.Snakes[otherIndex] + if snake.ID == other.ID { + continue + } if r.snakeHasBodyCollided(snake, other) { - if snake.ID == other.ID { - snake.EliminatedCause = EliminatedBySelfCollision - } else { - snake.EliminatedCause = EliminatedByCollision - } + snake.EliminatedCause = EliminatedByCollision snake.EliminatedBy = other.ID break } @@ -366,7 +372,7 @@ func (r *StandardRuleset) maybeEliminateSnakes(b *BoardState) error { continue } - // Always check head-to-heads after body collisions + // Check for head-to-heads last for _, otherIndex := range snakeIndicesByLength { other := &b.Snakes[otherIndex] if snake.ID != other.ID && r.snakeHasLostHeadToHead(snake, other) { diff --git a/standard_test.go b/standard_test.go index 2da82ce..dab6a5e 100644 --- a/standard_test.go +++ b/standard_test.go @@ -1266,6 +1266,45 @@ func TestMaybeEliminateSnakes(t *testing.T) { } } +func TestMaybeEliminateSnakesPriority(t *testing.T) { + tests := []struct { + Snakes []Snake + ExpectedEliminatedCauses []string + ExpectedEliminatedBy []string + }{ + { + []Snake{ + {ID: "1", Health: 0, Body: []Point{{-1, 0}, {0, 0}, {1, 0}}}, + {ID: "2", Health: 1, Body: []Point{{-1, 0}, {0, 0}, {1, 0}}}, + {ID: "3", Health: 1, Body: []Point{{1, 0}, {0, 0}, {1, 0}}}, + {ID: "4", Health: 1, Body: []Point{{1, 0}, {1, 1}, {1, 2}}}, + {ID: "5", Health: 1, Body: []Point{{2, 2}, {2, 1}, {2, 0}}}, + {ID: "6", Health: 1, Body: []Point{{2, 2}, {2, 3}, {2, 4}, {2, 5}}}, + }, + []string{ + EliminatedByStarvation, + EliminatedByOutOfBounds, + EliminatedBySelfCollision, + EliminatedByCollision, + EliminatedByHeadToHeadCollision, + NotEliminated, + }, + []string{"", "", "3", "1", "6", ""}, + }, + } + + r := StandardRuleset{} + for _, test := range tests { + b := &BoardState{Width: 10, Height: 10, Snakes: test.Snakes} + err := r.maybeEliminateSnakes(b) + require.NoError(t, err) + for i, snake := range b.Snakes { + require.Equal(t, test.ExpectedEliminatedCauses[i], snake.EliminatedCause) + require.Equal(t, test.ExpectedEliminatedBy[i], snake.EliminatedBy) + } + } +} + func TestMaybeFeedSnakes(t *testing.T) { tests := []struct { Name string