* add helper to draw a ring of hazards * refactor tests to not be internal tests * add "hz_inner_wall" map * add "hz_rings" map * fix map registry * fix: edge case bugs in drawRing * remove println * add "hz_columns" * add "hz_rivers_bridges" map * WIP: implementing spiral hazards map * finish basic testing of 'hz_spiral' * include first turn * add "hz_hazards" map * remove incorrect author * add "hz_grow_box" map * add "hz_expand_box" map * add "hz_expand_scatter" map * remove debug * document the new "Range" method * - use rules.RulesetError instead of generic error - use a rules.Point for map rivers and bridgets map key * use rules.RulesetError instead of errors.New * provide more detail about boundar conditions * fix documentation (max can be == min) * add unit tests
179 lines
4.1 KiB
Go
179 lines
4.1 KiB
Go
package maps
|
|
|
|
import (
|
|
"github.com/BattlesnakeOfficial/rules"
|
|
)
|
|
|
|
// SetupBoard is a shortcut for looking up a map by ID and initializing a new board state with it.
|
|
func SetupBoard(mapID string, settings rules.Settings, width, height int, snakeIDs []string) (*rules.BoardState, error) {
|
|
boardState := rules.NewBoardState(width, height)
|
|
|
|
rules.InitializeSnakes(boardState, snakeIDs)
|
|
|
|
gameMap, err := GetMap(mapID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
editor := NewBoardStateEditor(boardState)
|
|
|
|
err = gameMap.SetupBoard(boardState, settings, editor)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return boardState, nil
|
|
}
|
|
|
|
// UpdateBoard is a shortcut for looking up a map by ID and updating an existing board state with it.
|
|
func UpdateBoard(mapID string, previousBoardState *rules.BoardState, settings rules.Settings) (*rules.BoardState, error) {
|
|
gameMap, err := GetMap(mapID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nextBoardState := previousBoardState.Clone()
|
|
editor := NewBoardStateEditor(nextBoardState)
|
|
|
|
err = gameMap.UpdateBoard(previousBoardState, settings, editor)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nextBoardState, nil
|
|
}
|
|
|
|
// An implementation of GameMap that just does predetermined placements, for testing.
|
|
type StubMap struct {
|
|
Id string
|
|
SnakePositions map[string]rules.Point
|
|
Food []rules.Point
|
|
Hazards []rules.Point
|
|
Error error
|
|
}
|
|
|
|
func (m StubMap) ID() string {
|
|
return m.Id
|
|
}
|
|
|
|
func (StubMap) Meta() Metadata {
|
|
return Metadata{}
|
|
}
|
|
|
|
func (m StubMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
|
if m.Error != nil {
|
|
return m.Error
|
|
}
|
|
for _, snake := range initialBoardState.Snakes {
|
|
head := m.SnakePositions[snake.ID]
|
|
editor.PlaceSnake(snake.ID, []rules.Point{head, head, head}, 100)
|
|
}
|
|
for _, food := range m.Food {
|
|
editor.AddFood(food)
|
|
}
|
|
for _, hazard := range m.Hazards {
|
|
editor.AddHazard(hazard)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m StubMap) UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
|
if m.Error != nil {
|
|
return m.Error
|
|
}
|
|
for _, food := range m.Food {
|
|
editor.AddFood(food)
|
|
}
|
|
for _, hazard := range m.Hazards {
|
|
editor.AddHazard(hazard)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// drawRing draws a ring of hazard points offset from the outer edge of the board
|
|
func drawRing(bw, bh, hOffset, vOffset int) ([]rules.Point, error) {
|
|
if bw < 1 {
|
|
return nil, rules.RulesetError("board width too small")
|
|
}
|
|
|
|
if bh < 1 {
|
|
return nil, rules.RulesetError("board height too small")
|
|
}
|
|
|
|
if hOffset >= bw-1 {
|
|
return nil, rules.RulesetError("horizontal offset too large")
|
|
}
|
|
|
|
if vOffset >= bh-1 {
|
|
return nil, rules.RulesetError("vertical offset too large")
|
|
}
|
|
|
|
if hOffset < 1 {
|
|
return nil, rules.RulesetError("horizontal offset too small")
|
|
}
|
|
|
|
if vOffset < 1 {
|
|
return nil, rules.RulesetError("vertical offset too small")
|
|
}
|
|
|
|
// calculate the start/end point of the horizontal borders
|
|
xStart := hOffset - 1
|
|
xEnd := bw - hOffset
|
|
|
|
// calculate start/end point of the vertical borders
|
|
yStart := vOffset - 1
|
|
yEnd := bh - vOffset
|
|
|
|
// we can pre-determine how many points will be in the ring and allocate a slice of exactly that size
|
|
numPoints := 2 * (xEnd - xStart + 1) // horizontal hazard points
|
|
|
|
// Add vertical walls, if there are any.
|
|
// Sometimes there are no vertical walls when the ring height is only 2.
|
|
// In that case, the vertical walls are handled by the horizontal walls
|
|
if yEnd >= yStart {
|
|
numPoints += 2*(yEnd-yStart+1) - 4
|
|
}
|
|
|
|
hazards := make([]rules.Point, 0, numPoints)
|
|
|
|
// draw horizontal walls
|
|
for x := xStart; x <= xEnd; x++ {
|
|
hazards = append(hazards,
|
|
rules.Point{X: x, Y: yStart},
|
|
rules.Point{X: x, Y: yEnd},
|
|
)
|
|
}
|
|
|
|
// draw vertical walls, but don't include corners that the horizontal walls already included
|
|
for y := yStart + 1; y <= yEnd-1; y++ {
|
|
hazards = append(hazards,
|
|
rules.Point{X: xStart, Y: y},
|
|
rules.Point{X: xEnd, Y: y},
|
|
)
|
|
}
|
|
|
|
return hazards, nil
|
|
}
|
|
|
|
func maxInt(n1 int, n ...int) int {
|
|
max := n1
|
|
for _, v := range n {
|
|
if v > max {
|
|
max = v
|
|
}
|
|
}
|
|
|
|
return max
|
|
}
|
|
|
|
func isOnBoard(w, h, x, y int) bool {
|
|
if x >= w || x < 0 {
|
|
return false
|
|
}
|
|
|
|
if y >= h || y < 0 {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|