Restrict fixed food spawns to only locations further from center.

This commit is contained in:
bvanvugt 2022-01-18 20:21:21 +00:00
parent 020303a8dd
commit 4df2c65432
2 changed files with 88 additions and 33 deletions

View file

@ -1,6 +1,8 @@
package rules package rules
import "math/rand" import (
"math/rand"
)
type BoardState struct { type BoardState struct {
Turn int32 Turn int32
@ -156,7 +158,9 @@ func PlaceFoodAutomatically(b *BoardState) error {
} }
func PlaceFoodFixed(b *BoardState) error { func PlaceFoodFixed(b *BoardState) error {
// Place 1 food within exactly 2 moves of each snake centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
// Place 1 food within exactly 2 moves of each snake, but never towards the center
for i := 0; i < len(b.Snakes); i++ { for i := 0; i < len(b.Snakes); i++ {
snakeHead := b.Snakes[i].Body[0] snakeHead := b.Snakes[i].Body[0]
possibleFoodLocations := []Point{ possibleFoodLocations := []Point{
@ -165,8 +169,9 @@ func PlaceFoodFixed(b *BoardState) error {
{snakeHead.X + 1, snakeHead.Y - 1}, {snakeHead.X + 1, snakeHead.Y - 1},
{snakeHead.X + 1, snakeHead.Y + 1}, {snakeHead.X + 1, snakeHead.Y + 1},
} }
availableFoodLocations := []Point{}
// Remove any positions already occupied by food or closer to center
availableFoodLocations := []Point{}
for _, p := range possibleFoodLocations { for _, p := range possibleFoodLocations {
isOccupiedAlready := false isOccupiedAlready := false
for _, food := range b.Food { for _, food := range b.Food {
@ -175,8 +180,14 @@ func PlaceFoodFixed(b *BoardState) error {
break break
} }
} }
if isOccupiedAlready {
continue
}
// availableFoodLocations = append(availableFoodLocations, p)
if !isOccupiedAlready { snakeHeadToCenter := getDistanceBetweenPoints(snakeHead, centerCoord)
foodToCenter := getDistanceBetweenPoints(p, centerCoord)
if snakeHeadToCenter <= foodToCenter {
availableFoodLocations = append(availableFoodLocations, p) availableFoodLocations = append(availableFoodLocations, p)
} }
} }
@ -192,7 +203,6 @@ func PlaceFoodFixed(b *BoardState) error {
// Finally, always place 1 food in center of board for dramatic purposes // Finally, always place 1 food in center of board for dramatic purposes
isCenterOccupied := true isCenterOccupied := true
centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
unoccupiedPoints := getUnoccupiedPoints(b, true) unoccupiedPoints := getUnoccupiedPoints(b, true)
for _, point := range unoccupiedPoints { for _, point := range unoccupiedPoints {
if point == centerCoord { if point == centerCoord {
@ -220,17 +230,26 @@ func PlaceFoodRandomly(b *BoardState, n int32) error {
return nil return nil
} }
func isKnownBoardSize(b *BoardState) bool { func absInt32(n int32) int32 {
if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall { if n < 0 {
return true return -n
} }
if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium { return n
return true }
func getEvenUnoccupiedPoints(b *BoardState) []Point {
// Start by getting unoccupied points
unoccupiedPoints := getUnoccupiedPoints(b, true)
// Create a new array to hold points that are even
evenUnoccupiedPoints := []Point{}
for _, point := range unoccupiedPoints {
if ((point.X + point.Y) % 2) == 0 {
evenUnoccupiedPoints = append(evenUnoccupiedPoints, point)
}
} }
if b.Height == BoardSizeLarge && b.Width == BoardSizeLarge { return evenUnoccupiedPoints
return true
}
return false
} }
func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point {
@ -284,17 +303,19 @@ func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point {
return unoccupiedPoints return unoccupiedPoints
} }
func getEvenUnoccupiedPoints(b *BoardState) []Point { func getDistanceBetweenPoints(a, b Point) int32 {
// Start by getting unoccupied points return absInt32(a.X-b.X) + absInt32(a.Y-b.Y)
unoccupiedPoints := getUnoccupiedPoints(b, true) }
// Create a new array to hold points that are even func isKnownBoardSize(b *BoardState) bool {
evenUnoccupiedPoints := []Point{} if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall {
return true
for _, point := range unoccupiedPoints { }
if ((point.X + point.Y) % 2) == 0 { if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium {
evenUnoccupiedPoints = append(evenUnoccupiedPoints, point) return true
} }
} if b.Height == BoardSizeLarge && b.Width == BoardSizeLarge {
return evenUnoccupiedPoints return true
}
return false
} }

View file

@ -2,6 +2,7 @@ package rules
import ( import (
"fmt" "fmt"
"sort"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -390,6 +391,8 @@ func TestPlaceFoodFixed(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(test.BoardState.Snakes)+1, len(test.BoardState.Food)) require.Equal(t, len(test.BoardState.Snakes)+1, len(test.BoardState.Food))
midPoint := Point{(test.BoardState.Width - 1) / 2, (test.BoardState.Height - 1) / 2}
// Make sure every snake has food within 2 moves of it // Make sure every snake has food within 2 moves of it
for _, snake := range test.BoardState.Snakes { for _, snake := range test.BoardState.Snakes {
head := snake.Body[0] head := snake.Body[0]
@ -403,6 +406,8 @@ func TestPlaceFoodFixed(t *testing.T) {
for _, food := range test.BoardState.Food { for _, food := range test.BoardState.Food {
if food == bottomLeft || food == topLeft || food == bottomRight || food == topRight { if food == bottomLeft || food == topLeft || food == bottomRight || food == topRight {
foundFoodInTwoMoves = true foundFoodInTwoMoves = true
// Ensure it's not closer to the center than snake
require.True(t, getDistanceBetweenPoints(head, midPoint) <= getDistanceBetweenPoints(food, midPoint))
break break
} }
} }
@ -411,7 +416,6 @@ func TestPlaceFoodFixed(t *testing.T) {
// Make sure one food exists in center of board // Make sure one food exists in center of board
foundFoodInCenter := false foundFoodInCenter := false
midPoint := Point{(test.BoardState.Width - 1) / 2, (test.BoardState.Height - 1) / 2}
for _, food := range test.BoardState.Food { for _, food := range test.BoardState.Food {
if food == midPoint { if food == midPoint {
foundFoodInCenter = true foundFoodInCenter = true
@ -442,6 +446,9 @@ func TestPlaceFoodFixedNoRoom(t *testing.T) {
}, },
Food: []Point{}, Food: []Point{},
} }
// There are 3 possible spawn locations: {0, 0}, {0, 2}, {2, 0}
// So repeat calls to place food should fail after 3 successes
err = PlaceFoodFixed(boardState) err = PlaceFoodFixed(boardState)
require.NoError(t, err) require.NoError(t, err)
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
@ -457,14 +464,41 @@ func TestPlaceFoodFixedNoRoom(t *testing.T) {
boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food boardState.Food = boardState.Food[:len(boardState.Food)-1] // Center food
require.Equal(t, 3, len(boardState.Food)) require.Equal(t, 3, len(boardState.Food))
err = 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. // And now there should be no more room.
err = PlaceFoodFixed(boardState) err = PlaceFoodFixed(boardState)
require.Error(t, err) require.Error(t, err)
expectedFood := []Point{{0, 0}, {0, 2}, {2, 0}}
sort.Slice(boardState.Food, func(i, j int) bool {
if boardState.Food[i].X != boardState.Food[j].X {
return boardState.Food[i].X < boardState.Food[j].X
}
return boardState.Food[i].Y < boardState.Food[j].Y
})
require.Equal(t, expectedFood, boardState.Food)
}
func TestGetDistanceBetweenPoints(t *testing.T) {
tests := []struct {
A Point
B Point
Expected int32
}{
{Point{0, 0}, Point{0, 0}, 0},
{Point{0, 0}, Point{1, 0}, 1},
{Point{0, 0}, Point{0, 1}, 1},
{Point{0, 0}, Point{1, 1}, 2},
{Point{0, 0}, Point{4, 4}, 8},
{Point{0, 0}, Point{4, 6}, 10},
{Point{8, 0}, Point{8, 0}, 0},
{Point{8, 0}, Point{8, 8}, 8},
{Point{8, 0}, Point{0, 8}, 16},
}
for _, test := range tests {
require.Equal(t, getDistanceBetweenPoints(test.A, test.B), test.Expected)
require.Equal(t, getDistanceBetweenPoints(test.B, test.A), test.Expected)
}
} }
func TestIsKnownBoardSize(t *testing.T) { func TestIsKnownBoardSize(t *testing.T) {