From 4df2c65432604ff7f24b45c86c5b3196e3f7c41d Mon Sep 17 00:00:00 2001 From: bvanvugt <1531419+bvanvugt@users.noreply.github.com> Date: Tue, 18 Jan 2022 20:21:21 +0000 Subject: [PATCH] Restrict fixed food spawns to only locations further from center. --- board.go | 75 ++++++++++++++++++++++++++++++++------------------- board_test.go | 46 ++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/board.go b/board.go index ce37d7c..8d9fa1e 100644 --- a/board.go +++ b/board.go @@ -1,6 +1,8 @@ package rules -import "math/rand" +import ( + "math/rand" +) type BoardState struct { Turn int32 @@ -156,7 +158,9 @@ func PlaceFoodAutomatically(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++ { snakeHead := b.Snakes[i].Body[0] possibleFoodLocations := []Point{ @@ -165,8 +169,9 @@ func PlaceFoodFixed(b *BoardState) error { {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 { isOccupiedAlready := false for _, food := range b.Food { @@ -175,8 +180,14 @@ func PlaceFoodFixed(b *BoardState) error { 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) } } @@ -192,7 +203,6 @@ func PlaceFoodFixed(b *BoardState) error { // 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 := getUnoccupiedPoints(b, true) for _, point := range unoccupiedPoints { if point == centerCoord { @@ -220,17 +230,26 @@ func PlaceFoodRandomly(b *BoardState, n int32) error { return nil } -func isKnownBoardSize(b *BoardState) bool { - if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall { - return true +func absInt32(n int32) int32 { + if n < 0 { + return -n } - if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium { - return true + return n +} + +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 true - } - return false + return evenUnoccupiedPoints } func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { @@ -284,17 +303,19 @@ func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { return unoccupiedPoints } -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) - } - } - return evenUnoccupiedPoints +func getDistanceBetweenPoints(a, b Point) int32 { + return absInt32(a.X-b.X) + absInt32(a.Y-b.Y) +} + +func isKnownBoardSize(b *BoardState) bool { + if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall { + return true + } + if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium { + return true + } + if b.Height == BoardSizeLarge && b.Width == BoardSizeLarge { + return true + } + return false } diff --git a/board_test.go b/board_test.go index 5250059..936d6e0 100644 --- a/board_test.go +++ b/board_test.go @@ -2,6 +2,7 @@ package rules import ( "fmt" + "sort" "testing" "github.com/stretchr/testify/require" @@ -390,6 +391,8 @@ func TestPlaceFoodFixed(t *testing.T) { require.NoError(t, err) 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 for _, snake := range test.BoardState.Snakes { head := snake.Body[0] @@ -403,6 +406,8 @@ func TestPlaceFoodFixed(t *testing.T) { for _, food := range test.BoardState.Food { if food == bottomLeft || food == topLeft || food == bottomRight || food == topRight { foundFoodInTwoMoves = true + // Ensure it's not closer to the center than snake + require.True(t, getDistanceBetweenPoints(head, midPoint) <= getDistanceBetweenPoints(food, midPoint)) break } } @@ -411,7 +416,6 @@ func TestPlaceFoodFixed(t *testing.T) { // 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 @@ -442,6 +446,9 @@ func TestPlaceFoodFixedNoRoom(t *testing.T) { }, 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) require.NoError(t, err) 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 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. err = PlaceFoodFixed(boardState) 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) {