package maps import ( "fmt" "testing" "github.com/BattlesnakeOfficial/rules" "github.com/stretchr/testify/require" ) const maxBoardWidth, maxBoardHeight = 25, 25 var testSettings rules.Settings = rules.NewSettings(map[string]string{ rules.ParamFoodSpawnChance: "25", rules.ParamMinimumFood: "1", rules.ParamHazardDamagePerTurn: "14", rules.ParamShrinkEveryNTurns: "1", }) func TestRegisteredMaps(t *testing.T) { for mapName, gameMap := range globalRegistry { t.Run(mapName, func(t *testing.T) { require.Equalf(t, mapName, gameMap.ID(), "%#v game map doesn't return its own ID", mapName) 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.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") require.NotNil(t, meta.Tags) 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 := 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 := 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 height := 0; height <= maxBoardHeight; height++ { initialBoardState := rules.NewBoardState(width, height) initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: "1", Body: []rules.Point{}}) if meta.MaxPlayers > 1 { initialBoardState.Snakes = append(initialBoardState.Snakes, rules.Snake{ID: "2", Body: []rules.Point{}}) } passedBoardState := initialBoardState.Clone() tempBoardState := initialBoardState.Clone() err := gameMap.SetupBoard(passedBoardState, testSettings, NewBoardStateEditor(tempBoardState)) if err == nil { setupBoardState = tempBoardState require.Equal(t, initialBoardState, passedBoardState, "BoardState should not be modified directly by GameMap.SetupBoard") break } } } require.NotNil(t, setupBoardState, "Map does not successfully setup the board at any supported combination of width and height") require.NotNil(t, setupBoardState.Food) require.NotNil(t, setupBoardState.Hazards) require.NotNil(t, setupBoardState.Snakes) for _, snake := range setupBoardState.Snakes { require.NotEmpty(t, snake.Body, "Map should place all snakes by initializing their body") } previousBoardState := rules.NewBoardState(rules.BoardSizeMedium, rules.BoardSizeMedium) previousBoardState.Food = append(previousBoardState.Food, []rules.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}...) previousBoardState.Hazards = append(previousBoardState.Food, []rules.Point{{X: 4, Y: 3}, {X: 2, Y: 1}}...) previousBoardState.Snakes = append(previousBoardState.Snakes, rules.Snake{ ID: "1", Body: []rules.Point{{X: 5, Y: 5}, {X: 5, Y: 4}, {X: 5, Y: 3}}, Health: 100, }) previousBoardState.Turn = 0 passedBoardState := previousBoardState.Clone() tempBoardState := previousBoardState.Clone() err := gameMap.PostUpdateBoard(passedBoardState, testSettings, NewBoardStateEditor(tempBoardState)) require.NoError(t, err, "GameMap.UpdateBoard returned an error") require.Equal(t, previousBoardState, passedBoardState, "BoardState should not be modified directly by GameMap.UpdateBoard") }) } } 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] } func TestListRegisteredMaps(t *testing.T) { keys := globalRegistry.List() mapCount := 0 for k := range globalRegistry { // every registry key should exist in List results require.Contains(t, keys, k) mapCount++ } // List should equal number of maps in the global registry require.Equal(t, len(keys), mapCount) }