Skip to content

Commit

Permalink
extensible terrain (#1775)
Browse files Browse the repository at this point in the history
Closes #1641

The `data/terrain.yaml` file is now the authoritative source of terrains, though `BlankT` is still a hard-coded special case.

I have not changed the underlying integer representation of terrain in the world function, which means that the `terrainIndexByName` Map in the `TerrainMap` record is needed for translating between `Int` and `TerrainType`.

# Demo

    scripts/play.sh -i data/scenarios/Testing/1775-custom-terrain.yaml

![Screenshot from 2024-02-22 16-51-53](https://github.com/swarm-game/swarm/assets/261693/1d263c8b-4e9c-40bf-bdc8-bf5ba8e33c4d)

# Changes

* There used to be a function called `integrateScenarioEntities` that combined the `EntityMap` stored in the `Scenario` record with the global entity map.  However, the global entity map is accessible at parse time of the `Scenario`, so we do the combining there and only ever store the combined map in the `Scenario` record.
* JSON Schema for terrain
* Removed the distinction between "World" attributes and "Terrain" attributes
* Unit tests for scenario-defined terrain and related validations
    * Validate existence of referenced terrain at scenario parse time
    * Validate attributes referenced by terrains at parse time
  • Loading branch information
kostmo authored Feb 29, 2024
1 parent 0d65a04 commit 936b30d
Show file tree
Hide file tree
Showing 58 changed files with 732 additions and 307 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"data/scenarios/**/*.yaml",
"scenarios/**/*.yaml"
],
"data/schema/terrains.json": [
"data/terrains.yaml"
],
"data/schema/entities.json": [
"data/entities.yaml"
],
Expand Down
1 change: 1 addition & 0 deletions app/doc/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ cliParser =
Data.Foldable.asum
[ pure Nothing
, Just Entities <$ switch (long "entities" <> help "Generate entities page (uses data from entities.yaml)")
, Just Terrain <$ switch (long "terrain" <> help "Generate terrain page (uses data from terrains.yaml)")
, Just Recipes <$ switch (long "recipes" <> help "Generate recipes page (uses data from recipes.yaml)")
, Just Capabilities <$ switch (long "capabilities" <> help "Generate capabilities page (uses entity map)")
, Just Commands <$ switch (long "commands" <> help "Generate commands page (uses constInfo, constCaps and inferConst)")
Expand Down
3 changes: 2 additions & 1 deletion app/doc/Swarm/Doc/Gen.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import Swarm.Doc.Util
import Swarm.Doc.Wiki.Cheatsheet
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityName)
import Swarm.Game.Entity qualified as E
import Swarm.Game.Land
import Swarm.Game.Recipe (Recipe, recipeCatalysts, recipeInputs, recipeOutputs)
import Swarm.Game.Robot (Robot, equippedDevices, robotInventory)
import Swarm.Game.Scenario (GameStateInputs (..), loadStandaloneScenario, scenarioLandscape)
Expand Down Expand Up @@ -135,7 +136,7 @@ generateSpecialKeyNames =

generateRecipe :: IO String
generateRecipe = simpleErrorHandle $ do
(classic, GameStateInputs worlds entities recipes) <- loadStandaloneScenario "data/scenarios/classic.yaml"
(classic, GameStateInputs worlds (TerrainEntityMaps _ entities) recipes) <- loadStandaloneScenario "data/scenarios/classic.yaml"
baseRobot <- instantiateBaseRobot $ classic ^. scenarioLandscape
return . Dot.showDot $ recipesToDot baseRobot (worlds ! "classic") entities recipes

Expand Down
8 changes: 6 additions & 2 deletions app/doc/Swarm/Doc/Wiki/Cheatsheet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ import Swarm.Game.Display (displayChar)
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities)
import Swarm.Game.Entity qualified as E
import Swarm.Game.Recipe (Recipe, loadRecipes, recipeCatalysts, recipeInputs, recipeOutputs, recipeTime, recipeWeight)
import Swarm.Game.Terrain (loadTerrain, terrainByName)
import Swarm.Language.Capability (Capability)
import Swarm.Language.Capability qualified as Capability
import Swarm.Language.Pretty (prettyText, prettyTextLine)
import Swarm.Language.Syntax (Const (..))
import Swarm.Language.Syntax qualified as Syntax
import Swarm.Language.Text.Markdown as Markdown (docToMark)
import Swarm.Language.Typecheck (inferConst)
import Swarm.Util (listEnums)
import Swarm.Util (listEnums, showT)
import Swarm.Util.Effect (simpleErrorHandle)

-- * Types
Expand All @@ -54,7 +55,7 @@ data PageAddress = PageAddress
deriving (Eq, Show)

-- | An enumeration of the kinds of cheat sheets we can produce.
data SheetType = Entities | Commands | CommandMatrix | Capabilities | Recipes | Scenario
data SheetType = Entities | Terrain | Commands | CommandMatrix | Capabilities | Recipes | Scenario
deriving (Eq, Show, Enum, Bounded)

-- * Functions
Expand All @@ -73,6 +74,9 @@ makeWikiPage address s = case s of
Entities -> simpleErrorHandle $ do
entities <- loadEntities
sendIO $ T.putStrLn $ entitiesPage address (Map.elems $ entitiesByName entities)
Terrain -> simpleErrorHandle $ do
terrains <- loadTerrain
sendIO . T.putStrLn . T.unlines . map showT . Map.elems $ terrainByName terrains
Recipes -> simpleErrorHandle $ do
entities <- loadEntities
recipes <- loadRecipes entities
Expand Down
1 change: 1 addition & 0 deletions data/scenarios/Testing/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ Achievements
1634-message-colors.yaml
1681-pushable-entity.yaml
1747-volume-command.yaml
1775-custom-terrain.yaml
58 changes: 58 additions & 0 deletions data/scenarios/Testing/1775-custom-terrain.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
version: 1
name: Demo custom terrain
description: |
Colorful new terrain
creative: false
attrs:
- name: beachsand
bg: "#c2b280"
- name: lava
bg: "#dd7733"
- name: lilac
bg: "#a4a4bb"
terrains:
- name: beach
attr: beachsand
description: |
Shoreline covering, laborious to cross
- name: lava
attr: lava
description: |
Scorching, liquid rock
- name: heather
attr: lilac
description: |
Flowery ground cover
objectives:
- goal:
- |
No entities should be here
condition: |
as base {
isEmpty
}
solution: |
noop
robots:
- name: base
dir: east
known: []
world:
dsl: |
{grass}
palette:
'B': [heather, null, base]
'.': [heather]
'i': [ice]
'b': [beach]
'v': [lava]
upperleft: [0, 0]
map: |
vvvvvvvv
vvvvvvvv
B.......
........
iiiiiiii
iiiiiiii
bbbbbbbb
bbbbbbbb
27 changes: 27 additions & 0 deletions data/scenarios/Testing/_Validation/1775-invalid-terrain-attr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: 1
name: Custom terrain - invalid attribute
description: |
Colorful new terrain
creative: false
attrs:
- name: lava
bg: "#dd7733"
terrains:
- name: lava
attr: baklava
description: |
Scorching, liquid rock
robots:
- name: base
dir: east
known: []
world:
dsl: |
{grass}
palette:
'B': [grass, null, base]
'.': [lava]
upperleft: [0, 0]
map: |
B.
..
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: 1
name: Custom terrain - invalid terrain reference
description: |
Colorful new terrain
creative: false
attrs:
- name: lava
bg: "#dd7733"
terrains:
- name: lava
attr: lava
description: |
Scorching, liquid rock
robots:
- name: base
dir: east
known: []
world:
dsl: |
{grass}
palette:
'B': [grass, null, base]
'.': [liver]
upperleft: [0, 0]
map: |
B.
..
7 changes: 7 additions & 0 deletions data/schema/scenario.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
"default": null,
"type": "number"
},
"terrains": {
"description": "An optional list of custom terrain, to be used in addition to the built-in terrain.",
"default": [],
"items": {
"$ref": "terrain.json"
}
},
"entities": {
"description": "An optional list of custom entities, to be used in addition to the built-in entities.",
"default": [],
Expand Down
27 changes: 27 additions & 0 deletions data/schema/terrain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/terrain.json",
"title": "Terrain",
"description": "Description of a terrain in the Swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The name of the terrain."
},
"description": {
"type": "string",
"description": "A description of the terrain."
},
"attr": {
"type": "string",
"examples": [
"red",
"ice",
"dirt"
],
"description": "The name of the attribute that should be used to style the robot or entity. A list of currently valid attributes can be found [here](https://github.com/swarm-game/swarm/blob/main/src/Swarm/TUI/View/Attribute/Attr.hs)."
}
}
}
10 changes: 10 additions & 0 deletions data/schema/terrains.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/terrains.json",
"title": "Terrains",
"description": "Description of terrains in the Swarm game",
"type": "array",
"items": {
"$ref": "terrain.json"
}
}
16 changes: 16 additions & 0 deletions data/terrains.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- name: stone
attr: stone
description: |
Solid, impenetrable material
- name: dirt
attr: dirt
description: |
Soil amenable to plant growth
- name: grass
attr: grass
description: |
Soft, verdant ground
- name: ice
attr: ice
description: |
Cold, solid, and slippery.
2 changes: 1 addition & 1 deletion scripts/enforce-todo-issues.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd $SCRIPT_DIR/..


if grep --line-number --include \*.hs -riP '(TODO|FIXME|XXX)\b' src 2>&1 | grep -vP '#\d+'; then
if grep --line-number --include \*.hs -riP '(TODO|FIXME|XXX)\b' src app 2>&1 | grep -vP '#\d+'; then
echo "Please add a link to Issue, for example: TODO: #123"
exit 1
else
Expand Down
1 change: 1 addition & 0 deletions scripts/validate-json-schemas.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ cd $SCRIPT_DIR/..

find data/scenarios -name "*.yaml" -type f -print0 | xargs -0 check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/scenario.json --schemafile data/schema/scenario.json

check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/terrains.json --schemafile data/schema/terrains.json data/terrains.yaml
check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/entities.json --schemafile data/schema/entities.json data/entities.yaml
check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/recipes.json --schemafile data/schema/recipes.json data/recipes.yaml
9 changes: 5 additions & 4 deletions src/Swarm/Doc/Pedagogy.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import Data.Set qualified as S
import Data.Text (Text)
import Data.Text qualified as T
import Swarm.Constant
import Swarm.Game.Entity (loadEntities)
import Swarm.Game.Failure (SystemFailure)
import Swarm.Game.Land
import Swarm.Game.Scenario (
Scenario,
scenarioDescription,
Expand Down Expand Up @@ -174,13 +174,14 @@ generateIntroductionsSequence =
-- For unit tests, can instead access the scenarios via the GameState.
loadScenarioCollection :: IO ScenarioCollection
loadScenarioCollection = simpleErrorHandle $ do
entities <- loadEntities
tem <- loadEntitiesAndTerrain

-- Note we ignore any warnings generated by 'loadWorlds' and
-- 'loadScenarios' below. Any warnings will be caught when loading
-- all the scenarios via the usual code path; we do not need to do
-- anything with them here while simply rendering pedagogy info.
worlds <- ignoreWarnings @(Seq SystemFailure) $ loadWorlds entities
ignoreWarnings @(Seq SystemFailure) $ loadScenarios entities worlds
worlds <- ignoreWarnings @(Seq SystemFailure) $ loadWorlds tem
ignoreWarnings @(Seq SystemFailure) $ loadScenarios tem worlds

renderUsagesMarkdown :: CoverageInfo -> Text
renderUsagesMarkdown (CoverageInfo (TutorialInfo (s, si) idx _sCmds dCmds) novelCmds) =
Expand Down
3 changes: 2 additions & 1 deletion src/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import Swarm.Game.Achievement.Definitions
import Swarm.Game.Achievement.Persistence
import Swarm.Game.CESK (CESK (Out), Frame (FApp, FExec), cancel, emptyStore, initMachine)
import Swarm.Game.Entity hiding (empty)
import Swarm.Game.Land
import Swarm.Game.Location
import Swarm.Game.ResourceLoading (getSwarmHistoryPath)
import Swarm.Game.Robot
Expand Down Expand Up @@ -1191,7 +1192,7 @@ handleREPLEventTyping = \case
CharKey '\t' -> do
s <- get
let names = s ^.. gameState . baseRobot . robotContext . defTypes . to assocs . traverse . _1
uiState . uiGameplay . uiREPL %= tabComplete (CompletionContext (s ^. gameState . creativeMode)) names (s ^. gameState . landscape . entityMap)
uiState . uiGameplay . uiREPL %= tabComplete (CompletionContext (s ^. gameState . creativeMode)) names (s ^. gameState . landscape . terrainAndEntities . entityMap)
modify validateREPLForm
EscapeKey -> do
formSt <- use $ uiState . uiGameplay . uiREPL . replPromptType
Expand Down
6 changes: 5 additions & 1 deletion src/Swarm/TUI/Editor/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
import Data.Map qualified as M
import Data.Yaml qualified as Y
import Graphics.Vty qualified as V
import Swarm.Game.Land
import Swarm.Game.Scenario.Topography.EntityFacade
import Swarm.Game.State
import Swarm.Game.State.Landscape
Expand Down Expand Up @@ -83,9 +84,11 @@ handleMiddleClick mouseLoc = do
worldEditor <- use $ uiState . uiGameplay . uiWorldEditor
when (worldEditor ^. worldOverdraw . isWorldEditorEnabled) $ do
w <- use $ gameState . landscape . multiWorld
tm <- use $ gameState . landscape . terrainAndEntities . terrainMap
let setTerrainPaint coords = do
let (terrain, maybeElementPaint) =
EU.getEditorContentAt
tm
(worldEditor ^. worldOverdraw)
w
coords
Expand Down Expand Up @@ -142,7 +145,8 @@ saveMapFile = do
worldEditor <- use $ uiState . uiGameplay . uiWorldEditor
maybeBounds <- use $ uiState . uiGameplay . uiWorldEditor . editingBounds . boundsRect
w <- use $ gameState . landscape . multiWorld
let mapCellGrid = EU.getEditedMapRectangle (worldEditor ^. worldOverdraw) maybeBounds w
tm <- use $ gameState . landscape . terrainAndEntities . terrainMap
let mapCellGrid = EU.getEditedMapRectangle tm (worldEditor ^. worldOverdraw) maybeBounds w

let fp = worldEditor ^. outputFilePath
maybeScenarioPair <- use $ uiState . uiGameplay . scenarioRef
Expand Down
2 changes: 1 addition & 1 deletion src/Swarm/TUI/Editor/Model.hs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ initialWorldEditor :: TimeSpec -> WorldEditor Name
initialWorldEditor ts =
WorldEditor
(WorldOverdraw False mempty)
(BL.list TerrainList (V.fromList listEnums) 1)
(BL.list TerrainList (V.fromList []) 1)
(BL.list EntityPaintList (V.fromList []) 1)
bounds
(focusRing $ map WorldEditorPanelControl listEnums)
Expand Down
Loading

0 comments on commit 936b30d

Please sign in to comment.