DEV 1247: Add a new map generator interface (#71)
* reorganize code * first draft of map generator interfaces * add explicit random interface to board helpers * implement standard map * rename Generator to GameMap * allow initializing snakes separately from placing them * add random number generator to Settings * updates to GameMap interface * add helpers for creating and updating BoardState with maps
This commit is contained in:
parent
1c3f434841
commit
dab9178a55
16 changed files with 916 additions and 160 deletions
115
maps/game_map.go
Normal file
115
maps/game_map.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package maps
|
||||
|
||||
import "github.com/BattlesnakeOfficial/rules"
|
||||
|
||||
type GameMap interface {
|
||||
// Return a unique identifier for this map.
|
||||
ID() string
|
||||
|
||||
// Return non-functional metadata about this map.
|
||||
Meta() Metadata
|
||||
|
||||
// Called to generate a new board. The map is responsible for placing all snakes, food, and hazards.
|
||||
SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
||||
|
||||
// Called every turn to optionally update the board.
|
||||
UpdateBoard(previousBoardState *rules.BoardState, settings rules.Settings, editor Editor) error
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string
|
||||
Author string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Editor is used by GameMap implementations to modify the board state.
|
||||
type Editor interface {
|
||||
// Returns a random number generator. This MUST be used for any non-deterministic behavior in a GameMap.
|
||||
Random() rules.Rand
|
||||
|
||||
// Clears all food from the board.
|
||||
ClearFood()
|
||||
|
||||
// Clears all hazards from the board.
|
||||
ClearHazards()
|
||||
|
||||
// Adds a food to the board. Does not check for duplicates.
|
||||
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.
|
||||
RemoveFood(rules.Point)
|
||||
|
||||
// Removes all hazards from a specific tile on the board.
|
||||
RemoveHazard(rules.Point)
|
||||
|
||||
// Updates the body and health of a snake.
|
||||
PlaceSnake(id string, body []rules.Point, health int32)
|
||||
}
|
||||
|
||||
// An Editor backed by a BoardState.
|
||||
type BoardStateEditor struct {
|
||||
*rules.BoardState
|
||||
rand rules.Rand
|
||||
}
|
||||
|
||||
func NewBoardStateEditor(boardState *rules.BoardState, rand rules.Rand) *BoardStateEditor {
|
||||
return &BoardStateEditor{
|
||||
BoardState: boardState,
|
||||
rand: rand,
|
||||
}
|
||||
}
|
||||
|
||||
func (editor *BoardStateEditor) Random() rules.Rand { return editor.rand }
|
||||
|
||||
func (editor *BoardStateEditor) ClearFood() {
|
||||
editor.Food = []rules.Point{}
|
||||
}
|
||||
|
||||
func (editor *BoardStateEditor) ClearHazards() {
|
||||
editor.Hazards = []rules.Point{}
|
||||
}
|
||||
|
||||
func (editor *BoardStateEditor) AddFood(p rules.Point) {
|
||||
editor.Food = append(editor.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) {
|
||||
for index, food := range editor.Food {
|
||||
if food.X == p.X && food.Y == p.Y {
|
||||
editor.Food[index] = editor.Food[len(editor.Food)-1]
|
||||
editor.Food = editor.Food[:len(editor.Food)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (editor *BoardStateEditor) RemoveHazard(p rules.Point) {
|
||||
for index, food := range editor.Hazards {
|
||||
if food.X == p.X && food.Y == p.Y {
|
||||
editor.Hazards[index] = editor.Hazards[len(editor.Hazards)-1]
|
||||
editor.Hazards = editor.Hazards[:len(editor.Hazards)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (editor *BoardStateEditor) PlaceSnake(id string, body []rules.Point, health int32) {
|
||||
for index, snake := range editor.Snakes {
|
||||
if snake.ID == id {
|
||||
editor.Snakes[index].Body = body
|
||||
editor.Snakes[index].Health = health
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
editor.Snakes = append(editor.Snakes, rules.Snake{
|
||||
ID: id,
|
||||
Health: health,
|
||||
Body: body,
|
||||
})
|
||||
}
|
||||
64
maps/game_map_test.go
Normal file
64
maps/game_map_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/BattlesnakeOfficial/rules"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBoardStateEditorInterface(t *testing.T) {
|
||||
var _ Editor = (*BoardStateEditor)(nil)
|
||||
}
|
||||
|
||||
func TestBoardStateEditor(t *testing.T) {
|
||||
boardState := rules.NewBoardState(11, 11)
|
||||
boardState.Snakes = append(boardState.Snakes, rules.Snake{
|
||||
ID: "existing_snake",
|
||||
Health: 100,
|
||||
})
|
||||
|
||||
editor := BoardStateEditor{BoardState: boardState}
|
||||
|
||||
editor.AddFood(rules.Point{X: 1, Y: 3})
|
||||
editor.AddFood(rules.Point{X: 3, Y: 6})
|
||||
editor.AddFood(rules.Point{X: 3, Y: 7})
|
||||
editor.RemoveFood(rules.Point{X: 3, Y: 6})
|
||||
editor.AddHazard(rules.Point{X: 1, Y: 3})
|
||||
editor.AddHazard(rules.Point{X: 3, Y: 6})
|
||||
editor.AddHazard(rules.Point{X: 3, Y: 7})
|
||||
editor.RemoveHazard(rules.Point{X: 3, Y: 6})
|
||||
editor.PlaceSnake("existing_snake", []rules.Point{{X: 5, Y: 2}, {X: 5, Y: 1}, {X: 5, Y: 0}}, 99)
|
||||
editor.PlaceSnake("new_snake", []rules.Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}}, 98)
|
||||
|
||||
require.Equal(t, &rules.BoardState{
|
||||
Width: 11,
|
||||
Height: 11,
|
||||
Food: []rules.Point{
|
||||
{X: 1, Y: 3},
|
||||
{X: 3, Y: 7},
|
||||
},
|
||||
Hazards: []rules.Point{
|
||||
{X: 1, Y: 3},
|
||||
{X: 3, Y: 7},
|
||||
},
|
||||
Snakes: []rules.Snake{
|
||||
{
|
||||
ID: "existing_snake",
|
||||
Health: 99,
|
||||
Body: []rules.Point{{X: 5, Y: 2}, {X: 5, Y: 1}, {X: 5, Y: 0}},
|
||||
},
|
||||
{
|
||||
ID: "new_snake",
|
||||
Health: 98,
|
||||
Body: []rules.Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}},
|
||||
},
|
||||
},
|
||||
}, boardState)
|
||||
|
||||
editor.ClearFood()
|
||||
require.Equal(t, []rules.Point{}, boardState.Food)
|
||||
|
||||
editor.ClearHazards()
|
||||
require.Equal(t, []rules.Point{}, boardState.Hazards)
|
||||
}
|
||||
42
maps/helpers.go
Normal file
42
maps/helpers.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
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(int32(width), int32(height))
|
||||
|
||||
rules.InitializeSnakes(boardState, snakeIDs)
|
||||
|
||||
gameMap, err := GetMap(mapID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
editor := NewBoardStateEditor(boardState, settings.Rand())
|
||||
|
||||
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, settings.Rand())
|
||||
|
||||
err = gameMap.SetupBoard(previousBoardState, settings, editor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nextBoardState, nil
|
||||
}
|
||||
35
maps/registry.go
Normal file
35
maps/registry.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/BattlesnakeOfficial/rules"
|
||||
)
|
||||
|
||||
// MapRegistry is a mapping of map names to game maps.
|
||||
type MapRegistry map[string]GameMap
|
||||
|
||||
var globalRegistry = MapRegistry{}
|
||||
|
||||
// RegisterMap adds a stage to the registry.
|
||||
// If a map has already been registered this will panic.
|
||||
func (registry MapRegistry) RegisterMap(id string, m GameMap) {
|
||||
if _, ok := registry[id]; ok {
|
||||
panic(fmt.Sprintf("map '%s' has already been registered", id))
|
||||
}
|
||||
|
||||
registry[id] = m
|
||||
}
|
||||
|
||||
// GetMap returns the map associated with the given ID.
|
||||
func (registry MapRegistry) GetMap(id string) (GameMap, error) {
|
||||
if m, ok := registry[id]; ok {
|
||||
return m, nil
|
||||
}
|
||||
return nil, rules.ErrorMapNotFound
|
||||
}
|
||||
|
||||
// GetMap returns the map associated with the given ID from the global registry.
|
||||
func GetMap(id string) (GameMap, error) {
|
||||
return globalRegistry.GetMap(id)
|
||||
}
|
||||
80
maps/standard.go
Normal file
80
maps/standard.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"github.com/BattlesnakeOfficial/rules"
|
||||
)
|
||||
|
||||
type StandardMap struct{}
|
||||
|
||||
func init() {
|
||||
globalRegistry.RegisterMap("standard", StandardMap{})
|
||||
}
|
||||
|
||||
func (m StandardMap) ID() string {
|
||||
return "standard"
|
||||
}
|
||||
|
||||
func (m StandardMap) Meta() Metadata {
|
||||
return Metadata{
|
||||
Name: "Standard",
|
||||
Description: "Standard snake placement and food spawning",
|
||||
Author: "Battlesnake",
|
||||
}
|
||||
}
|
||||
|
||||
func (m StandardMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||
snakeIDs := make([]string, 0, len(initialBoardState.Snakes))
|
||||
for _, snake := range initialBoardState.Snakes {
|
||||
snakeIDs = append(snakeIDs, snake.ID)
|
||||
}
|
||||
|
||||
tempBoardState, err := rules.CreateDefaultBoardState(editor.Random(), 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 StandardMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||
minFood := int(settings.MinimumFood)
|
||||
foodSpawnChance := int(settings.FoodSpawnChance)
|
||||
numCurrentFood := len(lastBoardState.Food)
|
||||
|
||||
if numCurrentFood < minFood {
|
||||
placeFoodRandomly(lastBoardState, editor, minFood-numCurrentFood)
|
||||
return nil
|
||||
}
|
||||
if foodSpawnChance > 0 && (100-editor.Random().Intn(100)) < foodSpawnChance {
|
||||
placeFoodRandomly(lastBoardState, editor, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func placeFoodRandomly(b *rules.BoardState, editor Editor, n int) {
|
||||
unoccupiedPoints := rules.GetUnoccupiedPoints(b, false)
|
||||
|
||||
if len(unoccupiedPoints) < n {
|
||||
n = len(unoccupiedPoints)
|
||||
}
|
||||
|
||||
editor.Random().Shuffle(len(unoccupiedPoints), func(i int, j int) {
|
||||
unoccupiedPoints[i], unoccupiedPoints[j] = unoccupiedPoints[j], unoccupiedPoints[i]
|
||||
})
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
editor.AddFood(unoccupiedPoints[i])
|
||||
}
|
||||
}
|
||||
322
maps/standard_test.go
Normal file
322
maps/standard_test.go
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/BattlesnakeOfficial/rules"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStandardMapInterface(t *testing.T) {
|
||||
var _ GameMap = StandardMap{}
|
||||
}
|
||||
|
||||
func TestStandardMapSetupBoard(t *testing.T) {
|
||||
m := StandardMap{}
|
||||
settings := rules.Settings{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
initialBoardState *rules.BoardState
|
||||
rand rules.Rand
|
||||
|
||||
expected *rules.BoardState
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"empty 7x7",
|
||||
rules.NewBoardState(7, 7),
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 7,
|
||||
Height: 7,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 3, Y: 3}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"not enough room for snakes 7x7",
|
||||
&rules.BoardState{
|
||||
Width: 7,
|
||||
Height: 7,
|
||||
Snakes: generateSnakes(9),
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.MinRand,
|
||||
nil,
|
||||
rules.ErrorTooManySnakes,
|
||||
},
|
||||
{
|
||||
"not enough room for snakes 5x5",
|
||||
&rules.BoardState{
|
||||
Width: 5,
|
||||
Height: 5,
|
||||
Snakes: generateSnakes(14),
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.MinRand,
|
||||
nil,
|
||||
rules.ErrorNoRoomForSnake,
|
||||
},
|
||||
{
|
||||
"full 11x11 min",
|
||||
&rules.BoardState{
|
||||
Width: 11,
|
||||
Height: 11,
|
||||
Snakes: generateSnakes(8),
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 11,
|
||||
Height: 11,
|
||||
Snakes: []rules.Snake{
|
||||
{ID: "1", Body: []rules.Point{{X: 1, Y: 1}, {X: 1, Y: 1}, {X: 1, Y: 1}}, Health: 100},
|
||||
{ID: "2", Body: []rules.Point{{X: 1, Y: 5}, {X: 1, Y: 5}, {X: 1, Y: 5}}, Health: 100},
|
||||
{ID: "3", Body: []rules.Point{{X: 1, Y: 9}, {X: 1, Y: 9}, {X: 1, Y: 9}}, Health: 100},
|
||||
{ID: "4", Body: []rules.Point{{X: 5, Y: 1}, {X: 5, Y: 1}, {X: 5, Y: 1}}, Health: 100},
|
||||
{ID: "5", Body: []rules.Point{{X: 5, Y: 9}, {X: 5, Y: 9}, {X: 5, Y: 9}}, Health: 100},
|
||||
{ID: "6", Body: []rules.Point{{X: 9, Y: 1}, {X: 9, Y: 1}, {X: 9, Y: 1}}, Health: 100},
|
||||
{ID: "7", Body: []rules.Point{{X: 9, Y: 5}, {X: 9, Y: 5}, {X: 9, Y: 5}}, Health: 100},
|
||||
{ID: "8", Body: []rules.Point{{X: 9, Y: 9}, {X: 9, Y: 9}, {X: 9, Y: 9}}, Health: 100},
|
||||
},
|
||||
Food: []rules.Point{
|
||||
{X: 0, Y: 2},
|
||||
{X: 0, Y: 4},
|
||||
{X: 0, Y: 8},
|
||||
{X: 4, Y: 0},
|
||||
{X: 4, Y: 10},
|
||||
{X: 8, Y: 0},
|
||||
{X: 10, Y: 4},
|
||||
{X: 8, Y: 10},
|
||||
{X: 5, Y: 5},
|
||||
},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"full 11x11 max",
|
||||
&rules.BoardState{
|
||||
Width: 11,
|
||||
Height: 11,
|
||||
Snakes: generateSnakes(8),
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.MaxRand,
|
||||
&rules.BoardState{
|
||||
Width: 11,
|
||||
Height: 11,
|
||||
Snakes: []rules.Snake{
|
||||
{ID: "1", Body: []rules.Point{{X: 1, Y: 5}, {X: 1, Y: 5}, {X: 1, Y: 5}}, Health: 100},
|
||||
{ID: "2", Body: []rules.Point{{X: 1, Y: 9}, {X: 1, Y: 9}, {X: 1, Y: 9}}, Health: 100},
|
||||
{ID: "3", Body: []rules.Point{{X: 5, Y: 1}, {X: 5, Y: 1}, {X: 5, Y: 1}}, Health: 100},
|
||||
{ID: "4", Body: []rules.Point{{X: 5, Y: 9}, {X: 5, Y: 9}, {X: 5, Y: 9}}, Health: 100},
|
||||
{ID: "5", Body: []rules.Point{{X: 9, Y: 1}, {X: 9, Y: 1}, {X: 9, Y: 1}}, Health: 100},
|
||||
{ID: "6", Body: []rules.Point{{X: 9, Y: 5}, {X: 9, Y: 5}, {X: 9, Y: 5}}, Health: 100},
|
||||
{ID: "7", Body: []rules.Point{{X: 9, Y: 9}, {X: 9, Y: 9}, {X: 9, Y: 9}}, Health: 100},
|
||||
{ID: "8", Body: []rules.Point{{X: 1, Y: 1}, {X: 1, Y: 1}, {X: 1, Y: 1}}, Health: 100},
|
||||
},
|
||||
Food: []rules.Point{
|
||||
{X: 0, Y: 6},
|
||||
{X: 2, Y: 10},
|
||||
{X: 6, Y: 0},
|
||||
{X: 6, Y: 10},
|
||||
{X: 10, Y: 2},
|
||||
{X: 10, Y: 6},
|
||||
{X: 10, Y: 8},
|
||||
{X: 2, Y: 0},
|
||||
{X: 5, Y: 5},
|
||||
},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
nextBoardState := rules.NewBoardState(test.initialBoardState.Width, test.initialBoardState.Height)
|
||||
editor := NewBoardStateEditor(nextBoardState, test.rand)
|
||||
|
||||
err := m.SetupBoard(test.initialBoardState, settings, editor)
|
||||
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, err)
|
||||
} else {
|
||||
require.Equal(t, test.expected, nextBoardState)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStandardMapUpdateBoard(t *testing.T) {
|
||||
m := StandardMap{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
initialBoardState *rules.BoardState
|
||||
settings rules.Settings
|
||||
rand rules.Rand
|
||||
|
||||
expected *rules.BoardState
|
||||
}{
|
||||
{
|
||||
"empty no food",
|
||||
rules.NewBoardState(2, 2),
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 0,
|
||||
MinimumFood: 0,
|
||||
},
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"empty MinimumFood",
|
||||
rules.NewBoardState(2, 2),
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 0,
|
||||
MinimumFood: 2,
|
||||
},
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 0}, {X: 0, Y: 1}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"not empty MinimumFood",
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 1}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 0,
|
||||
MinimumFood: 2,
|
||||
},
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 1}, {X: 0, Y: 0}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"empty FoodSpawnChance inactive",
|
||||
rules.NewBoardState(2, 2),
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 50,
|
||||
MinimumFood: 0,
|
||||
},
|
||||
rules.MinRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"empty FoodSpawnChance active",
|
||||
rules.NewBoardState(2, 2),
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 50,
|
||||
MinimumFood: 0,
|
||||
},
|
||||
rules.MaxRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 1}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"not empty FoodSpawnChance active",
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 0}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 50,
|
||||
MinimumFood: 0,
|
||||
},
|
||||
rules.MaxRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 0}, {X: 1, Y: 0}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"not empty FoodSpawnChance no room",
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 0}, {X: 1, Y: 1}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
rules.Settings{
|
||||
FoodSpawnChance: 50,
|
||||
MinimumFood: 0,
|
||||
},
|
||||
rules.MaxRand,
|
||||
&rules.BoardState{
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
Snakes: []rules.Snake{},
|
||||
Food: []rules.Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 0}, {X: 1, Y: 1}},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
nextBoardState := test.initialBoardState.Clone()
|
||||
editor := NewBoardStateEditor(nextBoardState, test.rand)
|
||||
|
||||
err := m.UpdateBoard(test.initialBoardState.Clone(), test.settings, editor)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, nextBoardState)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateSnakes(n int) []rules.Snake {
|
||||
var snakes []rules.Snake
|
||||
for i := 0; i < n; i++ {
|
||||
snakes = append(snakes, rules.Snake{
|
||||
ID: fmt.Sprint(i + 1),
|
||||
})
|
||||
}
|
||||
return snakes
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue