Add player and board size meta data to all game maps (#84)
* WIP: initial data model for new meta props * WIP: implemented new props * test and bug fix: - add coverage of players and sizes - fix unlimited map size bug * FIX: update supported players for arcade to 6 * fix: test should be min -> max, not max->max * Change some naming and the FixedSizes function * update comment to reflect API changes * improve comment clarity * rename field for improved clarity * change some more "map" -> "board" wording
This commit is contained in:
parent
cb014e7b37
commit
3180429688
8 changed files with 146 additions and 5 deletions
|
|
@ -20,6 +20,9 @@ func (m ArcadeMazeMap) Meta() Metadata {
|
||||||
Description: "Generic arcade maze map with deadly hazard walls.",
|
Description: "Generic arcade maze map with deadly hazard walls.",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 6,
|
||||||
|
BoardSizes: FixedSizes(Dimensions{19, 21}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ func (m EmptyMap) Meta() Metadata {
|
||||||
Description: "Default snake placement with no food",
|
Description: "Default snake placement with no food",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,41 @@ type GameMap interface {
|
||||||
UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dimensions describes the size of a Battlesnake board.
|
||||||
|
type Dimensions struct {
|
||||||
|
// Width is the width, in number of board squares, of the board.
|
||||||
|
// The value 0 has a special meaning to mean unlimited.
|
||||||
|
Width uint
|
||||||
|
// Height is the height, in number of board squares, of the board.
|
||||||
|
// The value 0 has a special meaning to mean unlimited.
|
||||||
|
Height uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// sizes is a list of board sizes that a map supports.
|
||||||
|
type sizes []Dimensions
|
||||||
|
|
||||||
|
// IsUnlimited reports whether the supported sizes are unlimited.
|
||||||
|
// Note that even for unlimited sizes, there will be an upper bound that can actually be run and visualised.
|
||||||
|
func (d sizes) IsUnlimited() bool {
|
||||||
|
return len(d) == 1 && d[0].Width == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnySize creates sizes for a board that has no fixed sizes (supports unlimited sizes).
|
||||||
|
func AnySize() sizes {
|
||||||
|
return sizes{Dimensions{Width: 0, Height: 0}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixedSizes creates dimensions for a board that has 1 or more fixed sizes.
|
||||||
|
// Examples:
|
||||||
|
// - FixedSizes(Dimension{9,11}) supports only a width of 9 and a height of 11.
|
||||||
|
// - FixedSizes(Dimensions{11,11},Dimensions{19,19}) supports sizes 11x11 and 19x19
|
||||||
|
func FixedSizes(a Dimensions, b ...Dimensions) sizes {
|
||||||
|
s := make(sizes, 0, 1+len(b))
|
||||||
|
s = append(s, a)
|
||||||
|
s = append(s, b...)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Name string
|
Name string
|
||||||
Author string
|
Author string
|
||||||
|
|
@ -25,6 +60,15 @@ type Metadata struct {
|
||||||
// Version is the current version of the game map.
|
// Version is the current version of the game map.
|
||||||
// Each time a map is changed, the version number should be incremented by 1.
|
// Each time a map is changed, the version number should be incremented by 1.
|
||||||
Version uint
|
Version uint
|
||||||
|
// MinPlayers is the minimum number of players that the map supports.
|
||||||
|
MinPlayers uint
|
||||||
|
// MaxPlayers is the maximum number of players that the map supports.
|
||||||
|
MaxPlayers uint
|
||||||
|
// BoardSizes is a list of supported board sizes. Board sizes can fall into one of 3 categories:
|
||||||
|
// 1. one fixed size (i.e. [11x11])
|
||||||
|
// 2. multiple, fixed sizes (i.e. [11x11, 19x19, 25x25])
|
||||||
|
// 3. "unlimited" sizes (the board is not fixed and can scale to any reasonable size)
|
||||||
|
BoardSizes sizes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor is used by GameMap implementations to modify the board state.
|
// Editor is used by GameMap implementations to modify the board state.
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,18 @@ func TestBoardStateEditor(t *testing.T) {
|
||||||
editor.ClearHazards()
|
editor.ClearHazards()
|
||||||
require.Equal(t, []rules.Point{}, boardState.Hazards)
|
require.Equal(t, []rules.Point{}, boardState.Hazards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapSizes(t *testing.T) {
|
||||||
|
s := FixedSizes(Dimensions{11, 12})
|
||||||
|
require.Equal(t, s[0].Width, uint(11))
|
||||||
|
require.Equal(t, s[0].Height, uint(12))
|
||||||
|
|
||||||
|
s = FixedSizes(Dimensions{11, 11}, Dimensions{19, 25})
|
||||||
|
require.Len(t, s, 2)
|
||||||
|
require.Equal(t, s[1].Width, uint(19))
|
||||||
|
require.Equal(t, s[1].Height, uint(25))
|
||||||
|
|
||||||
|
s = AnySize()
|
||||||
|
require.Len(t, s, 1, "unlimited maps should have just one dimensions")
|
||||||
|
require.True(t, s.IsUnlimited())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ func (m InnerBorderHazardsMap) Meta() Metadata {
|
||||||
Description: "Creates a static map on turn 0 that is a 1-square wall of hazard that is inset 2 squares from the edge of the board",
|
Description: "Creates a static map on turn 0 that is a 1-square wall of hazard that is inset 2 squares from the edge of the board",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +70,9 @@ func (m ConcentricRingsHazardsMap) Meta() Metadata {
|
||||||
Description: "Creates a static map where there are rings of hazard sauce starting from the center with a 1 square space between the rings that has no sauce",
|
Description: "Creates a static map where there are rings of hazard sauce starting from the center with a 1 square space between the rings that has no sauce",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +111,9 @@ func (m ColumnsHazardsMap) Meta() Metadata {
|
||||||
Description: "Creates a static map on turn 0 that fills in odd squares, i.e. (1,1), (1,3), (3,3) ... with hazard sauce",
|
Description: "Creates a static map on turn 0 that fills in odd squares, i.e. (1,1), (1,3), (3,3) ... with hazard sauce",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,8 +148,11 @@ func (m SpiralHazardsMap) Meta() Metadata {
|
||||||
Name: "hz_spiral",
|
Name: "hz_spiral",
|
||||||
Description: `Generates a dynamic hazard map that grows in a spiral pattern clockwise from a random point on
|
Description: `Generates a dynamic hazard map that grows in a spiral pattern clockwise from a random point on
|
||||||
the map. Each 2 turns a new hazard square is added to the map`,
|
the map. Each 2 turns a new hazard square is added to the map`,
|
||||||
Author: "altersaddle",
|
Author: "altersaddle",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,6 +242,9 @@ func (m ScatterFillMap) Meta() Metadata {
|
||||||
Name: "hz_scatter",
|
Name: "hz_scatter",
|
||||||
Description: `Fills the entire board with hazard squares that are set to appear on regular turn schedule. Each square is picked at random.`,
|
Description: `Fills the entire board with hazard squares that are set to appear on regular turn schedule. Each square is picked at random.`,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +293,9 @@ func (m DirectionalExpandingBoxMap) Meta() Metadata {
|
||||||
Name: "hz_grow_box",
|
Name: "hz_grow_box",
|
||||||
Description: `Creates an area of hazard that expands from a point with one random side growing on a turn schedule.`,
|
Description: `Creates an area of hazard that expands from a point with one random side growing on a turn schedule.`,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,6 +407,9 @@ func (m ExpandingBoxMap) Meta() Metadata {
|
||||||
Name: "hz_expand_box",
|
Name: "hz_expand_box",
|
||||||
Description: `Generates an area of hazard that expands from a random point on the board outward in concentric rings on a periodic turn schedule.`,
|
Description: `Generates an area of hazard that expands from a random point on the board outward in concentric rings on a periodic turn schedule.`,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,6 +482,9 @@ func (m ExpandingScatterMap) Meta() Metadata {
|
||||||
Name: "hz_expand_scatter",
|
Name: "hz_expand_scatter",
|
||||||
Description: `Builds an expanding hazard area that grows from a central point in rings that are randomly filled in on a regular turn schedule.`,
|
Description: `Builds an expanding hazard area that grows from a central point in rings that are randomly filled in on a regular turn schedule.`,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,8 +562,11 @@ func (m RiverAndBridgesHazardsMap) Meta() Metadata {
|
||||||
Name: "hz_rivers_bridges",
|
Name: "hz_rivers_bridges",
|
||||||
Description: `Creates fixed maps that have a lake of hazard in the middle with rivers going in the cardinal directions.
|
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`,
|
Each river has one or two 1-square "bridges" over them`,
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: FixedSizes(Dimensions{11, 11}, Dimensions{19, 19}, Dimensions{25, 25}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,42 @@ func TestRegisteredMaps(t *testing.T) {
|
||||||
for mapName, gameMap := range globalRegistry {
|
for mapName, gameMap := range globalRegistry {
|
||||||
t.Run(mapName, func(t *testing.T) {
|
t.Run(mapName, func(t *testing.T) {
|
||||||
require.Equalf(t, mapName, gameMap.ID(), "%#v game map doesn't return its own ID", mapName)
|
require.Equalf(t, mapName, gameMap.ID(), "%#v game map doesn't return its own ID", mapName)
|
||||||
require.True(t, gameMap.Meta().Version > 0, fmt.Sprintf("registered maps must have a valid version (>= 1) - '%d' is invalid", gameMap.Meta().Version))
|
meta := gameMap.Meta()
|
||||||
|
require.True(t, meta.Version > 0, fmt.Sprintf("registered maps must have a valid version (>= 1) - '%d' is invalid", meta.Version))
|
||||||
|
require.NotZero(t, meta.MinPlayers, "registered maps must have minimum players declared")
|
||||||
|
require.NotZero(t, meta.MaxPlayers, "registered maps must have maximum players declared")
|
||||||
|
require.LessOrEqual(t, meta.MaxPlayers, meta.MaxPlayers, "max players should always be >= min players")
|
||||||
|
require.NotEmpty(t, meta.BoardSizes, "registered maps must have at least one supported size declared")
|
||||||
var setupBoardState *rules.BoardState
|
var setupBoardState *rules.BoardState
|
||||||
|
|
||||||
|
// "fuzz test" supported players
|
||||||
|
mapSize := pickSize(meta)
|
||||||
|
for i := meta.MinPlayers; i < meta.MaxPlayers; i++ {
|
||||||
|
t.Run(fmt.Sprintf("%d players", i), func(t *testing.T) {
|
||||||
|
initialBoardState := rules.NewBoardState(int(mapSize.Width), int(mapSize.Height))
|
||||||
|
for j := uint(0); j < i; j++ {
|
||||||
|
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(j), Body: []rules.Point{}})
|
||||||
|
}
|
||||||
|
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
||||||
|
require.NoError(t, err, fmt.Sprintf("%d players should be supported by this map", i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// "fuzz test" supported map sizes
|
||||||
|
if !meta.BoardSizes.IsUnlimited() {
|
||||||
|
for _, mapSize := range meta.BoardSizes {
|
||||||
|
t.Run(fmt.Sprintf("%dx%d map size", mapSize.Width, mapSize.Height), func(t *testing.T) {
|
||||||
|
initialBoardState := rules.NewBoardState(int(mapSize.Width), int(mapSize.Height))
|
||||||
|
for i := uint(0); i < meta.MaxPlayers; i++ {
|
||||||
|
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(i), Body: []rules.Point{}})
|
||||||
|
}
|
||||||
|
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
||||||
|
require.NoError(t, err, "error setting up map")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that at least one map size can be setup without error
|
||||||
for width := 0; width < maxBoardWidth; width++ {
|
for width := 0; width < maxBoardWidth; width++ {
|
||||||
for height := 0; height < maxBoardHeight; height++ {
|
for height := 0; height < maxBoardHeight; height++ {
|
||||||
initialBoardState := rules.NewBoardState(width, height)
|
initialBoardState := rules.NewBoardState(width, height)
|
||||||
|
|
@ -67,3 +100,13 @@ func TestRegisteredMaps(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pickSize(meta Metadata) Dimensions {
|
||||||
|
// For unlimited, we can pick any size
|
||||||
|
if meta.BoardSizes.IsUnlimited() {
|
||||||
|
return Dimensions{Width: 11, Height: 11}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For fixed, just pick the first supported size
|
||||||
|
return meta.BoardSizes[0]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ func (m RoyaleHazardsMap) Meta() Metadata {
|
||||||
Description: "A map where hazards are generated every N turns",
|
Description: "A map where hazards are generated every N turns",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ func (m StandardMap) Meta() Metadata {
|
||||||
Description: "Standard snake placement and food spawning",
|
Description: "Standard snake placement and food spawning",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
MinPlayers: 1,
|
||||||
|
MaxPlayers: 8,
|
||||||
|
BoardSizes: AnySize(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue