validate subworld names of explicitly located robots (#1875)

## Demo
```
scripts/test/run-tests.sh --test-options '--pattern "1875-robot-subworld-location"'
```
and
```
scripts/play.sh -i data/scenarios/Testing/144-subworlds/subworld-shared-structures.yaml --autoplay
```
This commit is contained in:
Karl Ostmo 2024-05-29 09:43:21 -07:00 committed by GitHub
parent eb20ea9b07
commit 834f549759
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 75 additions and 14 deletions

View File

@ -1,7 +1,7 @@
version: 1
name: Subworld shared structures
description: |
Traverse floors of the tower
Traverse the floors of the tower, then grab the flower in the middle
attrs:
- name: portal_in
fg: "#ff9a00"
@ -9,6 +9,12 @@ attrs:
- name: portal_out
fg: "#00a2ff"
bg: "#0065ff"
objectives:
- goal:
- |
Visit all of the floors
condition: |
as base {has "flower"}
entities:
- name: telepad entrance
display:
@ -27,7 +33,9 @@ entities:
robots:
- name: base
dir: east
loc: [0, 0]
loc:
subworld: root
loc: [0, 0]
devices:
- ADT calculator
- branch predictor
@ -41,6 +49,20 @@ robots:
- logger
- strange loop
- treads
solution: |
def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;
doN 4 (
doN 4 move;
turn right;
doN 4 move;
);
doN 2 move;
turn right;
doN 2 move;
grab;
known: [flower]
structures:
- name: minibox
@ -87,17 +109,18 @@ subworlds:
waypoint:
name: portal_in
placements:
- src: flowers
offset: [1, 1]
- src: minibox
offset: [0, 0]
orient:
up: west
- src: flowers
offset: [1, 1]
portals:
- entrance: portal_in
exitInfo:
exit: portal_out
subworldName: floor2
reorient: back
upperleft: [0, 0]
map: |
.....
@ -119,17 +142,18 @@ subworlds:
waypoint:
name: portal_in
placements:
- src: flowers
offset: [1, 0]
- src: minibox
offset: [0, 0]
orient:
up: south
- src: flowers
offset: [1, 0]
portals:
- entrance: portal_in
exitInfo:
exit: portal_out
subworldName: floor3
reorient: back
upperleft: [0, 0]
map: |
.....
@ -151,17 +175,18 @@ subworlds:
waypoint:
name: portal_in
placements:
- src: flowers
offset: [1, -2]
- src: minibox
offset: [0, 0]
orient:
up: east
- src: flowers
offset: [1, -2]
portals:
- entrance: portal_in
exitInfo:
exit: portal_out
subworldName: root
reorient: back
upperleft: [0, 0]
map: |
.....
@ -171,20 +196,21 @@ subworlds:
.....
world:
name: root
default: [blank]
palette:
'.': [grass]
'B': [grass, null, base]
upperleft: [0, 0]
placements:
- src: flowers
offset: [0, -2]
- src: minibox
offset: [0, 0]
- src: flowers
offset: [0, -2]
portals:
- entrance: portal_in
exitInfo:
exit: portal_out
subworldName: floor1
reorient: back
map: |
.....
.....

View File

@ -0,0 +1,16 @@
version: 1
name: Robot location in nonexistent named subworld
description: |
Robot specifies an explicit "planar" location, which uses
the "default" subworld name. However, since the root subworld
has been given a name, we object that the subworld specified by
the robot doesn't exist.
robots:
- name: base
loc: [0, 0]
world:
name: mySubworld
palette:
'.': [grass]
map: |-
.

View File

@ -69,7 +69,7 @@ import Control.Carrier.Throw.Either (runThrow)
import Control.Effect.Lift (Lift, sendIO)
import Control.Effect.Throw
import Control.Lens hiding (from, (.=), (<.>))
import Control.Monad (filterM, unless, (<=<))
import Control.Monad (filterM, forM_, unless, (<=<))
import Data.Aeson
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NE
@ -89,7 +89,7 @@ import Swarm.Game.Land
import Swarm.Game.Location
import Swarm.Game.Recipe
import Swarm.Game.ResourceLoading (getDataFileNameSafe)
import Swarm.Game.Robot (TRobot)
import Swarm.Game.Robot (TRobot, trobotLocation, trobotName)
import Swarm.Game.Scenario.Objective
import Swarm.Game.Scenario.Objective.Validation
import Swarm.Game.Scenario.RobotLookup
@ -112,7 +112,7 @@ import Swarm.Language.Pipeline (ProcessedTerm)
import Swarm.Language.Pretty (prettyText)
import Swarm.Language.Syntax (Syntax)
import Swarm.Language.Text.Markdown (Document)
import Swarm.Util (binTuples, failT)
import Swarm.Util (binTuples, commaList, failT, quote)
import Swarm.Util.Effect (ignoreWarnings, throwToMaybe, withThrow)
import Swarm.Util.Lens (makeLensesNoSigs)
import Swarm.Util.Yaml
@ -352,6 +352,18 @@ instance FromJSONE ScenarioInputs Scenario where
, T.intercalate ", " $ map renderWorldName dupedNames
]
-- Validate robot locations
forM_ rs $ \r -> forM_ (r ^. trobotLocation) $ \rLoc ->
unless ((rLoc ^. subworld) `M.member` worldsByName)
. failT
$ [ "Robot"
, quote $ r ^. trobotName
, "specifies location in nonexistent subworld"
, renderQuotedWorldName (rLoc ^. subworld) <> "."
, "Valid subworlds are:"
, commaList $ map renderQuotedWorldName $ M.keys worldsByName
]
let mergedWaypoints =
M.fromList $
map (worldName &&& runIdentity . waypoints . navigation) $

View File

@ -16,6 +16,7 @@ import Data.Yaml (FromJSON, ToJSON, Value (Object), parseJSON, withText, (.:))
import GHC.Generics (Generic)
import Linear (V2 (..))
import Swarm.Game.Location
import Swarm.Util (quote)
-- * Referring to subworlds
@ -30,6 +31,11 @@ renderWorldName = \case
SubworldName s -> s
DefaultRootSubworld -> "<default>"
renderQuotedWorldName :: SubworldName -> Text
renderQuotedWorldName = \case
SubworldName s -> quote s
DefaultRootSubworld -> "<default>"
-- * Universal location
-- | The swarm universe consists of locations

View File

@ -353,6 +353,7 @@ testScenarioSolutions rs ui =
, testSolution Default "Testing/144-subworlds/basic-subworld"
, testSolution Default "Testing/144-subworlds/subworld-mapped-robots"
, testSolution Default "Testing/144-subworlds/subworld-located-robots"
, testSolution Default "Testing/144-subworlds/subworld-shared-structures"
, testSolution Default "Testing/1355-combustion"
, testSolution Default "Testing/1379-single-world-portal-reorientation"
, testSolution Default "Testing/1322-wait-with-instant"