From 7d9a9fb1ab9ee5b0f710bc4384f01b5203abb7e6 Mon Sep 17 00:00:00 2001 From: Chris Hoefgen <53871533+chris-bsnake@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:07:16 -0700 Subject: [PATCH] DEV-1556-sinkholes-map (#96) * initial map implementation * added support for larger/smaller boards * basic unit test --- maps/sinkholes.go | 88 ++++++++++++++++++++++++++++++++++++++++++ maps/sinkholes_test.go | 57 +++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 maps/sinkholes.go create mode 100644 maps/sinkholes_test.go diff --git a/maps/sinkholes.go b/maps/sinkholes.go new file mode 100644 index 0000000..80c8ceb --- /dev/null +++ b/maps/sinkholes.go @@ -0,0 +1,88 @@ +package maps + +import ( + "math" + + "github.com/BattlesnakeOfficial/rules" +) + +type SinkholesMap struct{} + +func init() { + globalRegistry.RegisterMap("sinkholes", SinkholesMap{}) +} + +func (m SinkholesMap) ID() string { + return "sinkholes" +} + +func (m SinkholesMap) Meta() Metadata { + return Metadata{ + Name: "Sinkholes", + Description: "Spawns a rounded sinkhole on the board that grows every N turns, layering additional hazard squares over previously spawned ones.", + Author: "Battlesnake", + Version: 1, + MinPlayers: 1, + MaxPlayers: 8, + BoardSizes: FixedSizes(Dimensions{7, 7}, Dimensions{11, 11}, Dimensions{19, 19}), + } +} + +func (m SinkholesMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + return (StandardMap{}).SetupBoard(initialBoardState, settings, editor) +} + +func (m SinkholesMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { + err := StandardMap{}.UpdateBoard(lastBoardState, settings, editor) + if err != nil { + return err + } + + currentTurn := lastBoardState.Turn + startTurn := 1 + spawnEveryNTurns := 10 + if settings.RoyaleSettings.ShrinkEveryNTurns > 0 { + spawnEveryNTurns = settings.RoyaleSettings.ShrinkEveryNTurns + } + maxRings := 5 + if lastBoardState.Width == 7 { + maxRings = 3 + } else if lastBoardState.Width == 19 { + maxRings = 7 + } + + spawnLocation := rules.Point{X: lastBoardState.Width / 2, Y: lastBoardState.Height / 2} + + if currentTurn == startTurn { + editor.AddHazard(spawnLocation) + return nil + } + + // Are we at max size, if so stop try to generate hazards + if currentTurn > spawnEveryNTurns*maxRings { + return nil + } + + // Is this a turn to grow the sinkhole? + if (currentTurn-startTurn)%spawnEveryNTurns != 0 { + return nil + } + + offset := int(math.Floor(float64(currentTurn-startTurn) / float64(spawnEveryNTurns))) + + if offset > 0 && offset <= maxRings { + for x := spawnLocation.X - offset; x <= spawnLocation.X+offset; x++ { + for y := spawnLocation.Y - offset; y <= spawnLocation.Y+offset; y++ { + // don't draw in the corners of the square so we get a rounded effect + if !(x == spawnLocation.X-offset && y == spawnLocation.Y-offset) && + !(x == spawnLocation.X+offset && y == spawnLocation.Y-offset) && + !(x == spawnLocation.X-offset && y == spawnLocation.Y+offset) && + !(x == spawnLocation.X+offset && y == spawnLocation.Y+offset) { + editor.AddHazard(rules.Point{X: x, Y: y}) + } + } + } + } + + return nil +} diff --git a/maps/sinkholes_test.go b/maps/sinkholes_test.go new file mode 100644 index 0000000..4fa06a7 --- /dev/null +++ b/maps/sinkholes_test.go @@ -0,0 +1,57 @@ +package maps_test + +import ( + "fmt" + "testing" + + "github.com/BattlesnakeOfficial/rules" + "github.com/BattlesnakeOfficial/rules/maps" + "github.com/stretchr/testify/require" +) + +func TestSinkholesMap(t *testing.T) { + + tests := []struct { + boardSize int + expectedHazards int + expectedHazardsInCenter int + }{ + {7, 27, 3}, + {11, 149, 5}, + {19, 431, 7}, + } + + for _, tc := range tests { + + t.Run(fmt.Sprintf("%dx%d", tc.boardSize, tc.boardSize), func(t *testing.T) { + m := maps.SinkholesMap{} + state := rules.NewBoardState(tc.boardSize, tc.boardSize) + settings := rules.Settings{} + + // ensure the ring of hazards is added to the board at setup + editor := maps.NewBoardStateEditor(state) + require.Empty(t, state.Hazards) + err := m.SetupBoard(state, settings, editor) + require.NoError(t, err) + require.Empty(t, state.Hazards) + + totalTurns := 100 + for i := 0; i < totalTurns; i++ { + state.Turn = i + err = m.UpdateBoard(state, settings, editor) + require.NoError(t, err) + } + require.NotEmpty(t, state.Hazards) + require.Len(t, state.Hazards, tc.expectedHazards) + + centerPoint := rules.Point{X: tc.boardSize / 2, Y: tc.boardSize / 2} + numCenterHazards := 0 + for _, p := range state.Hazards { + if p == centerPoint { + numCenterHazards += 1 + } + } + require.Equal(t, numCenterHazards, tc.expectedHazardsInCenter) + }) + } +}