DEV-1479 ensure snake elimination turn is set (#93)

* ensure snake elimination turn is set

- centralise elimination update logic to a single place to ensure consistency

* doc the method

* testing
This commit is contained in:
Torben 2022-07-21 14:26:56 -07:00 committed by GitHub
parent 663c377cc4
commit e1289af5fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 73 deletions

View file

@ -57,6 +57,7 @@ func (prevState *BoardState) Clone() *BoardState {
nextState.Snakes[i].Health = prevState.Snakes[i].Health
nextState.Snakes[i].Body = append([]Point{}, prevState.Snakes[i].Body...)
nextState.Snakes[i].EliminatedCause = prevState.Snakes[i].EliminatedCause
nextState.Snakes[i].EliminatedOnTurn = prevState.Snakes[i].EliminatedOnTurn
nextState.Snakes[i].EliminatedBy = prevState.Snakes[i].EliminatedBy
}
return nextState
@ -551,3 +552,13 @@ func getDistanceBetweenPoints(a, b Point) int {
func isSquareBoard(b *BoardState) bool {
return b.Width == b.Height
}
// EliminateSnake updates a snake's state to reflect that it was eliminated.
// - "cause" identifies what type of event caused the snake to be eliminated
// - "by" identifies which snake (if any, use empty string "" if none) eliminated the snake.
// - "turn" is the turn number that this snake was eliminated on.
func EliminateSnake(s *Snake, cause, by string, turn int) {
s.EliminatedCause = cause
s.EliminatedBy = by
s.EliminatedOnTurn = turn
}

View file

@ -881,3 +881,11 @@ func TestPlaceFoodRandomly(t *testing.T) {
require.NoError(t, err)
require.Equal(t, len(b.Food), 0)
}
func TestEliminateSnake(t *testing.T) {
s := &Snake{}
EliminateSnake(s, "test-cause", "", 2)
require.Equal(t, "test-cause", s.EliminatedCause)
require.Equal(t, "", s.EliminatedBy)
require.Equal(t, 2, s.EliminatedOnTurn)
}

View file

@ -15,6 +15,7 @@ func TestConstrictorRulesetInterface(t *testing.T) {
var constrictorMoveAndCollideMAD = gameTestCase{
"Constrictor Case Move and Collide",
&BoardState{
Turn: 41,
Width: 10,
Height: 10,
Snakes: []Snake{
@ -47,6 +48,7 @@ var constrictorMoveAndCollideMAD = gameTestCase{
Health: 100,
EliminatedCause: EliminatedByCollision,
EliminatedBy: "two",
EliminatedOnTurn: 42,
},
{
ID: "two",
@ -54,6 +56,7 @@ var constrictorMoveAndCollideMAD = gameTestCase{
Health: 100,
EliminatedCause: EliminatedByCollision,
EliminatedBy: "one",
EliminatedOnTurn: 42,
},
},
Food: []Point{},

View file

@ -116,3 +116,34 @@ func TestSoloCreateNextBoardState(t *testing.T) {
gc.requireValidNextState(t, NewRulesetBuilder().PipelineRuleset(GameTypeSolo, NewPipeline(soloRulesetStages...)))
}
}
// Test a snake running right into the wall is properly eliminated
func TestSoloEliminationOutOfBounds(t *testing.T) {
r := SoloRuleset{}
// Using MaxRand is important because it ensures that the snakes are consistently placed in a way this test will work.
// Actually random placement could result in the assumptions made by this test being incorrect.
initialState, err := CreateDefaultBoardState(MaxRand, 2, 2, []string{"one"})
require.NoError(t, err)
_, next, err := r.Execute(
initialState,
r.Settings(),
[]SnakeMove{{ID: "one", Move: "right"}},
)
require.NoError(t, err)
require.NotNil(t, initialState)
ended, next, err := r.Execute(
next,
r.Settings(),
[]SnakeMove{{ID: "one", Move: "right"}},
)
require.NoError(t, err)
require.NotNil(t, initialState)
require.True(t, ended)
require.Equal(t, EliminatedByOutOfBounds, next.Snakes[0].EliminatedCause)
require.Equal(t, "", next.Snakes[0].EliminatedBy)
require.Equal(t, 1, next.Snakes[0].EliminatedOnTurn)
}

View file

@ -184,7 +184,7 @@ func DamageHazardsStandard(b *BoardState, settings Settings, moves []SnakeMove)
snake.Health = SnakeMaxHealth
}
if snakeIsOutOfHealth(snake) {
snake.EliminatedCause = EliminatedByOutOfHealth
EliminateSnake(snake, EliminatedByOutOfHealth, "", b.Turn+1)
}
}
}
@ -221,12 +221,12 @@ func EliminateSnakesStandard(b *BoardState, settings Settings, moves []SnakeMove
}
if snakeIsOutOfHealth(snake) {
snake.EliminatedCause = EliminatedByOutOfHealth
EliminateSnake(snake, EliminatedByOutOfHealth, "", b.Turn+1)
continue
}
if snakeIsOutOfBounds(snake, b.Width, b.Height) {
snake.EliminatedCause = EliminatedByOutOfBounds
EliminateSnake(snake, EliminatedByOutOfBounds, "", b.Turn+1)
continue
}
}
@ -306,8 +306,7 @@ func EliminateSnakesStandard(b *BoardState, settings Settings, moves []SnakeMove
for i := 0; i < len(b.Snakes); i++ {
snake := &b.Snakes[i]
if snake.ID == elimination.ID {
snake.EliminatedCause = elimination.Cause
snake.EliminatedBy = elimination.By
EliminateSnake(snake, elimination.Cause, elimination.By, b.Turn+1)
break
}
}

View file

@ -164,6 +164,7 @@ var standardCaseMoveEatAndGrow = gameTestCase{
var standardMoveAndCollideMAD = gameTestCase{
"Standard Case Move and Collide",
&BoardState{
Turn: 0,
Width: 10,
Height: 10,
Snakes: []Snake{
@ -196,6 +197,7 @@ var standardMoveAndCollideMAD = gameTestCase{
Health: 98,
EliminatedCause: EliminatedByCollision,
EliminatedBy: "two",
EliminatedOnTurn: 1,
},
{
ID: "two",
@ -203,6 +205,7 @@ var standardMoveAndCollideMAD = gameTestCase{
Health: 98,
EliminatedCause: EliminatedByCollision,
EliminatedBy: "one",
EliminatedOnTurn: 1,
},
},
Food: []Point{},
@ -279,6 +282,7 @@ func TestEatingOnLastMove(t *testing.T) {
Body: []Point{{3, 1}, {3, 2}, {3, 3}},
Health: 0,
EliminatedCause: EliminatedByOutOfHealth,
EliminatedOnTurn: 1,
},
},
Food: []Point{{9, 9}},
@ -315,6 +319,7 @@ func TestHeadToHeadOnFood(t *testing.T) {
}{
{
&BoardState{
Turn: 41,
Width: 10,
Height: 10,
Snakes: []Snake{
@ -346,6 +351,7 @@ func TestHeadToHeadOnFood(t *testing.T) {
Health: 100,
EliminatedCause: EliminatedByHeadToHeadCollision,
EliminatedBy: "two",
EliminatedOnTurn: 42,
},
{
ID: "two",
@ -353,6 +359,7 @@ func TestHeadToHeadOnFood(t *testing.T) {
Health: 100,
EliminatedCause: EliminatedByHeadToHeadCollision,
EliminatedBy: "one",
EliminatedOnTurn: 42,
},
},
Food: []Point{{9, 9}},
@ -360,6 +367,7 @@ func TestHeadToHeadOnFood(t *testing.T) {
},
{
&BoardState{
Turn: 41,
Width: 10,
Height: 10,
Snakes: []Snake{
@ -391,6 +399,7 @@ func TestHeadToHeadOnFood(t *testing.T) {
Health: 100,
EliminatedCause: EliminatedByHeadToHeadCollision,
EliminatedBy: "two",
EliminatedOnTurn: 42,
},
{
ID: "two",
@ -1277,6 +1286,7 @@ func TestMaybeDamageHazards(t *testing.T) {
Food []Point
ExpectedEliminatedCauses []string
ExpectedEliminatedByIDs []string
ExpectedEliminatedOnTurns []int
}{
{},
{
@ -1284,12 +1294,14 @@ func TestMaybeDamageHazards(t *testing.T) {
Hazards: []Point{},
ExpectedEliminatedCauses: []string{NotEliminated},
ExpectedEliminatedByIDs: []string{""},
ExpectedEliminatedOnTurns: []int{0},
},
{
Snakes: []Snake{{Body: []Point{{0, 0}}}},
Hazards: []Point{{0, 0}},
ExpectedEliminatedCauses: []string{EliminatedByOutOfHealth},
ExpectedEliminatedByIDs: []string{""},
ExpectedEliminatedOnTurns: []int{42},
},
{
Snakes: []Snake{{Body: []Point{{0, 0}}}},
@ -1297,12 +1309,14 @@ func TestMaybeDamageHazards(t *testing.T) {
Food: []Point{{0, 0}},
ExpectedEliminatedCauses: []string{NotEliminated},
ExpectedEliminatedByIDs: []string{""},
ExpectedEliminatedOnTurns: []int{0},
},
{
Snakes: []Snake{{Body: []Point{{0, 0}, {1, 0}, {2, 0}}}},
Hazards: []Point{{1, 0}, {2, 0}},
ExpectedEliminatedCauses: []string{NotEliminated},
ExpectedEliminatedByIDs: []string{""},
ExpectedEliminatedOnTurns: []int{0},
},
{
Snakes: []Snake{
@ -1312,6 +1326,7 @@ func TestMaybeDamageHazards(t *testing.T) {
Hazards: []Point{{1, 0}, {2, 0}, {3, 4}, {3, 5}, {3, 6}},
ExpectedEliminatedCauses: []string{NotEliminated, NotEliminated},
ExpectedEliminatedByIDs: []string{"", ""},
ExpectedEliminatedOnTurns: []int{0, 0},
},
{
Snakes: []Snake{
@ -1321,17 +1336,20 @@ func TestMaybeDamageHazards(t *testing.T) {
Hazards: []Point{{3, 3}},
ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByOutOfHealth},
ExpectedEliminatedByIDs: []string{"", ""},
ExpectedEliminatedOnTurns: []int{0, 42},
},
}
for _, test := range tests {
b := &BoardState{Snakes: test.Snakes, Hazards: test.Hazards, Food: test.Food}
b := &BoardState{Turn: 41, Snakes: test.Snakes, Hazards: test.Hazards, Food: test.Food}
r := StandardRuleset{HazardDamagePerTurn: 100}
_, err := DamageHazardsStandard(b, r.Settings(), mockSnakeMoves())
require.NoError(t, err)
for i, snake := range b.Snakes {
require.Equal(t, test.ExpectedEliminatedCauses[i], snake.EliminatedCause)
require.Equal(t, test.ExpectedEliminatedByIDs[i], snake.EliminatedBy)
require.Equal(t, test.ExpectedEliminatedOnTurns[i], snake.EliminatedOnTurn)
}
}