More test coverage.

This commit is contained in:
Brad Van Vugt 2020-01-03 12:56:33 -08:00
parent 9f411e097c
commit 72b6d78c93
2 changed files with 187 additions and 35 deletions

View file

@ -22,6 +22,8 @@ const (
EliminatedByStarvation = "starvation" EliminatedByStarvation = "starvation"
EliminatedByHeadToHeadCollision = "head-collision" EliminatedByHeadToHeadCollision = "head-collision"
EliminatedByOutOfBounds = "wall-collision" EliminatedByOutOfBounds = "wall-collision"
// TODO - Error consts
) )
func (r *StandardRuleset) CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) { func (r *StandardRuleset) CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) {
@ -95,8 +97,8 @@ func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error { func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
for i := 0; i < len(b.Snakes); i++ { for i := 0; i < len(b.Snakes); i++ {
unoccupiedPoints := r.getUnoccupiedPoints(b) unoccupiedPoints := r.getUnoccupiedPoints(b)
if len(unoccupiedPoints) < len(b.Snakes)-i { if len(unoccupiedPoints) <= 0 {
return errors.New("not enough empty squares to place snakes") return errors.New("not enough space to place snake")
} }
p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))] p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
for j := 0; j < SnakeStartSize; j++ { for j := 0; j < SnakeStartSize; j++ {
@ -107,8 +109,7 @@ func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
} }
func (r *StandardRuleset) placeFood(b *BoardState) error { func (r *StandardRuleset) placeFood(b *BoardState) error {
r.spawnFood(b, len(b.Snakes)) return r.spawnFood(b, len(b.Snakes))
return nil
} }
func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool { func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
@ -188,6 +189,11 @@ func (r *StandardRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error {
} }
} }
// Do not move eliminated snakes
if snake.EliminatedCause != NotEliminated {
continue
}
var newHead = Point{} var newHead = Point{}
switch move.Move { switch move.Move {
case MoveDown: case MoveDown:
@ -227,7 +233,9 @@ func (r *StandardRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error {
func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error { func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
for i := 0; i < len(b.Snakes); i++ { for i := 0; i < len(b.Snakes); i++ {
b.Snakes[i].Health = b.Snakes[i].Health - 1 if b.Snakes[i].EliminatedCause == NotEliminated {
b.Snakes[i].Health = b.Snakes[i].Health - 1
}
} }
return nil return nil
} }
@ -313,18 +321,22 @@ func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
} }
func (r *StandardRuleset) feedSnakes(b *BoardState) error { func (r *StandardRuleset) feedSnakes(b *BoardState) error {
var newFood []Point newFood := []Point{}
var tail Point
for _, food := range b.Food { for _, food := range b.Food {
foodHasBeenEaten := false foodHasBeenEaten := false
for _, snake := range b.Snakes { for i := 0; i < len(b.Snakes); i++ {
snake := &b.Snakes[i]
// Ignore eliminated and zero-length snakes, they can't eat.
if snake.EliminatedCause != NotEliminated || len(snake.Body) == 0 {
continue
}
if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y { if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y {
foodHasBeenEaten = true foodHasBeenEaten = true
// Update snake // Update snake
snake.Body = append(snake.Body, snake.Body[len(snake.Body)-1])
snake.Health = SnakeMaxHealth snake.Health = SnakeMaxHealth
tail = snake.Body[len(snake.Body)-1]
snake.Body = append(snake.Body, tail)
} }
} }
// Persist food to next BoardState if not eaten // Persist food to next BoardState if not eaten
@ -339,12 +351,12 @@ func (r *StandardRuleset) feedSnakes(b *BoardState) error {
func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error { func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error {
if rand.Float32() <= FoodSpawnChance { if rand.Float32() <= FoodSpawnChance {
r.spawnFood(b, n) return r.spawnFood(b, n)
} }
return nil return nil
} }
func (r *StandardRuleset) spawnFood(b *BoardState, n int) { func (r *StandardRuleset) spawnFood(b *BoardState, n int) error {
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
unoccupiedPoints := r.getUnoccupiedPoints(b) unoccupiedPoints := r.getUnoccupiedPoints(b)
if len(unoccupiedPoints) > 0 { if len(unoccupiedPoints) > 0 {
@ -352,6 +364,7 @@ func (r *StandardRuleset) spawnFood(b *BoardState, n int) {
b.Food = append(b.Food, newFood) b.Food = append(b.Food, newFood)
} }
} }
return nil
} }
func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []Point { func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []Point {

View file

@ -31,12 +31,39 @@ func TestSanity(t *testing.T) {
require.Len(t, state.Snakes, 0) require.Len(t, state.Snakes, 0)
} }
// Create Board
// REsolveMoves
// move, reduce, feed, need to consider dead snakes
func TestCreateInitialBoardState(t *testing.T) { func TestCreateInitialBoardState(t *testing.T) {
// TODO tests := []struct {
Height int32
Width int32
IDs []string
ExpectedNumFood int
Err error
}{
{1, 1, []string{"one"}, 0, nil},
{1, 2, []string{"one"}, 1, nil},
{9, 8, []string{"one"}, 1, nil},
{2, 2, []string{"one", "two"}, 2, nil},
{2, 2, []string{"one", "two"}, 2, nil},
{1, 1, []string{"one", "two"}, 2, errors.New("not enough space to place snake")},
}
r := StandardRuleset{}
for _, test := range tests {
state, err := r.CreateInitialBoardState(test.Width, test.Height, test.IDs)
require.Equal(t, test.Err, err)
if err != nil {
require.Nil(t, state)
continue
}
require.NotNil(t, state)
require.Equal(t, test.Width, state.Width)
require.Equal(t, test.Height, state.Height)
require.Equal(t, len(test.IDs), len(state.Snakes))
for i, id := range test.IDs {
require.Equal(t, id, state.Snakes[i].ID)
}
require.Len(t, state.Food, test.ExpectedNumFood)
}
} }
func TestPlaceSnakes(t *testing.T) { func TestPlaceSnakes(t *testing.T) {
@ -60,7 +87,7 @@ func TestPlaceSnakes(t *testing.T) {
Height: 1, Height: 1,
Snakes: make([]Snake, 2), Snakes: make([]Snake, 2),
}, },
errors.New("not enough empty squares to place snakes"), errors.New("not enough space to place snake"),
}, },
{ {
&BoardState{ &BoardState{
@ -84,7 +111,7 @@ func TestPlaceSnakes(t *testing.T) {
Height: 2, Height: 2,
Snakes: make([]Snake, 51), Snakes: make([]Snake, 51),
}, },
errors.New("not enough empty squares to place snakes"), errors.New("not enough space to place snake"),
}, },
{ {
&BoardState{ &BoardState{
@ -152,6 +179,12 @@ func TestPlaceSnakes(t *testing.T) {
if err == nil { if err == nil {
for i := 0; i < len(test.BoardState.Snakes); i++ { for i := 0; i < len(test.BoardState.Snakes); i++ {
require.Len(t, test.BoardState.Snakes[i].Body, 3) require.Len(t, test.BoardState.Snakes[i].Body, 3)
for _, point := range test.BoardState.Snakes[i].Body {
require.GreaterOrEqual(t, point.X, int32(0))
require.GreaterOrEqual(t, point.Y, int32(0))
require.Less(t, point.X, test.BoardState.Width)
require.Less(t, point.Y, test.BoardState.Height)
}
} }
} }
} }
@ -202,6 +235,12 @@ func TestPlaceFood(t *testing.T) {
err := r.placeFood(test.BoardState) err := r.placeFood(test.BoardState)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.ExpectedFood, len(test.BoardState.Food)) require.Equal(t, test.ExpectedFood, len(test.BoardState.Food))
for _, point := range test.BoardState.Food {
require.GreaterOrEqual(t, point.X, int32(0))
require.GreaterOrEqual(t, point.Y, int32(0))
require.Less(t, point.X, test.BoardState.Width)
require.Less(t, point.Y, test.BoardState.Height)
}
} }
} }
@ -222,34 +261,47 @@ func TestMoveSnakes(t *testing.T) {
Body: []Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}}, Body: []Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}},
Health: 222222, Health: 222222,
}, },
{
ID: "three",
Body: []Point{{0, 0}},
Health: 1,
EliminatedCause: EliminatedByOutOfBounds,
},
}, },
} }
tests := []struct { tests := []struct {
MoveOne string MoveOne string
ExpectedOne []Point ExpectedOne []Point
MoveTwo string MoveTwo string
ExpectedTwo []Point ExpectedTwo []Point
MoveThree string
ExpectedThree []Point
}{ }{
{ {
MoveUp, []Point{{10, 109}, {10, 110}}, MoveUp, []Point{{10, 109}, {10, 110}},
MoveDown, []Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}}, MoveDown, []Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}},
MoveUp, []Point{{0, 0}},
}, },
{ {
MoveRight, []Point{{11, 109}, {10, 109}}, MoveRight, []Point{{11, 109}, {10, 109}},
MoveLeft, []Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}}, MoveLeft, []Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}},
MoveUp, []Point{{0, 0}},
}, },
{ {
MoveRight, []Point{{12, 109}, {11, 109}}, MoveRight, []Point{{12, 109}, {11, 109}},
MoveLeft, []Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}}, MoveLeft, []Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}},
MoveUp, []Point{{0, 0}},
}, },
{ {
MoveRight, []Point{{13, 109}, {12, 109}}, MoveRight, []Point{{13, 109}, {12, 109}},
MoveLeft, []Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}}, MoveLeft, []Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}},
MoveUp, []Point{{0, 0}},
}, },
{ {
MoveUp, []Point{{13, 108}, {13, 109}}, MoveUp, []Point{{13, 108}, {13, 109}},
MoveDown, []Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}}, MoveDown, []Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}},
MoveUp, []Point{{0, 0}},
}, },
} }
@ -258,15 +310,20 @@ func TestMoveSnakes(t *testing.T) {
moves := []SnakeMove{ moves := []SnakeMove{
{ID: "one", Move: test.MoveOne}, {ID: "one", Move: test.MoveOne},
{ID: "two", Move: test.MoveTwo}, {ID: "two", Move: test.MoveTwo},
{ID: "three", Move: test.MoveThree},
} }
err := r.moveSnakes(b, moves) err := r.moveSnakes(b, moves)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, b.Snakes, 2) require.Len(t, b.Snakes, 3)
require.Equal(t, int32(111111), b.Snakes[0].Health) require.Equal(t, int32(111111), b.Snakes[0].Health)
require.Equal(t, int32(222222), b.Snakes[1].Health) require.Equal(t, int32(222222), b.Snakes[1].Health)
require.Equal(t, int32(1), b.Snakes[2].Health)
require.Len(t, b.Snakes[0].Body, 2) require.Len(t, b.Snakes[0].Body, 2)
require.Len(t, b.Snakes[1].Body, 4) require.Len(t, b.Snakes[1].Body, 4)
require.Len(t, b.Snakes[2].Body, 1)
require.Equal(t, len(b.Snakes[0].Body), len(test.ExpectedOne)) require.Equal(t, len(b.Snakes[0].Body), len(test.ExpectedOne))
for i, e := range test.ExpectedOne { for i, e := range test.ExpectedOne {
@ -276,6 +333,10 @@ func TestMoveSnakes(t *testing.T) {
for i, e := range test.ExpectedTwo { for i, e := range test.ExpectedTwo {
require.Equal(t, e, b.Snakes[1].Body[i]) require.Equal(t, e, b.Snakes[1].Body[i])
} }
require.Equal(t, len(b.Snakes[2].Body), len(test.ExpectedThree))
for i, e := range test.ExpectedThree {
require.Equal(t, e, b.Snakes[2].Body[i])
}
} }
} }
@ -312,7 +373,7 @@ func TestMoveSnakesDefault(t *testing.T) {
}{ }{
{ {
Body: []Point{{0, 0}}, Body: []Point{{0, 0}},
Move: "asdf", Move: "invalid",
Expected: []Point{{0, -1}}, Expected: []Point{{0, -1}},
}, },
{ {
@ -373,6 +434,11 @@ func TestReduceSnakeHealth(t *testing.T) {
Body: []Point{{5, 8}, {6, 8}, {7, 8}}, Body: []Point{{5, 8}, {6, 8}, {7, 8}},
Health: 2, Health: 2,
}, },
{
Body: []Point{{0, 0}, {0, 1}},
Health: 50,
EliminatedCause: EliminatedByCollision,
},
}, },
} }
@ -381,21 +447,25 @@ func TestReduceSnakeHealth(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(98)) require.Equal(t, b.Snakes[0].Health, int32(98))
require.Equal(t, b.Snakes[1].Health, int32(1)) require.Equal(t, b.Snakes[1].Health, int32(1))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b) err = r.reduceSnakeHealth(b)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(97)) require.Equal(t, b.Snakes[0].Health, int32(97))
require.Equal(t, b.Snakes[1].Health, int32(0)) require.Equal(t, b.Snakes[1].Health, int32(0))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b) err = r.reduceSnakeHealth(b)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(96)) require.Equal(t, b.Snakes[0].Health, int32(96))
require.Equal(t, b.Snakes[1].Health, int32(-1)) require.Equal(t, b.Snakes[1].Health, int32(-1))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b) err = r.reduceSnakeHealth(b)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(95)) require.Equal(t, b.Snakes[0].Health, int32(95))
require.Equal(t, b.Snakes[1].Health, int32(-2)) require.Equal(t, b.Snakes[1].Health, int32(-2))
require.Equal(t, b.Snakes[2].Health, int32(50))
} }
func TestSnakeHasStarved(t *testing.T) { func TestSnakeHasStarved(t *testing.T) {
@ -777,18 +847,87 @@ func TestEliminateSnakes(t *testing.T) {
} }
func TestFeedSnakes(t *testing.T) { func TestFeedSnakes(t *testing.T) {
r := StandardRuleset{} tests := []struct {
b := &BoardState{ Name string
Snakes: []Snake{ Snakes []Snake
{Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}}}, Food []Point
ExpectedSnakes []Snake
ExpectedFood []Point
}{
{
Name: "snake not on food",
Snakes: []Snake{
{Health: 5, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
Food: []Point{{3, 3}},
ExpectedSnakes: []Snake{
{Health: 5, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
ExpectedFood: []Point{{3, 3}},
},
{
Name: "snake on food",
Snakes: []Snake{
{Health: SnakeMaxHealth - 1, Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}}},
},
Food: []Point{{2, 1}},
ExpectedSnakes: []Snake{
{Health: SnakeMaxHealth, Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}, {2, 2}}},
},
ExpectedFood: []Point{},
},
{
Name: "food under body",
Snakes: []Snake{
{Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
Food: []Point{{0, 1}},
ExpectedSnakes: []Snake{
{Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
ExpectedFood: []Point{{0, 1}},
},
{
Name: "snake on food but already eliminated",
Snakes: []Snake{
{Body: []Point{{0, 0}, {0, 1}, {0, 2}}, EliminatedCause: "EliminatedByOutOfBounds"},
},
Food: []Point{{0, 0}},
ExpectedSnakes: []Snake{
{Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
ExpectedFood: []Point{{0, 0}},
},
{
Name: "multiple snakes on same food",
Snakes: []Snake{
{Health: SnakeMaxHealth, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
{Health: SnakeMaxHealth - 9, Body: []Point{{0, 0}, {1, 0}, {2, 0}}},
},
Food: []Point{{0, 0}, {4, 4}},
ExpectedSnakes: []Snake{
{Health: SnakeMaxHealth, Body: []Point{{0, 0}, {0, 1}, {0, 2}, {0, 2}}},
{Health: SnakeMaxHealth, Body: []Point{{0, 0}, {1, 0}, {2, 0}, {2, 0}}},
},
ExpectedFood: []Point{{4, 4}},
}, },
Food: []Point{{2, 1}},
} }
err := r.feedSnakes(b) r := StandardRuleset{}
require.NoError(t, err) for _, test := range tests {
require.Equal(t, 0, len(b.Food)) b := &BoardState{
Snakes: test.Snakes,
Food: test.Food,
}
err := r.feedSnakes(b)
require.NoError(t, err, test.Name)
require.Equal(t, len(test.ExpectedSnakes), len(b.Snakes), test.Name)
for i := 0; i < len(b.Snakes); i++ {
require.Equal(t, test.ExpectedSnakes[i].Health, b.Snakes[i].Health, test.Name)
require.Equal(t, test.ExpectedSnakes[i].Body, b.Snakes[i].Body, test.Name)
}
require.Equal(t, test.ExpectedFood, b.Food, test.Name)
}
} }
func TestGetUnoccupiedPoints(t *testing.T) { func TestGetUnoccupiedPoints(t *testing.T) {