DEV 1676: Add maps helper functions (#111)
* add utility methods to Editor and BoardStateEditor * add Meta.Validate * allow setting Meta.MinPlayers to zero * remove uints in map sizes * use Meta.Validate in HazardPitsMap
This commit is contained in:
parent
c5810d8604
commit
c4247945ca
7 changed files with 701 additions and 83 deletions
|
|
@ -10,7 +10,7 @@ func init() {
|
||||||
globalRegistry.RegisterMap("hz_castle_wall_xl", CastleWallExtraLargeHazardsMap{})
|
globalRegistry.RegisterMap("hz_castle_wall_xl", CastleWallExtraLargeHazardsMap{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCastleWallBoard(maxPlayers uint, startingPositions []rules.Point, hazards []rules.Point, initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
func setupCastleWallBoard(maxPlayers int, startingPositions []rules.Point, hazards []rules.Point, initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||||
rand := settings.GetRand(initialBoardState.Turn)
|
rand := settings.GetRand(initialBoardState.Turn)
|
||||||
|
|
||||||
if len(initialBoardState.Snakes) > int(maxPlayers) {
|
if len(initialBoardState.Snakes) > int(maxPlayers) {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ func TestCastleWallHazardsMap(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Map maps.GameMap
|
Map maps.GameMap
|
||||||
Width uint
|
Width int
|
||||||
Height uint
|
Height int
|
||||||
}{
|
}{
|
||||||
{maps.CastleWallMediumHazardsMap{}, 11, 11},
|
{maps.CastleWallMediumHazardsMap{}, 11, 11},
|
||||||
{maps.CastleWallLargeHazardsMap{}, 19, 19},
|
{maps.CastleWallLargeHazardsMap{}, 19, 19},
|
||||||
|
|
@ -31,7 +31,7 @@ func TestCastleWallHazardsMap(t *testing.T) {
|
||||||
|
|
||||||
// check all the supported sizes
|
// check all the supported sizes
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
state = rules.NewBoardState(int(test.Width), int(test.Height))
|
state = rules.NewBoardState(test.Width, test.Height)
|
||||||
state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}})
|
state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}})
|
||||||
editor = maps.NewBoardStateEditor(state)
|
editor = maps.NewBoardStateEditor(state)
|
||||||
require.Empty(t, state.Hazards)
|
require.Empty(t, state.Hazards)
|
||||||
|
|
|
||||||
303
maps/game_map.go
303
maps/game_map.go
|
|
@ -1,6 +1,9 @@
|
||||||
package maps
|
package maps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules"
|
"github.com/BattlesnakeOfficial/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,14 +28,55 @@ type GameMap interface {
|
||||||
UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Name string
|
||||||
|
Author string
|
||||||
|
Description string
|
||||||
|
// Version is the current version of the game map.
|
||||||
|
// Each time a map is changed, the version number should be incremented by 1.
|
||||||
|
Version int
|
||||||
|
// MinPlayers is the minimum number of players that the map supports.
|
||||||
|
MinPlayers int
|
||||||
|
// MaxPlayers is the maximum number of players that the map supports.
|
||||||
|
MaxPlayers int
|
||||||
|
// 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
|
||||||
|
// Tags is a list of strings use to categorize the map.
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (meta Metadata) Validate(boardState *rules.BoardState) error {
|
||||||
|
if !meta.BoardSizes.IsAllowable(boardState.Width, boardState.Height) {
|
||||||
|
var sizesStrings []string
|
||||||
|
for _, size := range meta.BoardSizes {
|
||||||
|
sizesStrings = append(sizesStrings, fmt.Sprintf("%dx%d", size.Width, size.Height))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules.RulesetError("This map can only be played on these board sizes: " + strings.Join(sizesStrings, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.MinPlayers != 0 && len(boardState.Snakes) < int(meta.MinPlayers) {
|
||||||
|
return rules.RulesetError(fmt.Sprintf("This map can only be played with %d-%d players", meta.MinPlayers, meta.MaxPlayers))
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.MaxPlayers != 0 && len(boardState.Snakes) > int(meta.MaxPlayers) {
|
||||||
|
return rules.RulesetError(fmt.Sprintf("This map can only be played with %d-%d players", meta.MinPlayers, meta.MaxPlayers))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Dimensions describes the size of a Battlesnake board.
|
// Dimensions describes the size of a Battlesnake board.
|
||||||
type Dimensions struct {
|
type Dimensions struct {
|
||||||
// Width is the width, in number of board squares, of the board.
|
// Width is the width, in number of board squares, of the board.
|
||||||
// The value 0 has a special meaning to mean unlimited.
|
// The value 0 has a special meaning to mean unlimited.
|
||||||
Width uint
|
Width int
|
||||||
// Height is the height, in number of board squares, of the board.
|
// Height is the height, in number of board squares, of the board.
|
||||||
// The value 0 has a special meaning to mean unlimited.
|
// The value 0 has a special meaning to mean unlimited.
|
||||||
Height uint
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
// sizes is a list of board sizes that a map supports.
|
// sizes is a list of board sizes that a map supports.
|
||||||
|
|
@ -50,7 +94,7 @@ func (d sizes) IsAllowable(Width int, Height int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, size := range d {
|
for _, size := range d {
|
||||||
if size.Width == uint(Width) && size.Height == uint(Height) {
|
if size.Width == Width && size.Height == Height {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +111,7 @@ func AnySize() sizes {
|
||||||
// in the vertical and horizontal directions.
|
// in the vertical and horizontal directions.
|
||||||
// Examples:
|
// Examples:
|
||||||
// - OddSizes(11,21) produces [(11,11), (13,13), (15,15), (17,17), (19,19), (21,21)]
|
// - OddSizes(11,21) produces [(11,11), (13,13), (15,15), (17,17), (19,19), (21,21)]
|
||||||
func OddSizes(min, max uint) sizes {
|
func OddSizes(min, max int) sizes {
|
||||||
var s sizes
|
var s sizes
|
||||||
for i := min; i <= max; i += 2 {
|
for i := min; i <= max; i += 2 {
|
||||||
s = append(s, Dimensions{Width: i, Height: i})
|
s = append(s, Dimensions{Width: i, Height: i})
|
||||||
|
|
@ -87,107 +131,262 @@ func FixedSizes(a Dimensions, b ...Dimensions) sizes {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Name string
|
|
||||||
Author string
|
|
||||||
Description string
|
|
||||||
// Version is the current version of the game map.
|
|
||||||
// Each time a map is changed, the version number should be incremented by 1.
|
|
||||||
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
|
|
||||||
// Tags is a list of strings use to categorize the map.
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editor is used by GameMap implementations to modify the board state.
|
// Editor is used by GameMap implementations to modify the board state.
|
||||||
type Editor interface {
|
type Editor interface {
|
||||||
// Clears all food from the board.
|
// Clears all food from the board.
|
||||||
ClearFood()
|
ClearFood()
|
||||||
|
|
||||||
// Clears all hazards from the board.
|
|
||||||
ClearHazards()
|
|
||||||
|
|
||||||
// Adds a food to the board. Does not check for duplicates.
|
// Adds a food to the board. Does not check for duplicates.
|
||||||
AddFood(rules.Point)
|
AddFood(rules.Point)
|
||||||
|
|
||||||
// Adds a hazard to the board. Does not check for duplicates.
|
|
||||||
AddHazard(rules.Point)
|
|
||||||
|
|
||||||
// Removes all food from a specific tile on the board.
|
// Removes all food from a specific tile on the board.
|
||||||
RemoveFood(rules.Point)
|
RemoveFood(rules.Point)
|
||||||
|
|
||||||
|
// Get the locations of food currently on the board.
|
||||||
|
// Note: the return value is a copy and modifying it won't affect the board.
|
||||||
|
Food() []rules.Point
|
||||||
|
|
||||||
|
// Clears all hazards from the board.
|
||||||
|
ClearHazards()
|
||||||
|
|
||||||
|
// Adds a hazard to the board. Does not check for duplicates.
|
||||||
|
AddHazard(rules.Point)
|
||||||
|
|
||||||
// Removes all hazards from a specific tile on the board.
|
// Removes all hazards from a specific tile on the board.
|
||||||
RemoveHazard(rules.Point)
|
RemoveHazard(rules.Point)
|
||||||
|
|
||||||
|
// Get the locations of hazards currently on the board.
|
||||||
|
// Note: the return value is a copy and modifying it won't affect the board.
|
||||||
|
Hazards() []rules.Point
|
||||||
|
|
||||||
// Updates the body and health of a snake.
|
// Updates the body and health of a snake.
|
||||||
PlaceSnake(id string, body []rules.Point, health int)
|
PlaceSnake(id string, body []rules.Point, health int)
|
||||||
|
|
||||||
|
// Get the bodies of all non-eliminated snakes currently on the board, keyed by Snake ID
|
||||||
|
// Note: the body values in the return value are a copy and modifying them won't affect the board.
|
||||||
|
SnakeBodies() map[string][]rules.Point
|
||||||
|
|
||||||
|
// Given a list of Snakes and a list of head coordinates, randomly place
|
||||||
|
// the snakes on those coordinates, or return an error if placement of all
|
||||||
|
// Snakes is impossible.
|
||||||
|
PlaceSnakesRandomlyAtPositions(rand rules.Rand, snakes []rules.Snake, heads []rules.Point, bodyLength int) error
|
||||||
|
|
||||||
|
// Returns true if the provided point on the board is occupied by a snake body, food, and/or hazard.
|
||||||
|
IsOccupied(point rules.Point, snakes, hazards, food bool) bool
|
||||||
|
|
||||||
|
// Get a set of all points on the board the are occupied by snake bodies, food, and/or hazards.
|
||||||
|
// The value for each point will be set to true in the return value if that point is occupied by one of the selected objects.
|
||||||
|
OccupiedPoints(snakes, hazards, food bool) map[rules.Point]bool
|
||||||
|
|
||||||
|
// Given a list of points, return only those that are unoccupied by snake bodies, food, and/or hazards.
|
||||||
|
FilterUnoccupiedPoints(targets []rules.Point, snakes, hazards, food bool) []rules.Point
|
||||||
|
|
||||||
|
// Shuffle the provided slice of points randomly using the provided rules.Rand
|
||||||
|
ShufflePoints(rules.Rand, []rules.Point)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Editor backed by a BoardState.
|
// An Editor backed by a BoardState.
|
||||||
type BoardStateEditor struct {
|
type BoardStateEditor struct {
|
||||||
*rules.BoardState
|
boardState *rules.BoardState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoardStateEditor(boardState *rules.BoardState) *BoardStateEditor {
|
func NewBoardStateEditor(boardState *rules.BoardState) *BoardStateEditor {
|
||||||
return &BoardStateEditor{
|
return &BoardStateEditor{
|
||||||
BoardState: boardState,
|
boardState: boardState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (editor *BoardStateEditor) ClearFood() {
|
func (editor *BoardStateEditor) ClearFood() {
|
||||||
editor.Food = []rules.Point{}
|
editor.boardState.Food = []rules.Point{}
|
||||||
}
|
|
||||||
|
|
||||||
func (editor *BoardStateEditor) ClearHazards() {
|
|
||||||
editor.Hazards = []rules.Point{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (editor *BoardStateEditor) AddFood(p rules.Point) {
|
func (editor *BoardStateEditor) AddFood(p rules.Point) {
|
||||||
editor.Food = append(editor.Food, rules.Point{X: p.X, Y: p.Y})
|
editor.boardState.Food = append(editor.boardState.Food, rules.Point{X: p.X, Y: p.Y})
|
||||||
}
|
|
||||||
|
|
||||||
func (editor *BoardStateEditor) AddHazard(p rules.Point) {
|
|
||||||
editor.Hazards = append(editor.Hazards, rules.Point{X: p.X, Y: p.Y})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (editor *BoardStateEditor) RemoveFood(p rules.Point) {
|
func (editor *BoardStateEditor) RemoveFood(p rules.Point) {
|
||||||
for index, food := range editor.Food {
|
for index, food := range editor.boardState.Food {
|
||||||
if food.X == p.X && food.Y == p.Y {
|
if food.X == p.X && food.Y == p.Y {
|
||||||
editor.Food[index] = editor.Food[len(editor.Food)-1]
|
editor.boardState.Food[index] = editor.boardState.Food[len(editor.boardState.Food)-1]
|
||||||
editor.Food = editor.Food[:len(editor.Food)-1]
|
editor.boardState.Food = editor.boardState.Food[:len(editor.boardState.Food)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the locations of food currently on the board.
|
||||||
|
// Note: the return value is read-only.
|
||||||
|
func (editor *BoardStateEditor) Food() []rules.Point {
|
||||||
|
return append([]rules.Point(nil), editor.boardState.Food...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (editor *BoardStateEditor) ClearHazards() {
|
||||||
|
editor.boardState.Hazards = []rules.Point{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (editor *BoardStateEditor) AddHazard(p rules.Point) {
|
||||||
|
editor.boardState.Hazards = append(editor.boardState.Hazards, rules.Point{X: p.X, Y: p.Y})
|
||||||
|
}
|
||||||
|
|
||||||
func (editor *BoardStateEditor) RemoveHazard(p rules.Point) {
|
func (editor *BoardStateEditor) RemoveHazard(p rules.Point) {
|
||||||
for index, food := range editor.Hazards {
|
for index, food := range editor.boardState.Hazards {
|
||||||
if food.X == p.X && food.Y == p.Y {
|
if food.X == p.X && food.Y == p.Y {
|
||||||
editor.Hazards[index] = editor.Hazards[len(editor.Hazards)-1]
|
editor.boardState.Hazards[index] = editor.boardState.Hazards[len(editor.boardState.Hazards)-1]
|
||||||
editor.Hazards = editor.Hazards[:len(editor.Hazards)-1]
|
editor.boardState.Hazards = editor.boardState.Hazards[:len(editor.boardState.Hazards)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the locations of hazards currently on the board.
|
||||||
|
// Note: the return value is read-only.
|
||||||
|
func (editor *BoardStateEditor) Hazards() []rules.Point {
|
||||||
|
return append([]rules.Point(nil), editor.boardState.Hazards...)
|
||||||
|
}
|
||||||
|
|
||||||
func (editor *BoardStateEditor) PlaceSnake(id string, body []rules.Point, health int) {
|
func (editor *BoardStateEditor) PlaceSnake(id string, body []rules.Point, health int) {
|
||||||
for index, snake := range editor.Snakes {
|
for index, snake := range editor.boardState.Snakes {
|
||||||
if snake.ID == id {
|
if snake.ID == id {
|
||||||
editor.Snakes[index].Body = body
|
editor.boardState.Snakes[index].Body = body
|
||||||
editor.Snakes[index].Health = health
|
editor.boardState.Snakes[index].Health = health
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.Snakes = append(editor.Snakes, rules.Snake{
|
editor.boardState.Snakes = append(editor.boardState.Snakes, rules.Snake{
|
||||||
ID: id,
|
ID: id,
|
||||||
Health: health,
|
Health: health,
|
||||||
Body: body,
|
Body: body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the bodies of all non-eliminated snakes currently on the board.
|
||||||
|
// Note: the return value is read-only.
|
||||||
|
func (editor *BoardStateEditor) SnakeBodies() map[string][]rules.Point {
|
||||||
|
result := make(map[string][]rules.Point, len(editor.boardState.Snakes))
|
||||||
|
|
||||||
|
for _, snake := range editor.boardState.Snakes {
|
||||||
|
result[snake.ID] = append([]rules.Point(nil), snake.Body...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a list of Snakes and a list of head coordinates, randomly place
|
||||||
|
// the snakes on those coordinates, or return an error if placement of all
|
||||||
|
// Snakes is impossible.
|
||||||
|
func (editor *BoardStateEditor) PlaceSnakesRandomlyAtPositions(rand rules.Rand, snakes []rules.Snake, heads []rules.Point, bodyLength int) error {
|
||||||
|
if len(snakes) > len(heads) {
|
||||||
|
return rules.ErrorTooManySnakes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle starting points
|
||||||
|
editor.ShufflePoints(rand, heads)
|
||||||
|
|
||||||
|
// Assign starting points to snakes in order
|
||||||
|
for index, snake := range snakes {
|
||||||
|
head := heads[index]
|
||||||
|
body := make([]rules.Point, bodyLength)
|
||||||
|
for i := 0; i < bodyLength; i++ {
|
||||||
|
body[i] = head
|
||||||
|
}
|
||||||
|
editor.PlaceSnake(snake.ID, body, rules.SnakeMaxHealth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the provided point on the board is occupied by a snake body, food, and/or hazard.
|
||||||
|
func (editor *BoardStateEditor) IsOccupied(point rules.Point, snakes, hazards, food bool) bool {
|
||||||
|
if food {
|
||||||
|
for _, food := range editor.boardState.Food {
|
||||||
|
if food == point {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hazards {
|
||||||
|
for _, hazard := range editor.boardState.Hazards {
|
||||||
|
if hazard == point {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if snakes {
|
||||||
|
for _, snake := range editor.boardState.Snakes {
|
||||||
|
for _, body := range snake.Body {
|
||||||
|
if body == point {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a set of all points on the board the are occupied by snake bodies, food, and/or hazards.
|
||||||
|
// The value for each point will be set to true in the return value if that point is occupied by one of the selected objects.
|
||||||
|
func (editor *BoardStateEditor) OccupiedPoints(snakes, hazards, food bool) map[rules.Point]bool {
|
||||||
|
boardState := editor.boardState
|
||||||
|
result := make(map[rules.Point]bool, len(boardState.Food)+len(boardState.Hazards)+len(boardState.Snakes)*3)
|
||||||
|
|
||||||
|
if food {
|
||||||
|
for _, food := range editor.boardState.Food {
|
||||||
|
result[food] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hazards {
|
||||||
|
for _, hazard := range editor.boardState.Hazards {
|
||||||
|
result[hazard] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if snakes {
|
||||||
|
for _, snake := range editor.boardState.Snakes {
|
||||||
|
for _, body := range snake.Body {
|
||||||
|
result[body] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a list of points, return only those that are unoccupied by snake bodies, food, and/or hazards.
|
||||||
|
func (editor *BoardStateEditor) FilterUnoccupiedPoints(targets []rules.Point, snakes, hazards, food bool) []rules.Point {
|
||||||
|
result := make([]rules.Point, 0, len(targets))
|
||||||
|
|
||||||
|
targetLoop:
|
||||||
|
for _, point := range targets {
|
||||||
|
if food {
|
||||||
|
for _, food := range editor.boardState.Food {
|
||||||
|
if food == point {
|
||||||
|
continue targetLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hazards {
|
||||||
|
for _, hazard := range editor.boardState.Hazards {
|
||||||
|
if hazard == point {
|
||||||
|
continue targetLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if snakes {
|
||||||
|
for _, snake := range editor.boardState.Snakes {
|
||||||
|
for _, body := range snake.Body {
|
||||||
|
if body == point {
|
||||||
|
continue targetLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (editor *BoardStateEditor) ShufflePoints(rand rules.Rand, points []rules.Point) {
|
||||||
|
rand.Shuffle(len(points), func(i int, j int) {
|
||||||
|
points[i], points[j] = points[j], points[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,110 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMetadataValidate(t *testing.T) {
|
||||||
|
for label, test := range map[string]struct {
|
||||||
|
metadata Metadata
|
||||||
|
boardState *rules.BoardState
|
||||||
|
expected error
|
||||||
|
}{
|
||||||
|
"unlimited": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: AnySize(),
|
||||||
|
},
|
||||||
|
rules.NewBoardState(99, 99),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"in sizes": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: OddSizes(7, 25),
|
||||||
|
},
|
||||||
|
rules.NewBoardState(7, 7),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"too small": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: OddSizes(7, 25),
|
||||||
|
},
|
||||||
|
rules.NewBoardState(6, 6),
|
||||||
|
rules.RulesetError("This map can only be played on these board sizes: 7x7, 9x9, 11x11, 13x13, 15x15, 17x17, 19x19, 21x21, 23x23, 25x25"),
|
||||||
|
},
|
||||||
|
"too large": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: OddSizes(7, 25),
|
||||||
|
},
|
||||||
|
rules.NewBoardState(26, 26),
|
||||||
|
rules.RulesetError("This map can only be played on these board sizes: 7x7, 9x9, 11x11, 13x13, 15x15, 17x17, 19x19, 21x21, 23x23, 25x25"),
|
||||||
|
},
|
||||||
|
"valid players": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: AnySize(),
|
||||||
|
MinPlayers: 4,
|
||||||
|
MaxPlayers: 4,
|
||||||
|
},
|
||||||
|
&rules.BoardState{
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{ID: "1"},
|
||||||
|
{ID: "2"},
|
||||||
|
{ID: "3"},
|
||||||
|
{ID: "4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"too few players": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: AnySize(),
|
||||||
|
MinPlayers: 3,
|
||||||
|
MaxPlayers: 4,
|
||||||
|
},
|
||||||
|
&rules.BoardState{
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{ID: "1"},
|
||||||
|
{ID: "2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules.RulesetError("This map can only be played with 3-4 players"),
|
||||||
|
},
|
||||||
|
"too many players": {
|
||||||
|
Metadata{
|
||||||
|
BoardSizes: AnySize(),
|
||||||
|
MinPlayers: 3,
|
||||||
|
MaxPlayers: 4,
|
||||||
|
},
|
||||||
|
&rules.BoardState{
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{ID: "1"},
|
||||||
|
{ID: "2"},
|
||||||
|
{ID: "3"},
|
||||||
|
{ID: "4"},
|
||||||
|
{ID: "5"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules.RulesetError("This map can only be played with 3-4 players"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(label, func(t *testing.T) {
|
||||||
|
actual := test.metadata.Validate(test.boardState)
|
||||||
|
require.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapSizes(t *testing.T) {
|
||||||
|
s := FixedSizes(Dimensions{11, 12})
|
||||||
|
require.Equal(t, s[0].Width, 11)
|
||||||
|
require.Equal(t, s[0].Height, 12)
|
||||||
|
|
||||||
|
s = FixedSizes(Dimensions{11, 11}, Dimensions{19, 25})
|
||||||
|
require.Len(t, s, 2)
|
||||||
|
require.Equal(t, s[1].Width, 19)
|
||||||
|
require.Equal(t, s[1].Height, 25)
|
||||||
|
|
||||||
|
s = AnySize()
|
||||||
|
require.Len(t, s, 1, "unlimited maps should have just one dimensions")
|
||||||
|
require.True(t, s.IsUnlimited())
|
||||||
|
}
|
||||||
|
|
||||||
func TestBoardStateEditorInterface(t *testing.T) {
|
func TestBoardStateEditorInterface(t *testing.T) {
|
||||||
var _ Editor = (*BoardStateEditor)(nil)
|
var _ Editor = (*BoardStateEditor)(nil)
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +122,7 @@ func TestBoardStateEditor(t *testing.T) {
|
||||||
Health: 100,
|
Health: 100,
|
||||||
})
|
})
|
||||||
|
|
||||||
editor := BoardStateEditor{BoardState: boardState}
|
editor := BoardStateEditor{boardState: boardState}
|
||||||
|
|
||||||
editor.AddFood(rules.Point{X: 1, Y: 3})
|
editor.AddFood(rules.Point{X: 1, Y: 3})
|
||||||
editor.AddFood(rules.Point{X: 3, Y: 6})
|
editor.AddFood(rules.Point{X: 3, Y: 6})
|
||||||
|
|
@ -56,6 +160,26 @@ func TestBoardStateEditor(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, boardState)
|
}, boardState)
|
||||||
|
|
||||||
|
require.Equal(t, []rules.Point{
|
||||||
|
{X: 1, Y: 3},
|
||||||
|
{X: 3, Y: 7},
|
||||||
|
}, editor.Food())
|
||||||
|
|
||||||
|
require.Equal(t, []rules.Point{
|
||||||
|
{X: 1, Y: 3},
|
||||||
|
{X: 3, Y: 7},
|
||||||
|
}, editor.Hazards())
|
||||||
|
|
||||||
|
require.Equal(t, map[string][]rules.Point{
|
||||||
|
"existing_snake": {
|
||||||
|
{X: 5, Y: 2}, {X: 5, Y: 1}, {X: 5, Y: 0},
|
||||||
|
},
|
||||||
|
"new_snake": {
|
||||||
|
|
||||||
|
{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1},
|
||||||
|
},
|
||||||
|
}, editor.SnakeBodies())
|
||||||
|
|
||||||
editor.ClearFood()
|
editor.ClearFood()
|
||||||
require.Equal(t, []rules.Point{}, boardState.Food)
|
require.Equal(t, []rules.Point{}, boardState.Food)
|
||||||
|
|
||||||
|
|
@ -63,17 +187,317 @@ func TestBoardStateEditor(t *testing.T) {
|
||||||
require.Equal(t, []rules.Point{}, boardState.Hazards)
|
require.Equal(t, []rules.Point{}, boardState.Hazards)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapSizes(t *testing.T) {
|
func TestBoardStateEditorPlaceSnakesRandomlyAtPositions(t *testing.T) {
|
||||||
s := FixedSizes(Dimensions{11, 12})
|
for label, test := range map[string]struct {
|
||||||
require.Equal(t, s[0].Width, uint(11))
|
rand rules.Rand
|
||||||
require.Equal(t, s[0].Height, uint(12))
|
initialSnakes []rules.Snake
|
||||||
|
heads []rules.Point
|
||||||
|
bodyLength int
|
||||||
|
expectedError error
|
||||||
|
expectedSnakes []rules.Snake
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
rules.MinRand,
|
||||||
|
[]rules.Snake{},
|
||||||
|
[]rules.Point{},
|
||||||
|
0,
|
||||||
|
nil,
|
||||||
|
[]rules.Snake{},
|
||||||
|
},
|
||||||
|
"too many snakes": {
|
||||||
|
rules.MinRand,
|
||||||
|
[]rules.Snake{
|
||||||
|
{ID: "1"}, {ID: "2"}, {ID: "3"},
|
||||||
|
},
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 6, Y: 2}},
|
||||||
|
3,
|
||||||
|
rules.ErrorTooManySnakes,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"success unshuffled": {
|
||||||
|
rules.MinRand,
|
||||||
|
[]rules.Snake{
|
||||||
|
{ID: "1"}, {ID: "2"},
|
||||||
|
},
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 6, Y: 2}},
|
||||||
|
3,
|
||||||
|
nil,
|
||||||
|
[]rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 3, Y: 3}, {X: 3, Y: 3}, {X: 3, Y: 3}},
|
||||||
|
Health: rules.SnakeMaxHealth,
|
||||||
|
}, {
|
||||||
|
ID: "2",
|
||||||
|
Body: []rules.Point{{X: 6, Y: 2}, {X: 6, Y: 2}, {X: 6, Y: 2}},
|
||||||
|
Health: rules.SnakeMaxHealth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"success shuffled": {
|
||||||
|
rules.MaxRand,
|
||||||
|
[]rules.Snake{
|
||||||
|
{ID: "1"}, {ID: "2"},
|
||||||
|
},
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 6, Y: 2}},
|
||||||
|
3,
|
||||||
|
nil,
|
||||||
|
[]rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 6, Y: 2}, {X: 6, Y: 2}, {X: 6, Y: 2}},
|
||||||
|
Health: rules.SnakeMaxHealth,
|
||||||
|
}, {
|
||||||
|
ID: "2",
|
||||||
|
Body: []rules.Point{{X: 3, Y: 3}, {X: 3, Y: 3}, {X: 3, Y: 3}},
|
||||||
|
Health: rules.SnakeMaxHealth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(label, func(t *testing.T) {
|
||||||
|
boardState := rules.NewBoardState(rules.BoardSizeSmall, rules.BoardSizeSmall)
|
||||||
|
boardState.Snakes = test.initialSnakes
|
||||||
|
editor := NewBoardStateEditor(boardState)
|
||||||
|
|
||||||
s = FixedSizes(Dimensions{11, 11}, Dimensions{19, 25})
|
err := editor.PlaceSnakesRandomlyAtPositions(test.rand, test.initialSnakes, test.heads, test.bodyLength)
|
||||||
require.Len(t, s, 2)
|
if test.expectedError != nil {
|
||||||
require.Equal(t, s[1].Width, uint(19))
|
require.Equal(t, test.expectedError, err)
|
||||||
require.Equal(t, s[1].Height, uint(25))
|
} else {
|
||||||
|
require.Equal(t, test.expectedSnakes, boardState.Snakes)
|
||||||
s = AnySize()
|
}
|
||||||
require.Len(t, s, 1, "unlimited maps should have just one dimensions")
|
})
|
||||||
require.True(t, s.IsUnlimited())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoardStateEditorIsOccupied(t *testing.T) {
|
||||||
|
for label, test := range map[string]struct {
|
||||||
|
boardState *rules.BoardState
|
||||||
|
point rules.Point
|
||||||
|
snakes, hazards, food bool
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
"empty board": {
|
||||||
|
rules.NewBoardState(rules.BoardSizeSmall, rules.BoardSizeSmall),
|
||||||
|
rules.Point{X: 3, Y: 3},
|
||||||
|
true, true, true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"unoccupied": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
Hazards: []rules.Point{{X: 2, Y: 2}},
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 3, Y: 3}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules.Point{X: 2, Y: 3},
|
||||||
|
true, true, true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"food": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
false, false, true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"ignored food": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
false, false, false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"hazard": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Hazards: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
false, true, false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"ignored hazard": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
false, false, false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"snake": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
true, false, false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"ignored snake": {
|
||||||
|
&rules.BoardState{
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules.Point{X: 1, Y: 1},
|
||||||
|
false, false, false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(label, func(t *testing.T) {
|
||||||
|
editor := NewBoardStateEditor(test.boardState)
|
||||||
|
|
||||||
|
actual := editor.IsOccupied(test.point, test.snakes, test.hazards, test.food)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoardStateEditorOccupiedPoints(t *testing.T) {
|
||||||
|
testBoardState := &rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
Hazards: []rules.Point{{X: 2, Y: 2}},
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 3, Y: 3}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for label, test := range map[string]struct {
|
||||||
|
boardState *rules.BoardState
|
||||||
|
snakes, hazards, food bool
|
||||||
|
expected map[rules.Point]bool
|
||||||
|
}{
|
||||||
|
"empty board": {
|
||||||
|
rules.NewBoardState(rules.BoardSizeSmall, rules.BoardSizeSmall),
|
||||||
|
true, true, true,
|
||||||
|
map[rules.Point]bool{},
|
||||||
|
},
|
||||||
|
"all types": {
|
||||||
|
testBoardState,
|
||||||
|
true, true, true,
|
||||||
|
map[rules.Point]bool{
|
||||||
|
{X: 1, Y: 1}: true,
|
||||||
|
{X: 2, Y: 2}: true,
|
||||||
|
{X: 3, Y: 3}: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ignore snakes": {
|
||||||
|
testBoardState,
|
||||||
|
false, true, true,
|
||||||
|
map[rules.Point]bool{
|
||||||
|
{X: 1, Y: 1}: true,
|
||||||
|
{X: 2, Y: 2}: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ignore hazards": {
|
||||||
|
testBoardState,
|
||||||
|
true, false, true,
|
||||||
|
map[rules.Point]bool{
|
||||||
|
{X: 1, Y: 1}: true,
|
||||||
|
{X: 3, Y: 3}: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ignore food": {
|
||||||
|
testBoardState,
|
||||||
|
true, true, false,
|
||||||
|
map[rules.Point]bool{
|
||||||
|
{X: 2, Y: 2}: true,
|
||||||
|
{X: 3, Y: 3}: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(label, func(t *testing.T) {
|
||||||
|
editor := NewBoardStateEditor(test.boardState)
|
||||||
|
|
||||||
|
actual := editor.OccupiedPoints(test.snakes, test.hazards, test.food)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoardStateEditorFilterUnoccupiedPoints(t *testing.T) {
|
||||||
|
testBoardState := &rules.BoardState{
|
||||||
|
Food: []rules.Point{{X: 1, Y: 1}},
|
||||||
|
Hazards: []rules.Point{{X: 2, Y: 2}},
|
||||||
|
Snakes: []rules.Snake{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Body: []rules.Point{{X: 3, Y: 3}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for label, test := range map[string]struct {
|
||||||
|
boardState *rules.BoardState
|
||||||
|
targets []rules.Point
|
||||||
|
snakes, hazards, food bool
|
||||||
|
expected []rules.Point
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
rules.NewBoardState(rules.BoardSizeSmall, rules.BoardSizeSmall),
|
||||||
|
[]rules.Point{},
|
||||||
|
true, true, true,
|
||||||
|
[]rules.Point{},
|
||||||
|
},
|
||||||
|
"all types": {
|
||||||
|
testBoardState,
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 1, Y: 1}, {X: 2, Y: 2}, {X: 2, Y: 1}},
|
||||||
|
true, true, true,
|
||||||
|
[]rules.Point{{X: 2, Y: 1}},
|
||||||
|
},
|
||||||
|
"ignore snakes": {
|
||||||
|
testBoardState,
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 1, Y: 1}, {X: 2, Y: 2}, {X: 2, Y: 1}},
|
||||||
|
false, true, true,
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 2, Y: 1}},
|
||||||
|
},
|
||||||
|
"ignore hazards": {
|
||||||
|
testBoardState,
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 1, Y: 1}, {X: 2, Y: 2}, {X: 2, Y: 1}},
|
||||||
|
true, false, true,
|
||||||
|
[]rules.Point{{X: 2, Y: 2}, {X: 2, Y: 1}},
|
||||||
|
},
|
||||||
|
"ignore food": {
|
||||||
|
testBoardState,
|
||||||
|
[]rules.Point{{X: 3, Y: 3}, {X: 1, Y: 1}, {X: 2, Y: 2}, {X: 2, Y: 1}},
|
||||||
|
true, true, false,
|
||||||
|
[]rules.Point{{X: 1, Y: 1}, {X: 2, Y: 1}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(label, func(t *testing.T) {
|
||||||
|
editor := NewBoardStateEditor(test.boardState)
|
||||||
|
|
||||||
|
actual := editor.FilterUnoccupiedPoints(test.targets, test.snakes, test.hazards, test.food)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoardStateEditorShufflePoints(t *testing.T) {
|
||||||
|
editor := NewBoardStateEditor(rules.NewBoardState(rules.BoardSizeSmall, rules.BoardSizeSmall))
|
||||||
|
points := []rules.Point{{X: 4, Y: 0}, {X: 3, Y: 1}, {X: 2, Y: 2}, {X: 1, Y: 3}, {X: 0, Y: 4}}
|
||||||
|
|
||||||
|
editor.ShufflePoints(rules.MaxRand, points)
|
||||||
|
expected := []rules.Point{{X: 3, Y: 1}, {X: 2, Y: 2}, {X: 1, Y: 3}, {X: 0, Y: 4}, {X: 4, Y: 0}}
|
||||||
|
|
||||||
|
require.Equal(t, expected, points)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ func (m HazardPitsMap) Meta() Metadata {
|
||||||
Description: "A map that that fills in grid-like pattern of squares with pits filled with hazard sauce. Every N turns the pits will fill with another layer of sauce up to a maximum of 4 layers which last a few cycles, then the pits drain and the pattern repeats",
|
Description: "A map that that fills in grid-like pattern of squares with pits filled with hazard sauce. Every N turns the pits will fill with another layer of sauce up to a maximum of 4 layers which last a few cycles, then the pits drain and the pattern repeats",
|
||||||
Author: "Battlesnake",
|
Author: "Battlesnake",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
MinPlayers: 1,
|
MinPlayers: 0,
|
||||||
MaxPlayers: 4,
|
MaxPlayers: len(hazardPitStartPositions),
|
||||||
BoardSizes: FixedSizes(Dimensions{11, 11}),
|
BoardSizes: FixedSizes(Dimensions{11, 11}),
|
||||||
Tags: []string{TAG_FOOD_PLACEMENT, TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT},
|
Tags: []string{TAG_FOOD_PLACEMENT, TAG_HAZARD_PLACEMENT, TAG_SNAKE_PLACEMENT},
|
||||||
}
|
}
|
||||||
|
|
@ -47,12 +47,8 @@ func (m HazardPitsMap) AddHazardPits(board *rules.BoardState, settings rules.Set
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m HazardPitsMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
func (m HazardPitsMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||||
if !m.Meta().BoardSizes.IsAllowable(initialBoardState.Width, initialBoardState.Height) {
|
if err := m.Meta().Validate(initialBoardState); err != nil {
|
||||||
return rules.RulesetError("This map can only be played on a 11x11 board")
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
if len(initialBoardState.Snakes) > len(hazardPitStartPositions) {
|
|
||||||
return rules.ErrorTooManySnakes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rand := settings.GetRand(0)
|
rand := settings.GetRand(0)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ func TestRegisteredMaps(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)
|
||||||
meta := gameMap.Meta()
|
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.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.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.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")
|
require.NotEmpty(t, meta.BoardSizes, "registered maps must have at least one supported size declared")
|
||||||
|
|
@ -37,7 +36,7 @@ func TestRegisteredMaps(t *testing.T) {
|
||||||
for i := meta.MinPlayers; i < meta.MaxPlayers; i++ {
|
for i := meta.MinPlayers; i < meta.MaxPlayers; i++ {
|
||||||
t.Run(fmt.Sprintf("%d players", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d players", i), func(t *testing.T) {
|
||||||
initialBoardState := rules.NewBoardState(int(mapSize.Width), int(mapSize.Height))
|
initialBoardState := rules.NewBoardState(int(mapSize.Width), int(mapSize.Height))
|
||||||
for j := uint(0); j < i; j++ {
|
for j := 0; j < i; j++ {
|
||||||
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(j), Body: []rules.Point{}})
|
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(j), Body: []rules.Point{}})
|
||||||
}
|
}
|
||||||
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
||||||
|
|
@ -50,7 +49,7 @@ func TestRegisteredMaps(t *testing.T) {
|
||||||
for _, mapSize := range meta.BoardSizes {
|
for _, mapSize := range meta.BoardSizes {
|
||||||
t.Run(fmt.Sprintf("%dx%d map size", mapSize.Width, mapSize.Height), func(t *testing.T) {
|
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))
|
initialBoardState := rules.NewBoardState(int(mapSize.Width), int(mapSize.Height))
|
||||||
for i := uint(0); i < meta.MaxPlayers; i++ {
|
for i := 0; i < meta.MaxPlayers; i++ {
|
||||||
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(i), Body: []rules.Point{}})
|
initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: fmt.Sprint(i), Body: []rules.Point{}})
|
||||||
}
|
}
|
||||||
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
err := gameMap.SetupBoard(initialBoardState, testSettings, NewBoardStateEditor(initialBoardState))
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ func TestRiversAndBridgetsHazardsMap(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Map maps.GameMap
|
Map maps.GameMap
|
||||||
Width uint
|
Width int
|
||||||
Height uint
|
Height int
|
||||||
}{
|
}{
|
||||||
{maps.RiverAndBridgesMediumHazardsMap{}, 11, 11},
|
{maps.RiverAndBridgesMediumHazardsMap{}, 11, 11},
|
||||||
{maps.RiverAndBridgesLargeHazardsMap{}, 19, 19},
|
{maps.RiverAndBridgesLargeHazardsMap{}, 19, 19},
|
||||||
|
|
@ -33,7 +33,7 @@ func TestRiversAndBridgetsHazardsMap(t *testing.T) {
|
||||||
|
|
||||||
// check all the supported sizes
|
// check all the supported sizes
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
state = rules.NewBoardState(int(test.Width), int(test.Height))
|
state = rules.NewBoardState(test.Width, test.Height)
|
||||||
state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}})
|
state.Snakes = append(state.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}})
|
||||||
editor = maps.NewBoardStateEditor(state)
|
editor = maps.NewBoardStateEditor(state)
|
||||||
require.Empty(t, state.Hazards)
|
require.Empty(t, state.Hazards)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue