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"
EliminatedByHeadToHeadCollision = "head-collision"
EliminatedByOutOfBounds = "wall-collision"
// TODO - Error consts
)
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 {
for i := 0; i < len(b.Snakes); i++ {
unoccupiedPoints := r.getUnoccupiedPoints(b)
if len(unoccupiedPoints) < len(b.Snakes)-i {
return errors.New("not enough empty squares to place snakes")
if len(unoccupiedPoints) <= 0 {
return errors.New("not enough space to place snake")
}
p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
for j := 0; j < SnakeStartSize; j++ {
@ -107,8 +109,7 @@ func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
}
func (r *StandardRuleset) placeFood(b *BoardState) error {
r.spawnFood(b, len(b.Snakes))
return nil
return r.spawnFood(b, len(b.Snakes))
}
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{}
switch move.Move {
case MoveDown:
@ -227,8 +233,10 @@ func (r *StandardRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error {
func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
for i := 0; i < len(b.Snakes); i++ {
if b.Snakes[i].EliminatedCause == NotEliminated {
b.Snakes[i].Health = b.Snakes[i].Health - 1
}
}
return nil
}
@ -313,18 +321,22 @@ func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
}
func (r *StandardRuleset) feedSnakes(b *BoardState) error {
var newFood []Point
var tail Point
newFood := []Point{}
for _, food := range b.Food {
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 {
foodHasBeenEaten = true
// Update snake
snake.Body = append(snake.Body, snake.Body[len(snake.Body)-1])
snake.Health = SnakeMaxHealth
tail = snake.Body[len(snake.Body)-1]
snake.Body = append(snake.Body, tail)
}
}
// 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 {
if rand.Float32() <= FoodSpawnChance {
r.spawnFood(b, n)
return r.spawnFood(b, n)
}
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++ {
unoccupiedPoints := r.getUnoccupiedPoints(b)
if len(unoccupiedPoints) > 0 {
@ -352,6 +364,7 @@ func (r *StandardRuleset) spawnFood(b *BoardState, n int) {
b.Food = append(b.Food, newFood)
}
}
return nil
}
func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []Point {

View file

@ -31,12 +31,39 @@ func TestSanity(t *testing.T) {
require.Len(t, state.Snakes, 0)
}
// Create Board
// REsolveMoves
// move, reduce, feed, need to consider dead snakes
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) {
@ -60,7 +87,7 @@ func TestPlaceSnakes(t *testing.T) {
Height: 1,
Snakes: make([]Snake, 2),
},
errors.New("not enough empty squares to place snakes"),
errors.New("not enough space to place snake"),
},
{
&BoardState{
@ -84,7 +111,7 @@ func TestPlaceSnakes(t *testing.T) {
Height: 2,
Snakes: make([]Snake, 51),
},
errors.New("not enough empty squares to place snakes"),
errors.New("not enough space to place snake"),
},
{
&BoardState{
@ -152,6 +179,12 @@ func TestPlaceSnakes(t *testing.T) {
if err == nil {
for i := 0; i < len(test.BoardState.Snakes); i++ {
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)
require.NoError(t, err)
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,6 +261,12 @@ func TestMoveSnakes(t *testing.T) {
Body: []Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}},
Health: 222222,
},
{
ID: "three",
Body: []Point{{0, 0}},
Health: 1,
EliminatedCause: EliminatedByOutOfBounds,
},
},
}
@ -230,26 +275,33 @@ func TestMoveSnakes(t *testing.T) {
ExpectedOne []Point
MoveTwo string
ExpectedTwo []Point
MoveThree string
ExpectedThree []Point
}{
{
MoveUp, []Point{{10, 109}, {10, 110}},
MoveDown, []Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}},
MoveUp, []Point{{0, 0}},
},
{
MoveRight, []Point{{11, 109}, {10, 109}},
MoveLeft, []Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}},
MoveUp, []Point{{0, 0}},
},
{
MoveRight, []Point{{12, 109}, {11, 109}},
MoveLeft, []Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}},
MoveUp, []Point{{0, 0}},
},
{
MoveRight, []Point{{13, 109}, {12, 109}},
MoveLeft, []Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}},
MoveUp, []Point{{0, 0}},
},
{
MoveUp, []Point{{13, 108}, {13, 109}},
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{
{ID: "one", Move: test.MoveOne},
{ID: "two", Move: test.MoveTwo},
{ID: "three", Move: test.MoveThree},
}
err := r.moveSnakes(b, moves)
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(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[1].Body, 4)
require.Len(t, b.Snakes[2].Body, 1)
require.Equal(t, len(b.Snakes[0].Body), len(test.ExpectedOne))
for i, e := range test.ExpectedOne {
@ -276,6 +333,10 @@ func TestMoveSnakes(t *testing.T) {
for i, e := range test.ExpectedTwo {
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}},
Move: "asdf",
Move: "invalid",
Expected: []Point{{0, -1}},
},
{
@ -373,6 +434,11 @@ func TestReduceSnakeHealth(t *testing.T) {
Body: []Point{{5, 8}, {6, 8}, {7, 8}},
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.Equal(t, b.Snakes[0].Health, int32(98))
require.Equal(t, b.Snakes[1].Health, int32(1))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b)
require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(97))
require.Equal(t, b.Snakes[1].Health, int32(0))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b)
require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(96))
require.Equal(t, b.Snakes[1].Health, int32(-1))
require.Equal(t, b.Snakes[2].Health, int32(50))
err = r.reduceSnakeHealth(b)
require.NoError(t, err)
require.Equal(t, b.Snakes[0].Health, int32(95))
require.Equal(t, b.Snakes[1].Health, int32(-2))
require.Equal(t, b.Snakes[2].Health, int32(50))
}
func TestSnakeHasStarved(t *testing.T) {
@ -777,18 +847,87 @@ func TestEliminateSnakes(t *testing.T) {
}
func TestFeedSnakes(t *testing.T) {
r := StandardRuleset{}
b := &BoardState{
tests := []struct {
Name string
Snakes []Snake
Food []Point
ExpectedSnakes []Snake
ExpectedFood []Point
}{
{
Name: "snake not on food",
Snakes: []Snake{
{Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}}},
{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}},
},
}
r := StandardRuleset{}
for _, test := range tests {
b := &BoardState{
Snakes: test.Snakes,
Food: test.Food,
}
err := r.feedSnakes(b)
require.NoError(t, err)
require.Equal(t, 0, len(b.Food))
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) {