Byte-snake-engine/maps/game_map_test.go

504 lines
11 KiB
Go
Raw Normal View History

package maps
import (
"testing"
"github.com/BattlesnakeOfficial/rules"
"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) {
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)
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()
require.Equal(t, []rules.Point{}, boardState.Food)
editor.ClearHazards()
require.Equal(t, []rules.Point{}, boardState.Hazards)
}
func TestBoardStateEditorPlaceSnakesRandomlyAtPositions(t *testing.T) {
for label, test := range map[string]struct {
rand rules.Rand
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)
err := editor.PlaceSnakesRandomlyAtPositions(test.rand, test.initialSnakes, test.heads, test.bodyLength)
if test.expectedError != nil {
require.Equal(t, test.expectedError, err)
} else {
require.Equal(t, test.expectedSnakes, boardState.Snakes)
}
})
}
}
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)
}