diff --git a/board.go b/board.go index 3ff2823..b3f0e1f 100644 --- a/board.go +++ b/board.go @@ -432,7 +432,7 @@ func PlaceFoodFixed(rand Rand, b *BoardState) error { // Finally, always place 1 food in center of board for dramatic purposes isCenterOccupied := true - unoccupiedPoints := GetUnoccupiedPoints(b, true) + unoccupiedPoints := GetUnoccupiedPoints(b, true, false) for _, point := range unoccupiedPoints { if point == centerCoord { isCenterOccupied = false @@ -450,7 +450,7 @@ func PlaceFoodFixed(rand Rand, b *BoardState) error { // PlaceFoodRandomly adds up to n new food to the board in random unoccupied squares func PlaceFoodRandomly(rand Rand, b *BoardState, n int) error { for i := 0; i < n; i++ { - unoccupiedPoints := GetUnoccupiedPoints(b, false) + unoccupiedPoints := GetUnoccupiedPoints(b, false, false) if len(unoccupiedPoints) > 0 { newFood := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))] b.Food = append(b.Food, newFood) @@ -468,7 +468,7 @@ func absInt(n int) int { func GetEvenUnoccupiedPoints(b *BoardState) []Point { // Start by getting unoccupied points - unoccupiedPoints := GetUnoccupiedPoints(b, true) + unoccupiedPoints := GetUnoccupiedPoints(b, true, false) // Create a new array to hold points that are even evenUnoccupiedPoints := []Point{} @@ -494,7 +494,7 @@ func removeCenterCoord(b *BoardState, points []Point) []Point { return noCenterPoints } -func GetUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { +func GetUnoccupiedPoints(b *BoardState, includePossibleMoves bool, includeHazards bool) []Point { pointIsOccupied := map[int]map[int]bool{} for _, p := range b.Food { if _, xExists := pointIsOccupied[p.X]; !xExists { @@ -502,6 +502,7 @@ func GetUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { } pointIsOccupied[p.X][p.Y] = true } + for _, snake := range b.Snakes { if snake.EliminatedCause != NotEliminated { continue @@ -529,6 +530,15 @@ func GetUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point { } } + if includeHazards { + for _, p := range b.Hazards { + if _, xExists := pointIsOccupied[p.X]; !xExists { + pointIsOccupied[p.X] = map[int]bool{} + } + pointIsOccupied[p.X][p.Y] = true + } + } + unoccupiedPoints := []Point{} for x := 0; x < b.Width; x++ { for y := 0; y < b.Height; y++ { diff --git a/board_test.go b/board_test.go index 10a0008..730d831 100644 --- a/board_test.go +++ b/board_test.go @@ -254,7 +254,7 @@ func TestPlaceSnakesDefault(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprint(test.BoardState.Width, test.BoardState.Height, len(test.SnakeIDs)), func(t *testing.T) { - require.Equal(t, test.BoardState.Width*test.BoardState.Height, len(GetUnoccupiedPoints(test.BoardState, true))) + require.Equal(t, test.BoardState.Width*test.BoardState.Height, len(GetUnoccupiedPoints(test.BoardState, true, false))) err := PlaceSnakesAutomatically(MaxRand, test.BoardState, test.SnakeIDs) require.Equal(t, test.Err, err, "Snakes: %d", len(test.BoardState.Snakes)) if err == nil { @@ -769,10 +769,39 @@ func TestGetUnoccupiedPoints(t *testing.T) { }, []Point{{2, 1}}, }, + { + &BoardState{ + Height: 1, + Width: 1, + Hazards: []Point{{0, 0}}, + }, + []Point{}, + }, + { + &BoardState{ + Height: 2, + Width: 2, + Hazards: []Point{{1, 1}}, + }, + []Point{{0, 0}, {0, 1}, {1, 0}}, + }, + { + &BoardState{ + Height: 2, + Width: 3, + Food: []Point{{1, 1}, {2, 0}}, + Snakes: []Snake{ + {Body: []Point{{0, 0}, {1, 0}, {1, 1}}}, + {Body: []Point{{0, 1}}}, + }, + Hazards: []Point{{0, 0}, {1, 0}}, + }, + []Point{{2, 1}}, + }, } for _, test := range tests { - unoccupiedPoints := GetUnoccupiedPoints(test.Board, true) + unoccupiedPoints := GetUnoccupiedPoints(test.Board, true, true) require.Equal(t, len(test.Expected), len(unoccupiedPoints)) for i, e := range test.Expected { require.Equal(t, e, unoccupiedPoints[i]) diff --git a/maps/game_map.go b/maps/game_map.go index 94406af..ddeae74 100644 --- a/maps/game_map.go +++ b/maps/game_map.go @@ -44,6 +44,20 @@ func (d sizes) IsUnlimited() bool { return len(d) == 1 && d[0].Width == 0 } +func (d sizes) IsAllowable(Width int, Height int) bool { + if d.IsUnlimited() { + return true + } + + for _, size := range d { + if size.Width == uint(Width) && size.Height == uint(Height) { + return true + } + } + + return false +} + // AnySize creates sizes for a board that has no fixed sizes (supports unlimited sizes). func AnySize() sizes { return sizes{Dimensions{Width: 0, Height: 0}} diff --git a/maps/hazards.go b/maps/hazards.go index 2eb8de4..53b1ff9 100644 --- a/maps/hazards.go +++ b/maps/hazards.go @@ -12,7 +12,6 @@ func init() { globalRegistry.RegisterMap("hz_inner_wall", InnerBorderHazardsMap{}) globalRegistry.RegisterMap("hz_rings", ConcentricRingsHazardsMap{}) globalRegistry.RegisterMap("hz_columns", ColumnsHazardsMap{}) - globalRegistry.RegisterMap("hz_rivers_bridges", RiverAndBridgesHazardsMap{}) globalRegistry.RegisterMap("hz_spiral", SpiralHazardsMap{}) globalRegistry.RegisterMap("hz_scatter", ScatterFillMap{}) globalRegistry.RegisterMap("hz_grow_box", DirectionalExpandingBoxMap{}) @@ -558,314 +557,3 @@ func (m ExpandingScatterMap) UpdateBoard(lastBoardState *rules.BoardState, setti return nil } - -type RiverAndBridgesHazardsMap struct{} - -func (m RiverAndBridgesHazardsMap) ID() string { - return "hz_rivers_bridges" -} - -func (m RiverAndBridgesHazardsMap) Meta() Metadata { - return Metadata{ - Name: "hz_rivers_bridges", - Description: `Creates fixed maps that have a lake of hazard in the middle with rivers going in the cardinal directions. -Each river has one or two 1-square "bridges" over them`, - Author: "Battlesnake", - Version: 1, - MinPlayers: 1, - MaxPlayers: 12, - BoardSizes: FixedSizes(Dimensions{11, 11}, Dimensions{19, 19}, Dimensions{25, 25}), - Tags: []string{TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT}, - } -} - -func (m RiverAndBridgesHazardsMap) SetupBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { - width := lastBoardState.Width - height := lastBoardState.Height - hazards, ok := riversAndBridgesHazards[rules.Point{X: width, Y: height}] - if !ok { - return rules.RulesetError("board size is not supported by this map") - } - startPositions, ok := riversAndBridgesStartPositions[rules.Point{X: width, Y: height}] - if !ok { - return rules.RulesetError("board size is not supported by this map") - } - - numSnakes := len(lastBoardState.Snakes) - if numSnakes == 0 { - return rules.RulesetError("too few snakes - at least one snake must be present") - } - - rand := settings.GetRand(0) - - snakeIDs := make([]string, 0, len(lastBoardState.Snakes)) - for _, snake := range lastBoardState.Snakes { - snakeIDs = append(snakeIDs, snake.ID) - } - - tempBoardState := rules.NewBoardState(width, height) - tempBoardState.Snakes = make([]rules.Snake, len(snakeIDs)) - - for i := 0; i < len(snakeIDs); i++ { - tempBoardState.Snakes[i] = rules.Snake{ - ID: snakeIDs[i], - Health: rules.SnakeMaxHealth, - } - } - err := rules.PlaceSnakesInQuadrants(rand, tempBoardState, startPositions) - if err != nil { - return err - } - - err = rules.PlaceFoodFixed(rand, tempBoardState) - if err != nil { - return err - } - - // Copy food from temp board state - for _, f := range tempBoardState.Food { - // skip the center food - if f.X == lastBoardState.Width/2 && f.Y == lastBoardState.Height/2 { - continue - } - editor.AddFood(f) - } - - // Copy snakes from temp board state - for _, snake := range tempBoardState.Snakes { - editor.PlaceSnake(snake.ID, snake.Body, snake.Health) - } - - for _, p := range hazards { - editor.AddHazard(p) - } - - return nil -} - -func (m RiverAndBridgesHazardsMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { - rand := settings.GetRand(lastBoardState.Turn) - - foodNeeded := checkFoodNeedingPlacement(rand, settings, lastBoardState) - if foodNeeded > 0 { - pts := m.getUnoccupiedPoints(lastBoardState) - placeFoodRandomlyAtPositions(rand, lastBoardState, editor, foodNeeded, pts) - } - - return nil -} - -func (m RiverAndBridgesHazardsMap) getUnoccupiedPoints(lastBoardState *rules.BoardState) []rules.Point { - unoccupiedPoints := rules.GetUnoccupiedPoints(lastBoardState, false) - - var totallyUnoccupiedPoints []rules.Point - // we want to avoid placing food on hazards in this map - for _, p := range unoccupiedPoints { - isHazard := false - for _, h := range lastBoardState.Hazards { - if p == h { - isHazard = true - break - } - } - - if !isHazard { - totallyUnoccupiedPoints = append(totallyUnoccupiedPoints, p) - } - } - - return totallyUnoccupiedPoints -} - -var riversAndBridgesStartPositions = map[rules.Point][][]rules.Point{ - {X: 11, Y: 11}: { - { - {X: 1, Y: 1}, - {X: 3, Y: 3}, - {X: 1, Y: 3}, - }, - { - {X: 9, Y: 9}, - {X: 7, Y: 7}, - {X: 9, Y: 7}, - }, - { - {X: 1, Y: 9}, - {X: 3, Y: 7}, - {X: 3, Y: 9}, - }, - { - {X: 9, Y: 3}, - {X: 9, Y: 1}, - {X: 7, Y: 3}, - }, - }, - {X: 19, Y: 19}: { - { - {X: 1, Y: 1}, - {X: 5, Y: 1}, - {X: 1, Y: 5}, - {X: 5, Y: 5}, - }, - { - {X: 17, Y: 1}, - {X: 17, Y: 5}, - {X: 13, Y: 5}, - {X: 13, Y: 1}, - }, - { - {X: 1, Y: 17}, - {X: 5, Y: 17}, - {X: 1, Y: 13}, - {X: 5, Y: 13}, - }, - { - {X: 17, Y: 17}, - {X: 17, Y: 13}, - {X: 13, Y: 17}, - {X: 13, Y: 13}, - }, - }, - {X: 25, Y: 25}: { - { - {X: 1, Y: 1}, - {X: 9, Y: 9}, - {X: 9, Y: 1}, - {X: 1, Y: 9}, - }, - { - {X: 23, Y: 23}, - {X: 15, Y: 15}, - {X: 23, Y: 15}, - {X: 15, Y: 23}, - }, - { - {X: 15, Y: 1}, - {X: 15, Y: 9}, - {X: 23, Y: 9}, - {X: 23, Y: 1}, - }, - { - {X: 9, Y: 23}, - {X: 1, Y: 23}, - {X: 9, Y: 15}, - {X: 1, Y: 15}, - }, - }, -} - -var riversAndBridgesHazards = map[rules.Point][]rules.Point{ - {X: 11, Y: 11}: { - {X: 5, Y: 10}, - {X: 5, Y: 9}, - {X: 5, Y: 7}, - {X: 5, Y: 6}, - {X: 5, Y: 5}, - {X: 5, Y: 4}, - {X: 5, Y: 3}, - {X: 5, Y: 0}, - {X: 5, Y: 1}, - {X: 6, Y: 5}, - {X: 7, Y: 5}, - {X: 9, Y: 5}, - {X: 10, Y: 5}, - {X: 4, Y: 5}, - {X: 3, Y: 5}, - {X: 1, Y: 5}, - {X: 0, Y: 5}, - }, - {X: 19, Y: 19}: { - {X: 9, Y: 0}, - {X: 9, Y: 1}, - {X: 9, Y: 2}, - {X: 9, Y: 5}, - {X: 9, Y: 6}, - {X: 9, Y: 7}, - {X: 9, Y: 9}, - {X: 9, Y: 8}, - {X: 9, Y: 10}, - {X: 9, Y: 12}, - {X: 9, Y: 11}, - {X: 9, Y: 13}, - {X: 9, Y: 14}, - {X: 9, Y: 16}, - {X: 9, Y: 17}, - {X: 9, Y: 18}, - {X: 0, Y: 9}, - {X: 2, Y: 9}, - {X: 1, Y: 9}, - {X: 3, Y: 9}, - {X: 5, Y: 9}, - {X: 6, Y: 9}, - {X: 7, Y: 9}, - {X: 8, Y: 9}, - {X: 10, Y: 9}, - {X: 13, Y: 9}, - {X: 12, Y: 9}, - {X: 11, Y: 9}, - {X: 15, Y: 9}, - {X: 16, Y: 9}, - {X: 17, Y: 9}, - {X: 18, Y: 9}, - {X: 9, Y: 4}, - {X: 8, Y: 10}, - {X: 8, Y: 8}, - {X: 10, Y: 8}, - {X: 10, Y: 10}, - }, - {X: 25, Y: 25}: { - {X: 12, Y: 24}, - {X: 12, Y: 21}, - {X: 12, Y: 20}, - {X: 12, Y: 19}, - {X: 12, Y: 18}, - {X: 12, Y: 15}, - {X: 12, Y: 14}, - {X: 12, Y: 13}, - {X: 12, Y: 12}, - {X: 12, Y: 11}, - {X: 12, Y: 10}, - {X: 12, Y: 9}, - {X: 12, Y: 5}, - {X: 12, Y: 4}, - {X: 12, Y: 3}, - {X: 12, Y: 0}, - {X: 0, Y: 12}, - {X: 3, Y: 12}, - {X: 4, Y: 12}, - {X: 5, Y: 12}, - {X: 6, Y: 12}, - {X: 9, Y: 12}, - {X: 10, Y: 12}, - {X: 11, Y: 12}, - {X: 13, Y: 12}, - {X: 14, Y: 12}, - {X: 15, Y: 12}, - {X: 18, Y: 12}, - {X: 20, Y: 12}, - {X: 19, Y: 12}, - {X: 21, Y: 12}, - {X: 24, Y: 12}, - {X: 11, Y: 14}, - {X: 10, Y: 13}, - {X: 11, Y: 13}, - {X: 10, Y: 11}, - {X: 11, Y: 11}, - {X: 11, Y: 10}, - {X: 13, Y: 10}, - {X: 14, Y: 11}, - {X: 13, Y: 11}, - {X: 13, Y: 13}, - {X: 14, Y: 13}, - {X: 13, Y: 14}, - {X: 12, Y: 6}, - {X: 12, Y: 2}, - {X: 2, Y: 12}, - {X: 22, Y: 12}, - {X: 12, Y: 22}, - {X: 16, Y: 12}, - {X: 12, Y: 8}, - {X: 8, Y: 12}, - {X: 12, Y: 16}, - }, -} diff --git a/maps/hazards_test.go b/maps/hazards_test.go index 1341512..80229e9 100644 --- a/maps/hazards_test.go +++ b/maps/hazards_test.go @@ -89,29 +89,6 @@ func TestColumnsHazardsMap(t *testing.T) { require.NotContains(t, state.Hazards, rules.Point{X: 1, Y: 0}) } -func TestRiversAndBridgetsHazardsMap(t *testing.T) { - // check error handling - m := maps.RiverAndBridgesHazardsMap{} - settings := rules.Settings{} - - // check error for unsupported board sizes - state := rules.NewBoardState(9, 9) - editor := maps.NewBoardStateEditor(state) - err := m.SetupBoard(state, settings, editor) - require.Error(t, err) - - // check all the supported sizes - for _, size := range []int{11, 19, 25} { - state = rules.NewBoardState(size, size) - state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}}) - editor = maps.NewBoardStateEditor(state) - require.Empty(t, state.Hazards) - err = m.SetupBoard(state, settings, editor) - require.NoError(t, err) - require.NotEmpty(t, state.Hazards) - } -} - func TestSpiralHazardsMap(t *testing.T) { // check error handling m := maps.SpiralHazardsMap{} diff --git a/maps/registry_test.go b/maps/registry_test.go index 62ad504..7250671 100644 --- a/maps/registry_test.go +++ b/maps/registry_test.go @@ -60,8 +60,8 @@ func TestRegisteredMaps(t *testing.T) { } // Check that at least one map size can be setup without error - for width := 0; width < maxBoardWidth; width++ { - for height := 0; height < maxBoardHeight; height++ { + for width := 0; width <= maxBoardWidth; width++ { + for height := 0; height <= maxBoardHeight; height++ { initialBoardState := rules.NewBoardState(width, height) initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}}) if meta.MaxPlayers > 1 { diff --git a/maps/rivers_and_bridges.go b/maps/rivers_and_bridges.go new file mode 100644 index 0000000..8178d42 --- /dev/null +++ b/maps/rivers_and_bridges.go @@ -0,0 +1,376 @@ +package maps + +import ( + "github.com/BattlesnakeOfficial/rules" +) + +func init() { + globalRegistry.RegisterMap("hz_rivers_bridges", RiverAndBridgesMediumHazardsMap{}) + globalRegistry.RegisterMap("hz_rivers_bridges_lg", RiverAndBridgesLargeHazardsMap{}) + globalRegistry.RegisterMap("hz_rivers_bridges_xl", RiverAndBridgesExtraLargeHazardsMap{}) +} + +func setupRiverAndBridgesBoard(startingPositions [][]rules.Point, hazards []rules.Point, lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + width := lastBoardState.Width + height := lastBoardState.Height + + numSnakes := len(lastBoardState.Snakes) + if numSnakes == 0 { + return rules.RulesetError("too few snakes - at least one snake must be present") + } + + rand := settings.GetRand(0) + + snakeIDs := make([]string, 0, len(lastBoardState.Snakes)) + for _, snake := range lastBoardState.Snakes { + snakeIDs = append(snakeIDs, snake.ID) + } + + tempBoardState := rules.NewBoardState(width, height) + tempBoardState.Snakes = make([]rules.Snake, len(snakeIDs)) + + for i := 0; i < len(snakeIDs); i++ { + tempBoardState.Snakes[i] = rules.Snake{ + ID: snakeIDs[i], + Health: rules.SnakeMaxHealth, + } + } + err := rules.PlaceSnakesInQuadrants(rand, tempBoardState, startingPositions) + if err != nil { + return err + } + + err = rules.PlaceFoodFixed(rand, tempBoardState) + if err != nil { + return err + } + + // Copy food from temp board state + for _, f := range tempBoardState.Food { + // skip the center food + if f.X == lastBoardState.Width/2 && f.Y == lastBoardState.Height/2 { + continue + } + editor.AddFood(f) + } + + // Copy snakes from temp board state + for _, snake := range tempBoardState.Snakes { + editor.PlaceSnake(snake.ID, snake.Body, snake.Health) + } + + for _, p := range hazards { + editor.AddHazard(p) + } + + return nil +} + +func placeRiverAndBridgesFood(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + rand := settings.GetRand(lastBoardState.Turn) + + foodNeeded := checkFoodNeedingPlacement(rand, settings, lastBoardState) + if foodNeeded > 0 { + pts := rules.GetUnoccupiedPoints(lastBoardState, false, true) + placeFoodRandomlyAtPositions(rand, lastBoardState, editor, foodNeeded, pts) + } + + return nil +} + +type RiverAndBridgesMediumHazardsMap struct{} + +func (m RiverAndBridgesMediumHazardsMap) ID() string { + return "hz_rivers_bridges" +} + +func (m RiverAndBridgesMediumHazardsMap) Meta() Metadata { + return Metadata{ + Name: "hz_rivers_bridges", + Description: `Creates fixed maps that have a lake of hazard in the middle with rivers going in the cardinal directions. +Each river has one or two 1-square "bridges" over them`, + Author: "Battlesnake", + Version: 1, + MinPlayers: 1, + MaxPlayers: 8, + BoardSizes: FixedSizes(Dimensions{11, 11}), + Tags: []string{TAG_FOOD_PLACEMENT, TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT}, + } +} + +func (m RiverAndBridgesMediumHazardsMap) SetupBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + if !m.Meta().BoardSizes.IsAllowable(lastBoardState.Width, lastBoardState.Height) { + return rules.RulesetError("This map can only be played on a 11x11 board") + } + + err := setupRiverAndBridgesBoard(riversAndBridgesMediumStartPositions, riversAndBridgesMediumHazards, lastBoardState, settings, editor) + if err != nil { + return err + } + return nil +} + +func (m RiverAndBridgesMediumHazardsMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + return placeRiverAndBridgesFood(lastBoardState, settings, editor) +} + +var riversAndBridgesMediumStartPositions = [][]rules.Point{ + { + {X: 1, Y: 1}, + {X: 3, Y: 3}, + }, + { + {X: 9, Y: 9}, + {X: 7, Y: 7}, + }, + { + {X: 1, Y: 9}, + {X: 3, Y: 9}, + }, + { + {X: 9, Y: 1}, + {X: 7, Y: 3}, + }, +} + +var riversAndBridgesMediumHazards = []rules.Point{ + {X: 5, Y: 10}, + {X: 5, Y: 9}, + {X: 5, Y: 7}, + {X: 5, Y: 6}, + {X: 5, Y: 5}, + {X: 5, Y: 4}, + {X: 5, Y: 3}, + {X: 5, Y: 0}, + {X: 5, Y: 1}, + {X: 6, Y: 5}, + {X: 7, Y: 5}, + {X: 9, Y: 5}, + {X: 10, Y: 5}, + {X: 4, Y: 5}, + {X: 3, Y: 5}, + {X: 1, Y: 5}, + {X: 0, Y: 5}, +} + +type RiverAndBridgesLargeHazardsMap struct{} + +func (m RiverAndBridgesLargeHazardsMap) ID() string { + return "hz_rivers_bridges_lg" +} + +func (m RiverAndBridgesLargeHazardsMap) Meta() Metadata { + return Metadata{ + Name: "hz_rivers_bridges_lg", + Description: `Creates fixed maps that have a lake of hazard in the middle with rivers going in the cardinal directions. +Each river has one or two 1-square "bridges" over them`, + Author: "Battlesnake", + Version: 1, + MinPlayers: 1, + MaxPlayers: 12, + BoardSizes: FixedSizes(Dimensions{19, 19}), + Tags: []string{TAG_FOOD_PLACEMENT, TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT}, + } +} + +func (m RiverAndBridgesLargeHazardsMap) SetupBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + if !m.Meta().BoardSizes.IsAllowable(lastBoardState.Width, lastBoardState.Height) { + return rules.RulesetError("This map can only be played on a 19x19 board") + } + + err := setupRiverAndBridgesBoard(riversAndBridgesLargeStartPositions, riversAndBridgesLargeHazards, lastBoardState, settings, editor) + if err != nil { + return err + } + return nil +} + +func (m RiverAndBridgesLargeHazardsMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + return placeRiverAndBridgesFood(lastBoardState, settings, editor) +} + +var riversAndBridgesLargeStartPositions = [][]rules.Point{ + { + {X: 1, Y: 1}, + {X: 5, Y: 1}, + {X: 1, Y: 5}, + {X: 5, Y: 5}, + }, + { + {X: 17, Y: 1}, + {X: 17, Y: 5}, + {X: 13, Y: 5}, + {X: 13, Y: 1}, + }, + { + {X: 1, Y: 17}, + {X: 5, Y: 17}, + {X: 1, Y: 13}, + {X: 5, Y: 13}, + }, + { + {X: 17, Y: 17}, + {X: 17, Y: 13}, + {X: 13, Y: 17}, + {X: 13, Y: 13}, + }, +} + +var riversAndBridgesLargeHazards = []rules.Point{ + {X: 9, Y: 0}, + {X: 9, Y: 1}, + {X: 9, Y: 2}, + {X: 9, Y: 5}, + {X: 9, Y: 6}, + {X: 9, Y: 7}, + {X: 9, Y: 9}, + {X: 9, Y: 8}, + {X: 9, Y: 10}, + {X: 9, Y: 12}, + {X: 9, Y: 11}, + {X: 9, Y: 13}, + {X: 9, Y: 14}, + {X: 9, Y: 16}, + {X: 9, Y: 17}, + {X: 9, Y: 18}, + {X: 0, Y: 9}, + {X: 2, Y: 9}, + {X: 1, Y: 9}, + {X: 3, Y: 9}, + {X: 5, Y: 9}, + {X: 6, Y: 9}, + {X: 7, Y: 9}, + {X: 8, Y: 9}, + {X: 10, Y: 9}, + {X: 13, Y: 9}, + {X: 12, Y: 9}, + {X: 11, Y: 9}, + {X: 15, Y: 9}, + {X: 16, Y: 9}, + {X: 17, Y: 9}, + {X: 18, Y: 9}, + {X: 9, Y: 4}, + {X: 8, Y: 10}, + {X: 8, Y: 8}, + {X: 10, Y: 8}, + {X: 10, Y: 10}, +} + +type RiverAndBridgesExtraLargeHazardsMap struct{} + +func (m RiverAndBridgesExtraLargeHazardsMap) ID() string { + return "hz_rivers_bridges_xl" +} + +func (m RiverAndBridgesExtraLargeHazardsMap) Meta() Metadata { + return Metadata{ + Name: "hz_rivers_bridges_xl", + Description: `Creates fixed maps that have a lake of hazard in the middle with rivers going in the cardinal directions. +Each river has one or two 1-square "bridges" over them`, + Author: "Battlesnake", + Version: 1, + MinPlayers: 1, + MaxPlayers: 12, + BoardSizes: FixedSizes(Dimensions{25, 25}), + Tags: []string{TAG_FOOD_PLACEMENT, TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT}, + } +} + +func (m RiverAndBridgesExtraLargeHazardsMap) SetupBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + if !m.Meta().BoardSizes.IsAllowable(lastBoardState.Width, lastBoardState.Height) { + return rules.RulesetError("This map can only be played on a 25x25 board") + } + + err := setupRiverAndBridgesBoard(riversAndBridgesExtraLargeStartPositions, riversAndBridgesExtraLargeHazards, lastBoardState, settings, editor) + if err != nil { + return err + } + return nil +} + +func (m RiverAndBridgesExtraLargeHazardsMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + return placeRiverAndBridgesFood(lastBoardState, settings, editor) +} + +var riversAndBridgesExtraLargeStartPositions = [][]rules.Point{ + { + {X: 1, Y: 1}, + {X: 9, Y: 9}, + {X: 9, Y: 1}, + {X: 1, Y: 9}, + }, + { + {X: 23, Y: 23}, + {X: 15, Y: 15}, + {X: 23, Y: 15}, + {X: 15, Y: 23}, + }, + { + {X: 15, Y: 1}, + {X: 15, Y: 9}, + {X: 23, Y: 9}, + {X: 23, Y: 1}, + }, + { + {X: 9, Y: 23}, + {X: 1, Y: 23}, + {X: 9, Y: 15}, + {X: 1, Y: 15}, + }, +} + +var riversAndBridgesExtraLargeHazards = []rules.Point{ + {X: 12, Y: 24}, + {X: 12, Y: 21}, + {X: 12, Y: 20}, + {X: 12, Y: 19}, + {X: 12, Y: 18}, + {X: 12, Y: 15}, + {X: 12, Y: 14}, + {X: 12, Y: 13}, + {X: 12, Y: 12}, + {X: 12, Y: 11}, + {X: 12, Y: 10}, + {X: 12, Y: 9}, + {X: 12, Y: 5}, + {X: 12, Y: 4}, + {X: 12, Y: 3}, + {X: 12, Y: 0}, + {X: 0, Y: 12}, + {X: 3, Y: 12}, + {X: 4, Y: 12}, + {X: 5, Y: 12}, + {X: 6, Y: 12}, + {X: 9, Y: 12}, + {X: 10, Y: 12}, + {X: 11, Y: 12}, + {X: 13, Y: 12}, + {X: 14, Y: 12}, + {X: 15, Y: 12}, + {X: 18, Y: 12}, + {X: 20, Y: 12}, + {X: 19, Y: 12}, + {X: 21, Y: 12}, + {X: 24, Y: 12}, + {X: 11, Y: 14}, + {X: 10, Y: 13}, + {X: 11, Y: 13}, + {X: 10, Y: 11}, + {X: 11, Y: 11}, + {X: 11, Y: 10}, + {X: 13, Y: 10}, + {X: 14, Y: 11}, + {X: 13, Y: 11}, + {X: 13, Y: 13}, + {X: 14, Y: 13}, + {X: 13, Y: 14}, + {X: 12, Y: 6}, + {X: 12, Y: 2}, + {X: 2, Y: 12}, + {X: 22, Y: 12}, + {X: 12, Y: 22}, + {X: 16, Y: 12}, + {X: 12, Y: 8}, + {X: 8, Y: 12}, + {X: 12, Y: 16}, +} diff --git a/maps/hazards_internal_test.go b/maps/rivers_and_bridges_internal_test.go similarity index 89% rename from maps/hazards_internal_test.go rename to maps/rivers_and_bridges_internal_test.go index 636049e..05723ff 100644 --- a/maps/hazards_internal_test.go +++ b/maps/rivers_and_bridges_internal_test.go @@ -5,17 +5,18 @@ import ( "testing" "github.com/BattlesnakeOfficial/rules" + "github.com/stretchr/testify/require" ) func TestRiversAndBridgesSnakePlacement(t *testing.T) { - m := RiverAndBridgesHazardsMap{} + m := RiverAndBridgesMediumHazardsMap{} settings := rules.Settings{} // check all the supported sizes - for _, size := range []int{11, 19, 25} { + for _, size := range []int{11} { initialState := rules.NewBoardState(size, size) - startPositions := riversAndBridgesStartPositions[rules.Point{X: size, Y: size}] + startPositions := riversAndBridgesMediumStartPositions maxSnakes := len(startPositions) for i := 0; i < maxSnakes; i++ { initialState.Snakes = append(initialState.Snakes, rules.Snake{ID: fmt.Sprint(i), Body: []rules.Point{}}) diff --git a/maps/rivers_and_bridges_test.go b/maps/rivers_and_bridges_test.go new file mode 100644 index 0000000..e301dca --- /dev/null +++ b/maps/rivers_and_bridges_test.go @@ -0,0 +1,42 @@ +package maps_test + +import ( + "testing" + + "github.com/BattlesnakeOfficial/rules" + "github.com/BattlesnakeOfficial/rules/maps" + "github.com/stretchr/testify/require" +) + +func TestRiversAndBridgetsHazardsMap(t *testing.T) { + // check error handling + m := maps.RiverAndBridgesMediumHazardsMap{} + settings := rules.Settings{} + + // check error for unsupported board sizes + state := rules.NewBoardState(9, 9) + editor := maps.NewBoardStateEditor(state) + err := m.SetupBoard(state, settings, editor) + require.Error(t, err) + + tests := []struct { + Map maps.GameMap + Width uint + Height uint + }{ + {maps.RiverAndBridgesMediumHazardsMap{}, 11, 11}, + {maps.RiverAndBridgesLargeHazardsMap{}, 19, 19}, + {maps.RiverAndBridgesExtraLargeHazardsMap{}, 25, 25}, + } + + // check all the supported sizes + for _, test := range tests { + state = rules.NewBoardState(int(test.Width), int(test.Height)) + state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}}) + editor = maps.NewBoardStateEditor(state) + require.Empty(t, state.Hazards) + err = test.Map.SetupBoard(state, settings, editor) + require.NoError(t, err) + require.NotEmpty(t, state.Hazards) + } +} diff --git a/maps/standard.go b/maps/standard.go index 0bd47ad..ee1eba1 100644 --- a/maps/standard.go +++ b/maps/standard.go @@ -84,7 +84,7 @@ func checkFoodNeedingPlacement(rand rules.Rand, settings rules.Settings, state * } func placeFoodRandomly(rand rules.Rand, b *rules.BoardState, editor Editor, n int) { - unoccupiedPoints := rules.GetUnoccupiedPoints(b, false) + unoccupiedPoints := rules.GetUnoccupiedPoints(b, false, false) placeFoodRandomlyAtPositions(rand, b, editor, n, unoccupiedPoints) }