Always spawn food within two moves of each snake on known board sizes.
This commit is contained in:
parent
70a8107a6f
commit
d0400fcb18
2 changed files with 235 additions and 0 deletions
60
standard.go
60
standard.go
|
|
@ -113,6 +113,66 @@ func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) placeFood(b *BoardState) error {
|
func (r *StandardRuleset) placeFood(b *BoardState) error {
|
||||||
|
if r.isKnownBoardSize(b) {
|
||||||
|
return r.placeFoodFixed(b)
|
||||||
|
}
|
||||||
|
return r.placeFoodRandomly(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) placeFoodFixed(b *BoardState) error {
|
||||||
|
// Place 1 food within exactly 2 moves of each snake
|
||||||
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
|
snakeHead := b.Snakes[i].Body[0]
|
||||||
|
possibleFoodLocations := []Point{
|
||||||
|
Point{snakeHead.X - 1, snakeHead.Y - 1},
|
||||||
|
Point{snakeHead.X - 1, snakeHead.Y + 1},
|
||||||
|
Point{snakeHead.X + 1, snakeHead.Y - 1},
|
||||||
|
Point{snakeHead.X + 1, snakeHead.Y + 1},
|
||||||
|
}
|
||||||
|
availableFoodLocations := []Point{}
|
||||||
|
|
||||||
|
for _, p := range possibleFoodLocations {
|
||||||
|
isOccupiedAlready := false
|
||||||
|
for _, food := range b.Food {
|
||||||
|
if food.X == p.X && food.Y == p.Y {
|
||||||
|
isOccupiedAlready = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isOccupiedAlready {
|
||||||
|
availableFoodLocations = append(availableFoodLocations, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(availableFoodLocations) <= 0 {
|
||||||
|
return errors.New("not enough space to place food")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select randomly from available locations
|
||||||
|
placedFood := availableFoodLocations[rand.Intn(len(availableFoodLocations))]
|
||||||
|
b.Food = append(b.Food, placedFood)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, always place 1 food in center of board for dramatic purposes
|
||||||
|
isCenterOccupied := true
|
||||||
|
centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
|
||||||
|
unoccupiedPoints := r.getUnoccupiedPoints(b)
|
||||||
|
for _, point := range unoccupiedPoints {
|
||||||
|
if point == centerCoord {
|
||||||
|
isCenterOccupied = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isCenterOccupied {
|
||||||
|
return errors.New("not enough space to place food")
|
||||||
|
}
|
||||||
|
b.Food = append(b.Food, centerCoord)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) placeFoodRandomly(b *BoardState) error {
|
||||||
return r.spawnFood(b, len(b.Snakes))
|
return r.spawnFood(b, len(b.Snakes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
175
standard_test.go
175
standard_test.go
|
|
@ -50,6 +50,7 @@ func TestCreateInitialBoardState(t *testing.T) {
|
||||||
{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")},
|
{1, 1, []string{"one", "two"}, 2, errors.New("not enough space to place snake")},
|
||||||
{1, 2, []string{"one", "two"}, 2, errors.New("not enough space to place snake")},
|
{1, 2, []string{"one", "two"}, 2, errors.New("not enough space to place snake")},
|
||||||
|
{BoardSizeSmall, BoardSizeSmall, []string{"one", "two"}, 3, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
|
|
@ -269,6 +270,50 @@ func TestPlaceFood(t *testing.T) {
|
||||||
},
|
},
|
||||||
200,
|
200,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeSmall,
|
||||||
|
Height: BoardSizeSmall,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{5, 1}}},
|
||||||
|
{Body: []Point{{5, 3}}},
|
||||||
|
{Body: []Point{{5, 5}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
4, // +1 because of fixed spawn locations
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeMedium,
|
||||||
|
Height: BoardSizeMedium,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
{Body: []Point{{1, 5}}},
|
||||||
|
{Body: []Point{{1, 9}}},
|
||||||
|
{Body: []Point{{5, 1}}},
|
||||||
|
{Body: []Point{{5, 9}}},
|
||||||
|
{Body: []Point{{9, 1}}},
|
||||||
|
{Body: []Point{{9, 5}}},
|
||||||
|
{Body: []Point{{9, 9}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
9, // +1 because of fixed spawn locations
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeLarge,
|
||||||
|
Height: BoardSizeLarge,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
{Body: []Point{{1, 9}}},
|
||||||
|
{Body: []Point{{1, 17}}},
|
||||||
|
{Body: []Point{{17, 1}}},
|
||||||
|
{Body: []Point{{17, 9}}},
|
||||||
|
{Body: []Point{{17, 17}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
7, // +1 because of fixed spawn locations
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
|
|
@ -286,6 +331,136 @@ func TestPlaceFood(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlaceFoodFixed(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
BoardState *BoardState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeSmall,
|
||||||
|
Height: BoardSizeSmall,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 3}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeMedium,
|
||||||
|
Height: BoardSizeMedium,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
{Body: []Point{{1, 5}}},
|
||||||
|
{Body: []Point{{9, 5}}},
|
||||||
|
{Body: []Point{{9, 9}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeLarge,
|
||||||
|
Height: BoardSizeLarge,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
{Body: []Point{{1, 9}}},
|
||||||
|
{Body: []Point{{1, 17}}},
|
||||||
|
{Body: []Point{{9, 1}}},
|
||||||
|
{Body: []Point{{9, 17}}},
|
||||||
|
{Body: []Point{{17, 1}}},
|
||||||
|
{Body: []Point{{17, 9}}},
|
||||||
|
{Body: []Point{{17, 17}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := StandardRuleset{}
|
||||||
|
for _, test := range tests {
|
||||||
|
require.Len(t, test.BoardState.Food, 0)
|
||||||
|
|
||||||
|
err := r.placeFoodFixed(test.BoardState)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(test.BoardState.Snakes)+1, len(test.BoardState.Food))
|
||||||
|
|
||||||
|
// Make sure every snake has food within 2 moves of it
|
||||||
|
for _, snake := range test.BoardState.Snakes {
|
||||||
|
head := snake.Body[0]
|
||||||
|
|
||||||
|
bottomLeft := Point{head.X - 1, head.Y - 1}
|
||||||
|
topLeft := Point{head.X - 1, head.Y + 1}
|
||||||
|
bottomRight := Point{head.X + 1, head.Y - 1}
|
||||||
|
topRight := Point{head.X + 1, head.Y + 1}
|
||||||
|
|
||||||
|
foundFoodInTwoMoves := false
|
||||||
|
for _, food := range test.BoardState.Food {
|
||||||
|
if food == bottomLeft || food == topLeft || food == bottomRight || food == topRight {
|
||||||
|
foundFoodInTwoMoves = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, foundFoodInTwoMoves)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure one food exists in center of board
|
||||||
|
foundFoodInCenter := false
|
||||||
|
midPoint := Point{(test.BoardState.Width - 1) / 2, (test.BoardState.Height - 1) / 2}
|
||||||
|
for _, food := range test.BoardState.Food {
|
||||||
|
if food == midPoint {
|
||||||
|
foundFoodInCenter = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, foundFoodInCenter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlaceFoodFixedNoRoom(t *testing.T) {
|
||||||
|
r := StandardRuleset{}
|
||||||
|
|
||||||
|
boardState := &BoardState{
|
||||||
|
Width: 3,
|
||||||
|
Height: 3,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
},
|
||||||
|
Food: []Point{},
|
||||||
|
}
|
||||||
|
err := r.placeFoodFixed(boardState)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
boardState = &BoardState{
|
||||||
|
Width: 7,
|
||||||
|
Height: 7,
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{1, 1}}},
|
||||||
|
},
|
||||||
|
Food: []Point{},
|
||||||
|
}
|
||||||
|
err = r.placeFoodFixed(boardState)
|
||||||
|
require.NoError(t, err)
|
||||||
|
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
|
||||||
|
require.Equal(t, 1, len(boardState.Food))
|
||||||
|
|
||||||
|
err = r.placeFoodFixed(boardState)
|
||||||
|
require.NoError(t, err)
|
||||||
|
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
|
||||||
|
require.Equal(t, 2, len(boardState.Food))
|
||||||
|
|
||||||
|
err = r.placeFoodFixed(boardState)
|
||||||
|
require.NoError(t, err)
|
||||||
|
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
|
||||||
|
require.Equal(t, 3, len(boardState.Food))
|
||||||
|
|
||||||
|
err = r.placeFoodFixed(boardState)
|
||||||
|
require.NoError(t, err)
|
||||||
|
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
|
||||||
|
require.Equal(t, 4, len(boardState.Food))
|
||||||
|
|
||||||
|
// And now there should be no more room.
|
||||||
|
err = r.placeFoodFixed(boardState)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateNextBoardState(t *testing.T) {
|
func TestCreateNextBoardState(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
prevState *BoardState
|
prevState *BoardState
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue