More test coverage for StandardRuleset.

This commit is contained in:
Brad Van Vugt 2020-01-03 11:39:19 -08:00
parent 010b3aa08f
commit 7d29514b6c
2 changed files with 273 additions and 28 deletions

View file

@ -16,8 +16,9 @@ const (
SnakeStartSize = 3 SnakeStartSize = 3
// bvanvugt - TODO: Just return formatted strings instead of codes? // bvanvugt - TODO: Just return formatted strings instead of codes?
EliminatedByColliision = "snake-collision" NotEliminated = ""
EliminatedBySelfColliision = "snake-self-collision" EliminatedByCollision = "snake-collision"
EliminatedBySelfCollision = "snake-self-collision"
EliminatedByStarvation = "starvation" EliminatedByStarvation = "starvation"
EliminatedByHeadToHeadCollision = "head-collision" EliminatedByHeadToHeadCollision = "head-collision"
EliminatedByOutOfBounds = "wall-collision" EliminatedByOutOfBounds = "wall-collision"
@ -42,7 +43,7 @@ func (r *StandardRuleset) CreateInitialBoardState(width int32, height int32, sna
return nil, err return nil, err
} }
err = r.placeInitialFood(initialBoardState) err = r.placeFood(initialBoardState)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -105,6 +106,11 @@ func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
return nil return nil
} }
func (r *StandardRuleset) placeFood(b *BoardState) error {
r.spawnFood(b, len(b.Snakes))
return nil
}
func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool { func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall { if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall {
return true return true
@ -118,11 +124,6 @@ func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
return false return false
} }
func (r *StandardRuleset) placeInitialFood(b *BoardState) error {
r.spawnFood(b, len(b.Snakes))
return nil
}
func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error) { func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
// We specifically want to copy prevState, so as not to alter it directly. // We specifically want to copy prevState, so as not to alter it directly.
nextState := &BoardState{ nextState := &BoardState{
@ -232,27 +233,47 @@ func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
} }
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error { func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
for _, snake := range b.Snakes { for i := 0; i < len(b.Snakes); i++ {
if r.snakeHasStarved(&snake) { snake := &b.Snakes[i]
if len(snake.Body) <= 0 {
return errors.New("snake is length zero")
}
if r.snakeHasStarved(snake) {
snake.EliminatedCause = EliminatedByStarvation snake.EliminatedCause = EliminatedByStarvation
} else if r.snakeIsOutOfBounds(&snake, b.Width, b.Height) { continue
}
if r.snakeIsOutOfBounds(snake, b.Width, b.Height) {
snake.EliminatedCause = EliminatedByOutOfBounds snake.EliminatedCause = EliminatedByOutOfBounds
} else { continue
for _, other := range b.Snakes { }
if r.snakeHasBodyCollided(&snake, &other) {
// Always check body collisions before head-to-heads
for j := 0; j < len(b.Snakes); j++ {
other := &b.Snakes[j]
if r.snakeHasBodyCollided(snake, other) {
if snake.ID == other.ID { if snake.ID == other.ID {
snake.EliminatedCause = EliminatedBySelfColliision snake.EliminatedCause = EliminatedBySelfCollision
} else { } else {
snake.EliminatedCause = EliminatedByColliision snake.EliminatedCause = EliminatedByCollision
} }
break break
} else if r.snakeHasLostHeadToHead(&snake, &other) { }
}
if snake.EliminatedCause != NotEliminated {
continue
}
// Always check body collisions before head-to-heads
for j := 0; j < len(b.Snakes); j++ {
other := &b.Snakes[j]
if snake.ID != other.ID && r.snakeHasLostHeadToHead(snake, other) {
snake.EliminatedCause = EliminatedByHeadToHeadCollision snake.EliminatedCause = EliminatedByHeadToHeadCollision
break break
} }
} }
} }
}
return nil return nil
} }

View file

@ -32,12 +32,7 @@ func TestSanity(t *testing.T) {
} }
// Create Board // Create Board
// placeSnakes
// placeFood
// knownBoardSize
// REsolveMoves // REsolveMoves
// eliminateSnakes
// --> related subs
// move, reduce, feed, need to consider dead snakes // move, reduce, feed, need to consider dead snakes
func TestCreateInitialBoardState(t *testing.T) { func TestCreateInitialBoardState(t *testing.T) {
@ -162,6 +157,54 @@ func TestPlaceSnakes(t *testing.T) {
} }
} }
func TestPlaceFood(t *testing.T) {
tests := []struct {
BoardState *BoardState
ExpectedFood int
}{
{
&BoardState{
Width: 1,
Height: 1,
Snakes: make([]Snake, 1),
},
1,
},
{
&BoardState{
Width: 1,
Height: 2,
Snakes: make([]Snake, 2),
},
2,
},
{
&BoardState{
Width: 101,
Height: 202,
Snakes: make([]Snake, 17),
},
17,
},
{
&BoardState{
Width: 10,
Height: 20,
Snakes: make([]Snake, 305),
},
200,
},
}
r := StandardRuleset{}
for _, test := range tests {
require.Len(t, test.BoardState.Food, 0)
err := r.placeFood(test.BoardState)
require.NoError(t, err)
require.Equal(t, test.ExpectedFood, len(test.BoardState.Food))
}
}
func TestResolveMoves(t *testing.T) { func TestResolveMoves(t *testing.T) {
// TODO // TODO
} }
@ -236,6 +279,31 @@ func TestMoveSnakes(t *testing.T) {
} }
} }
func TestIsKnownBoardSize(t *testing.T) {
tests := []struct {
Width int32
Height int32
Expected bool
}{
{1, 1, false},
{0, 0, false},
{0, 45, false},
{45, 1, false},
{7, 7, true},
{11, 11, true},
{19, 19, true},
{7, 11, false},
{11, 19, false},
{19, 7, false},
}
r := StandardRuleset{}
for _, test := range tests {
result := r.isKnownBoardSize(&BoardState{Width: test.Width, Height: test.Height})
require.Equal(t, test.Expected, result)
}
}
func TestMoveSnakesDefault(t *testing.T) { func TestMoveSnakesDefault(t *testing.T) {
tests := []struct { tests := []struct {
Body []Point Body []Point
@ -552,6 +620,162 @@ func TestSnakeHasLostHeadToHead(t *testing.T) {
} }
func TestEliminateSnakes(t *testing.T) {
tests := []struct {
Snakes []Snake
ExpectedEliminatedCauses []string
Err error
}{
{
[]Snake{},
[]string{},
nil,
},
{
[]Snake{
Snake{},
},
[]string{NotEliminated},
errors.New("snake is length zero"),
},
{
[]Snake{
Snake{Body: []Point{{1, 1}}},
},
[]string{EliminatedByStarvation},
nil,
},
{
[]Snake{
Snake{Health: 1, Body: []Point{{1, 1}}},
},
[]string{NotEliminated},
nil,
},
{
[]Snake{
Snake{Health: 1, Body: []Point{{-1, 1}}},
},
[]string{EliminatedByOutOfBounds},
nil,
},
{
[]Snake{
Snake{Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
},
[]string{EliminatedBySelfCollision},
nil,
},
{
[]Snake{
Snake{Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
Snake{Health: 1, Body: []Point{{-1, 1}}},
},
[]string{
EliminatedBySelfCollision,
EliminatedByOutOfBounds},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{0, 2}, {0, 3}, {0, 4}}},
Snake{ID: "2", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
},
[]string{
EliminatedByCollision,
NotEliminated},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}}},
Snake{ID: "3", Health: 1, Body: []Point{{1, 1}}},
},
[]string{
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}, {0, 1}}},
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}, {1, 2}, {1, 3}}},
Snake{ID: "3", Health: 1, Body: []Point{{1, 1}}},
},
[]string{
EliminatedByHeadToHeadCollision,
NotEliminated,
EliminatedByHeadToHeadCollision,
},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {3, 3}}},
Snake{ID: "2", Health: 1, Body: []Point{{3, 3}, {2, 2}}},
Snake{ID: "3", Health: 1, Body: []Point{{2, 2}, {1, 1}}},
Snake{ID: "4", Health: 1, Body: []Point{{1, 1}, {4, 4}}},
Snake{ID: "5", Health: 1, Body: []Point{{4, 4}}}, // Body collision takes priority
},
[]string{
EliminatedByCollision,
EliminatedByCollision,
EliminatedByCollision,
EliminatedByCollision,
EliminatedByCollision,
},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
Snake{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}}},
Snake{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
},
[]string{
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
},
nil,
},
{
[]Snake{
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
Snake{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}, {6, 4}}},
Snake{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
},
[]string{
EliminatedByHeadToHeadCollision,
EliminatedByHeadToHeadCollision,
NotEliminated,
EliminatedByHeadToHeadCollision,
},
nil,
},
}
r := StandardRuleset{}
for _, test := range tests {
b := &BoardState{
Width: 10,
Height: 10,
Snakes: test.Snakes,
}
err := r.eliminateSnakes(b)
require.Equal(t, test.Err, err)
for i := 0; i < len(b.Snakes); i++ {
require.Equal(t, test.ExpectedEliminatedCauses[i], b.Snakes[i].EliminatedCause)
}
}
}
func TestFeedSnakes(t *testing.T) { func TestFeedSnakes(t *testing.T) {
r := StandardRuleset{} r := StandardRuleset{}
b := &BoardState{ b := &BoardState{