Add "EliminatedBy" to snake eliminations. (#11)
* add eliminated by * add test * make sure largest snake is listed as eliminator on head to head collisions * remove unused type def * Reduce memory usage during elimination checks. Co-authored-by: Daniel Steuernol <dlsteuer@gmail.com>
This commit is contained in:
parent
a241c526b2
commit
8153585f57
4 changed files with 70 additions and 21 deletions
1
go.sum
1
go.sum
|
|
@ -5,6 +5,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ type Snake struct {
|
||||||
Body []Point
|
Body []Point
|
||||||
Health int32
|
Health int32
|
||||||
EliminatedCause string
|
EliminatedCause string
|
||||||
|
EliminatedBy string
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoardState struct {
|
type BoardState struct {
|
||||||
|
|
|
||||||
26
standard.go
26
standard.go
|
|
@ -3,6 +3,7 @@ package rules
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StandardRuleset struct{}
|
type StandardRuleset struct{}
|
||||||
|
|
@ -256,6 +257,19 @@ func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
||||||
|
// First order snake indices by length.
|
||||||
|
// In multi-collision scenarios we want to always attribute elimination to the longest snake.
|
||||||
|
snakeIndicesByLength := make([]int, len(b.Snakes))
|
||||||
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
|
snakeIndicesByLength[i] = i
|
||||||
|
}
|
||||||
|
sort.Slice(snakeIndicesByLength, func(i int, j int) bool {
|
||||||
|
lenI := len(b.Snakes[snakeIndicesByLength[i]].Body)
|
||||||
|
lenJ := len(b.Snakes[snakeIndicesByLength[j]].Body)
|
||||||
|
return lenI > lenJ
|
||||||
|
})
|
||||||
|
|
||||||
|
// Iterate through snakes checking for eliminations.
|
||||||
for i := 0; i < len(b.Snakes); i++ {
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
snake := &b.Snakes[i]
|
snake := &b.Snakes[i]
|
||||||
if len(snake.Body) <= 0 {
|
if len(snake.Body) <= 0 {
|
||||||
|
|
@ -273,14 +287,15 @@ func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always check body collisions before head-to-heads
|
// Always check body collisions before head-to-heads
|
||||||
for j := 0; j < len(b.Snakes); j++ {
|
for _, otherIndex := range snakeIndicesByLength {
|
||||||
other := &b.Snakes[j]
|
other := &b.Snakes[otherIndex]
|
||||||
if r.snakeHasBodyCollided(snake, other) {
|
if r.snakeHasBodyCollided(snake, other) {
|
||||||
if snake.ID == other.ID {
|
if snake.ID == other.ID {
|
||||||
snake.EliminatedCause = EliminatedBySelfCollision
|
snake.EliminatedCause = EliminatedBySelfCollision
|
||||||
} else {
|
} else {
|
||||||
snake.EliminatedCause = EliminatedByCollision
|
snake.EliminatedCause = EliminatedByCollision
|
||||||
}
|
}
|
||||||
|
snake.EliminatedBy = other.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,11 +303,12 @@ func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always check body collisions before head-to-heads
|
// Always check head-to-heads after body collisions
|
||||||
for j := 0; j < len(b.Snakes); j++ {
|
for _, otherIndex := range snakeIndicesByLength {
|
||||||
other := &b.Snakes[j]
|
other := &b.Snakes[otherIndex]
|
||||||
if snake.ID != other.ID && r.snakeHasLostHeadToHead(snake, other) {
|
if snake.ID != other.ID && r.snakeHasLostHeadToHead(snake, other) {
|
||||||
snake.EliminatedCause = EliminatedByHeadToHeadCollision
|
snake.EliminatedCause = EliminatedByHeadToHeadCollision
|
||||||
|
snake.EliminatedBy = other.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -763,61 +763,78 @@ func TestSnakeHasLostHeadToHead(t *testing.T) {
|
||||||
|
|
||||||
func TestEliminateSnakes(t *testing.T) {
|
func TestEliminateSnakes(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
Name string
|
||||||
Snakes []Snake
|
Snakes []Snake
|
||||||
ExpectedEliminatedCauses []string
|
ExpectedEliminatedCauses []string
|
||||||
|
ExpectedEliminatedBy []string
|
||||||
Err error
|
Err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
"Empty",
|
||||||
[]Snake{},
|
[]Snake{},
|
||||||
[]string{},
|
[]string{},
|
||||||
|
[]string{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Zero Snake",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{},
|
Snake{},
|
||||||
},
|
},
|
||||||
[]string{NotEliminated},
|
[]string{NotEliminated},
|
||||||
|
[]string{""},
|
||||||
errors.New("snake is length zero"),
|
errors.New("snake is length zero"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Single Starvation",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{Body: []Point{{1, 1}}},
|
Snake{ID: "1", Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedByStarvation},
|
[]string{EliminatedByStarvation},
|
||||||
|
[]string{""},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Not Eliminated",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{Health: 1, Body: []Point{{1, 1}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{NotEliminated},
|
[]string{NotEliminated},
|
||||||
|
[]string{""},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Out of Bounds",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{Health: 1, Body: []Point{{-1, 1}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{-1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedByOutOfBounds},
|
[]string{EliminatedByOutOfBounds},
|
||||||
|
[]string{""},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Self Collision",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedBySelfCollision},
|
[]string{EliminatedBySelfCollision},
|
||||||
|
[]string{"1"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Multiple Separate Deaths",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
||||||
Snake{Health: 1, Body: []Point{{-1, 1}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{-1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedBySelfCollision,
|
EliminatedBySelfCollision,
|
||||||
EliminatedByOutOfBounds},
|
EliminatedByOutOfBounds},
|
||||||
|
[]string{"1", ""},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"Other Collision",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{0, 2}, {0, 3}, {0, 4}}},
|
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}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
|
||||||
|
|
@ -825,9 +842,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByCollision,
|
EliminatedByCollision,
|
||||||
NotEliminated},
|
NotEliminated},
|
||||||
|
[]string{"2", ""},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"All Eliminated Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}}},
|
||||||
|
|
@ -838,9 +857,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
},
|
},
|
||||||
|
[]string{"2", "1", "1"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"One Snake wins Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}, {0, 1}}},
|
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: "2", Health: 1, Body: []Point{{1, 1}, {1, 2}, {1, 3}}},
|
||||||
|
|
@ -851,9 +872,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
NotEliminated,
|
NotEliminated,
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
},
|
},
|
||||||
|
[]string{"2", "", "2"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"All Snakes Body Eliminated",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {3, 3}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {3, 3}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{3, 3}, {2, 2}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{3, 3}, {2, 2}}},
|
||||||
|
|
@ -868,9 +891,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
EliminatedByCollision,
|
EliminatedByCollision,
|
||||||
EliminatedByCollision,
|
EliminatedByCollision,
|
||||||
},
|
},
|
||||||
|
[]string{"4", "1", "2", "3", "4"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"All Snakes Eliminated Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
||||||
|
|
@ -883,9 +908,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
},
|
},
|
||||||
|
[]string{"2", "1", "1", "1"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"4 Snakes Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
||||||
|
|
@ -898,12 +925,14 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
NotEliminated,
|
NotEliminated,
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
},
|
},
|
||||||
|
[]string{"3", "3", "", "3"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Width: 10,
|
Width: 10,
|
||||||
Height: 10,
|
Height: 10,
|
||||||
|
|
@ -911,9 +940,11 @@ func TestEliminateSnakes(t *testing.T) {
|
||||||
}
|
}
|
||||||
err := r.eliminateSnakes(b)
|
err := r.eliminateSnakes(b)
|
||||||
require.Equal(t, test.Err, err)
|
require.Equal(t, test.Err, err)
|
||||||
for i := 0; i < len(b.Snakes); i++ {
|
for i, snake := range b.Snakes {
|
||||||
require.Equal(t, test.ExpectedEliminatedCauses[i], b.Snakes[i].EliminatedCause)
|
require.Equal(t, test.ExpectedEliminatedCauses[i], snake.EliminatedCause)
|
||||||
|
require.Equal(t, test.ExpectedEliminatedBy[i], snake.EliminatedBy)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue