More test coverage for StandardRuleset.
This commit is contained in:
parent
010b3aa08f
commit
7d29514b6c
2 changed files with 273 additions and 28 deletions
67
standard.go
67
standard.go
|
|
@ -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,24 +233,44 @@ 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) {
|
|
||||||
if snake.ID == other.ID {
|
// Always check body collisions before head-to-heads
|
||||||
snake.EliminatedCause = EliminatedBySelfColliision
|
for j := 0; j < len(b.Snakes); j++ {
|
||||||
} else {
|
other := &b.Snakes[j]
|
||||||
snake.EliminatedCause = EliminatedByColliision
|
if r.snakeHasBodyCollided(snake, other) {
|
||||||
}
|
if snake.ID == other.ID {
|
||||||
break
|
snake.EliminatedCause = EliminatedBySelfCollision
|
||||||
} else if r.snakeHasLostHeadToHead(&snake, &other) {
|
} else {
|
||||||
snake.EliminatedCause = EliminatedByHeadToHeadCollision
|
snake.EliminatedCause = EliminatedByCollision
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
234
standard_test.go
234
standard_test.go
|
|
@ -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{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue