Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion cli/commands/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type GameState struct {
MinimumFood int
HazardDamagePerTurn int
ShrinkEveryNTurns int
ViewRadius int

// Internal game state
settings map[string]string
Expand Down Expand Up @@ -111,6 +112,8 @@ func NewPlayCommand() *cobra.Command {
playCmd.Flags().BoolVar(&gameState.ViewInBrowser, "browser", false, "View the game in the browser using the Battlesnake game board")
playCmd.Flags().StringVar(&gameState.BoardURL, "board-url", "https://board.battlesnake.com", "Base URL for the game board when using --browser")

playCmd.Flags().IntVarP(&gameState.ViewRadius, "viewRadius", "i", -1, "View Radius of Snake")

playCmd.Flags().IntVar(&gameState.FoodSpawnChance, "foodSpawnChance", 15, "Percentage chance of spawning a new food every round")
playCmd.Flags().IntVar(&gameState.MinimumFood, "minimumFood", 1, "Minimum food to keep on the board every turn")
playCmd.Flags().IntVar(&gameState.HazardDamagePerTurn, "hazardDamagePerTurn", 14, "Health damage a snake will take when ending its turn in a hazard")
Expand All @@ -137,6 +140,9 @@ func (gameState *GameState) Initialize() error {
}

// Load game map
if gameState.ViewRadius != -1 {
gameState.MapName = "limitInfo"
}
gameMap, err := maps.GetMap(gameState.MapName)
if err != nil {
return fmt.Errorf("Failed to load game map %#v: %v", gameState.MapName, err)
Expand Down Expand Up @@ -532,10 +538,16 @@ func (gameState *GameState) getRequestBodyForSnake(boardState *rules.BoardState,
break
}
}

filteredState := boardState
if gameState.ViewRadius >= 0 {
filteredState = FilterBoardStateForSnake(boardState, snakeState, gameState.ViewRadius)
}

request := client.SnakeRequest{
Game: gameState.createClientGame(),
Turn: boardState.Turn,
Board: convertStateToBoard(boardState, gameState.snakeStates),
Board: convertStateToBoard(filteredState, gameState.snakeStates),
You: convertRulesSnake(youSnake, snakeState),
}
return request
Expand Down
101 changes: 101 additions & 0 deletions cli/commands/state_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package commands

import (
"fmt"
"strconv"

"github.com/BattlesnakeOfficial/rules"
)

func manhattan_d(a, b rules.Point) int {
dx := a.X - b.X
if dx < 0 {
dx = -dx
}
dy := a.Y - b.Y
if dy < 0 {
dy = -dy
}
return dx + dy
}

func FilterBoardStateForSnake(boardState *rules.BoardState, self SnakeState, viewRadius int) *rules.BoardState {
filtered := &rules.BoardState{
Turn: boardState.Turn,
Height: boardState.Height,
Width: boardState.Width,
GameState: boardState.GameState,
Food: []rules.Point{},
Hazards: []rules.Point{},
Snakes: []rules.Snake{},
}

// find the head of self snake
var head rules.Point
for _, s := range boardState.Snakes {
if s.ID == self.ID && len(s.Body) > 0 {
head = s.Body[0]
break
}
}

// FILTER FOOD on view radius or spawn turn
for _, f := range boardState.Food {
visible := manhattan_d(f, head) <= viewRadius
key := fmt.Sprintf("food_spawn_%d_%d", f.X, f.Y)
spawnTurnStr, spawned := boardState.GameState[key]

spawnVisible := false
if spawned {
spawnTurn, _ := strconv.Atoi(spawnTurnStr)
spawnVisible = spawnTurn == boardState.Turn
}

if visible || spawnVisible {
filtered.Food = append(filtered.Food, f)
}
}

// FILTER HAZARDS with view radius
for _, h := range boardState.Hazards {
if manhattan_d(h, head) <= viewRadius {
filtered.Hazards = append(filtered.Hazards, h)
}
}

// FILTER SNAKE bodies
for _, s := range boardState.Snakes {
if s.ID == self.ID {
// self snake sees whole body
filtered.Snakes = append(filtered.Snakes, rules.Snake{
ID: s.ID,
Body: append([]rules.Point(nil), s.Body...),
Health: s.Health,
})
continue
}

filteredBody := []rules.Point{}
for i, seg := range s.Body {
if manhattan_d(seg, head) <= viewRadius {
filteredBody = append(filteredBody, seg)
} else if i == 0 || (i > 0 && manhattan_d(s.Body[i-1], head) <= viewRadius) {
// mark end with -1
filteredBody = append(filteredBody, rules.Point{X: -1, Y: -1})
}
}

if len(filteredBody) == 0 {
// mark end with -1
filteredBody = append(filteredBody, rules.Point{X: -1, Y: -1})
}

filtered.Snakes = append(filtered.Snakes, rules.Snake{
ID: s.ID,
Body: filteredBody,
Health: 0,
})
}

return filtered
}
107 changes: 107 additions & 0 deletions maps/limit_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package maps

import (
"fmt"
"strconv"
"strings"

"github.com/BattlesnakeOfficial/rules"
)

type LimitInfoMap struct{}

func init() {
globalRegistry.RegisterMap("limitInfo", LimitInfoMap{})
}

func (m LimitInfoMap) ID() string {
return "limitInfo"
}

func (m LimitInfoMap) Meta() Metadata {
return Metadata{
Name: "Standard with per snake view range",
Description: "Standard snake placement and food spawning but limited vision/information",
Author: "Kien Nguyen & Yannik Mahlau",
Version: 1,
MinPlayers: 1,
MaxPlayers: 16,
BoardSizes: OddSizes(rules.BoardSizeSmall, rules.BoardSizeXXLarge),
Tags: []string{},
}
}

func (m LimitInfoMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
rand := settings.GetRand(0)

if len(initialBoardState.Snakes) > int(m.Meta().MaxPlayers) {
return rules.ErrorTooManySnakes
}

snakeIDs := make([]string, 0, len(initialBoardState.Snakes))
for _, snake := range initialBoardState.Snakes {
snakeIDs = append(snakeIDs, snake.ID)
}

tempBoardState, err := rules.CreateDefaultBoardState(rand, initialBoardState.Width, initialBoardState.Height, snakeIDs)
if err != nil {
return err
}

// Copy food from temp board state
for _, food := range tempBoardState.Food {
editor.AddFood(food)
}

// Copy snakes from temp board state
for _, snake := range tempBoardState.Snakes {
editor.PlaceSnake(snake.ID, snake.Body, snake.Health)
}

return nil
}

func (m LimitInfoMap) PreUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
return nil
}

func (m LimitInfoMap) PostUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
rand := settings.GetRand(lastBoardState.Turn)

foodNeeded := checkFoodNeedingPlacement(rand, settings, lastBoardState)
if foodNeeded > 0 {
placeFoodRandomlySaveSpawn(rand, lastBoardState, editor, foodNeeded)
}

for k, v := range editor.GameState() {
if strings.HasPrefix(k, "food_spawn_") {
spawnTurn, _ := strconv.Atoi(v)
if spawnTurn < lastBoardState.Turn {
delete(editor.GameState(), k)
}
}
}

return nil
}

func placeFoodRandomlySaveSpawn(rand rules.Rand, b *rules.BoardState, editor Editor, n int) {
unoccupiedPoints := rules.GetUnoccupiedPoints(b, false, false)
placeFoodRandomlyAtPositionsSaveSpawn(rand, b, editor, n, unoccupiedPoints)
}

func placeFoodRandomlyAtPositionsSaveSpawn(rand rules.Rand, b *rules.BoardState, editor Editor, n int, positions []rules.Point) {
if len(positions) < n {
n = len(positions)
}

rand.Shuffle(len(positions), func(i int, j int) {
positions[i], positions[j] = positions[j], positions[i]
})

for i := 0; i < n; i++ {
editor.AddFood(positions[i])
key := fmt.Sprintf("food_spawn_%d_%d", positions[i].X, positions[i].Y)
editor.GameState()[key] = fmt.Sprintf("%d", b.Turn+1)
}
}
Loading