Skip to content

Commit

Permalink
Merge branch 'main' into snail-mode
Browse files Browse the repository at this point in the history
* main:
  DEV-1558-healing-pools-map (BattlesnakeOfficial#94)
  DEV-1556-sinkholes-map (BattlesnakeOfficial#96)
  CLI support for handling invalid responses (BattlesnakeOfficial#95)
  • Loading branch information
coreyja committed Jul 31, 2022
2 parents 68ccc0c + 215a0ea commit b61973d
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 19 deletions.
70 changes: 51 additions & 19 deletions cli/commands/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,26 +398,58 @@ func (gameState *GameState) getMoveForSnake(boardState *rules.BoardState, snakeS
log.Printf("POST %s: %v", u, string(requestBody))
}
res, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
move := snakeState.LastMove

// Use snake's last move as the default in case of an error
snakeMove := rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove}

if err != nil {
log.Printf("[WARN]: Request to %v failed\n", u.String())
log.Printf("Body --> %v\n", string(requestBody))
} else if res.Body != nil {
defer res.Body.Close()
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Fatal(readErr)
} else {
playerResponse := client.MoveResponse{}
jsonErr := json.Unmarshal(body, &playerResponse)
if jsonErr != nil {
log.Fatal(jsonErr)
} else {
move = playerResponse.Move
}
}
}
return rules.SnakeMove{ID: snakeState.ID, Move: move}
log.Printf(
"[WARN]: Request to %v failed\n"+
"\tError: %s\n", u.String(), err)
return snakeMove
}
if res.Body == nil {
log.Printf(
"[WARN]: Failed to parse response from %v\n"+
"\tError: body is empty\n", u.String())
return snakeMove
}
defer res.Body.Close()
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Printf(
"[WARN]: Failed to read response body from %v\n"+
"\tError: %v\n", u.String(), readErr)
return snakeMove
}
if res.StatusCode != http.StatusOK {
log.Printf(
"[WARN]: Got non-ok status code from %v\n"+
"\tStatusCode: %d (expected %d)\n"+
"\tBody: %q\n", u.String(), res.StatusCode, http.StatusOK, body)
return snakeMove
}
playerResponse := client.MoveResponse{}
jsonErr := json.Unmarshal(body, &playerResponse)
if jsonErr != nil {
log.Printf(
"[WARN]: Failed to decode JSON from %v\n"+
"\tError: %v\n"+
"\tBody: %q\n"+
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), jsonErr, body)
return snakeMove
}
if playerResponse.Move != "up" && playerResponse.Move != "down" && playerResponse.Move != "left" && playerResponse.Move != "right" {
log.Printf(
"[WARN]: Failed to parse JSON data from %v\n"+
"\tError: invalid move %q, valid moves are \"up\", \"down\", \"left\" or \"right\"\n"+
"\tBody: %q\n"+
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), playerResponse.Move, body)
return snakeMove
}

snakeMove.Move = playerResponse.Move
return snakeMove
}

func (gameState *GameState) sendEndRequest(boardState *rules.BoardState, snakeState SnakeState) {
Expand Down
92 changes: 92 additions & 0 deletions maps/healing_pools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package maps

import (
"github.com/BattlesnakeOfficial/rules"
)

type HealingPoolsMap struct{}

func init() {
globalRegistry.RegisterMap("healing_pools", HealingPoolsMap{})
}

func (m HealingPoolsMap) ID() string {
return "healing_pools"
}

func (m HealingPoolsMap) Meta() Metadata {
return Metadata{
Name: "Healing Pools",
Description: "A simple map that spawns fixed single cell hazard areas based on the map size.",
Author: "Battlesnake",
Version: 1,
MinPlayers: 1,
MaxPlayers: 8,
BoardSizes: FixedSizes(Dimensions{7, 7}, Dimensions{11, 11}, Dimensions{19, 19}),
}
}

func (m HealingPoolsMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
if err := (StandardMap{}).SetupBoard(initialBoardState, settings, editor); err != nil {
return err
}

rand := settings.GetRand(0)

options, ok := poolLocationOptions[rules.Point{X: initialBoardState.Width, Y: initialBoardState.Height}]
if !ok {
return rules.RulesetError("board size is not supported by this map")
}

i := rand.Intn(len(options))

for _, p := range options[i] {
editor.AddHazard(p)
}

return nil
}

func (m HealingPoolsMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
return StandardMap{}.UpdateBoard(lastBoardState, settings, editor)
}

var poolLocationOptions = map[rules.Point][][]rules.Point{
{X: 7, Y: 7}: {
{
{X: 3, Y: 3},
},
},
{X: 11, Y: 11}: {
{
{X: 3, Y: 3},
{X: 7, Y: 7},
},
{
{X: 3, Y: 7},
{X: 7, Y: 3},
},
{
{X: 3, Y: 5},
{X: 7, Y: 5},
},
{
{X: 5, Y: 7},
{X: 5, Y: 3},
},
},
{X: 19, Y: 19}: {
{
{X: 5, Y: 5},
{X: 13, Y: 13},
{X: 5, Y: 13},
{X: 13, Y: 5},
},
{
{X: 5, Y: 10},
{X: 13, Y: 10},
{X: 10, Y: 13},
{X: 10, Y: 5},
},
},
}
58 changes: 58 additions & 0 deletions maps/healing_pools_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package maps_test

import (
"fmt"
"testing"

"github.com/BattlesnakeOfficial/rules"
"github.com/BattlesnakeOfficial/rules/maps"
"github.com/stretchr/testify/require"
)

func TestHealingPoolsMap(t *testing.T) {

tests := []struct {
boardSize int
expectedHazards int
allowableHazards []rules.Point
}{
{7, 1, []rules.Point{{X: 3, Y: 3}}},
{11, 2, []rules.Point{{X: 3, Y: 3},
{X: 7, Y: 7},
{X: 3, Y: 7},
{X: 7, Y: 3},
{X: 3, Y: 5},
{X: 7, Y: 5},
{X: 5, Y: 7},
{X: 5, Y: 3}}},
{19, 4, []rules.Point{{X: 5, Y: 5},
{X: 13, Y: 13},
{X: 5, Y: 13},
{X: 13, Y: 5},
{X: 5, Y: 10},
{X: 13, Y: 10},
{X: 10, Y: 13},
{X: 10, Y: 5}}},
}

for _, tc := range tests {

t.Run(fmt.Sprintf("%dx%d", tc.boardSize, tc.boardSize), func(t *testing.T) {
m := maps.HealingPoolsMap{}
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.NotEmpty(t, state.Hazards)
require.Len(t, state.Hazards, tc.expectedHazards)

for _, p := range state.Hazards {
require.Contains(t, tc.allowableHazards, p)
}
})
}
}
88 changes: 88 additions & 0 deletions maps/sinkholes.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 57 additions & 0 deletions maps/sinkholes_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}

0 comments on commit b61973d

Please sign in to comment.