Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subworlds #1353

Merged
merged 25 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
enforce spatial consistency
  • Loading branch information
kostmo committed Jul 20, 2023
commit 74a331da67bed9222cc3daea9aef970a405edae5
1 change: 1 addition & 0 deletions data/scenarios/Testing/144-subworlds/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ basic-subworld.yaml
subworld-shared-structures.yaml
subworld-mapped-robots.yaml
subworld-located-robots.yaml
spatial-consistency-enforcement.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
version: 1
name: Subworld spatial consistency enforcement
description: |
Portals annotated to enforce spatial consistency between subworlds
attrs:
- name: portal_in
fg: "#ff9a00"
bg: "#ff5d00"
- name: portal_out
fg: "#00a2ff"
bg: "#0065ff"
entities:
- name: telepad entrance
display:
attr: portal_in
char: "o"
description:
- Portal entrance
properties: [known]
- name: telepad exit
display:
attr: portal_out
char: "o"
description:
- Portal exit
properties: [known]
robots:
- name: base
dir: [1, 0]
devices:
- ADT calculator
- branch predictor
- comparator
- compass
- dictionary
- GPS receiver
- grabber
- lambda
- lodestone
- logger
- strange loop
- treads
known: [boulder]
subworlds:
- name: underground
default: [blank]
palette:
'.': [dirt]
'b': [dirt, boulder]
'p':
cell: [dirt, telepad exit]
waypoint:
name: portal_out2
'P':
cell: [dirt, telepad entrance]
waypoint:
name: portal_in2
portals:
- entrance: portal_in2
exitInfo:
exit: portal_out1
subworldName: root
consistent: true
upperleft: [-1, 1]
map: |
b..b..b..b
.p......P.
b..b..b..b
world:
name: root
default: [blank]
palette:
'.': [grass]
'B': [grass, null, base]
'p':
cell: [grass, telepad exit]
waypoint:
name: portal_out1
'P':
cell: [grass, telepad entrance]
waypoint:
name: portal_in1
upperleft: [-1, 1]
portals:
- entrance: portal_in1
exitInfo:
exit: portal_out2
subworldName: underground
consistent: true
map: |
..........
.p.B....P.
..........
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
version: 1
name: Subworld spatial consistency enforcement
description: |
Portals annotated to enforce spatial consistency between subworlds
attrs:
- name: portal_in
fg: "#ff9a00"
bg: "#ff5d00"
- name: portal_out
fg: "#00a2ff"
bg: "#0065ff"
entities:
- name: telepad entrance
display:
attr: portal_in
char: "o"
description:
- Portal entrance
properties: [known]
- name: telepad exit
display:
attr: portal_out
char: "o"
description:
- Portal exit
properties: [known]
robots:
- name: base
dir: [1, 0]
devices:
- ADT calculator
- branch predictor
- comparator
- compass
- dictionary
- GPS receiver
- grabber
- lambda
- lodestone
- logger
- strange loop
- treads
known: [boulder]
subworlds:
- name: underground
default: [blank]
palette:
'.': [dirt]
'b': [dirt, boulder]
'p':
cell: [dirt, telepad exit]
waypoint:
name: portal_out2
'P':
cell: [dirt, telepad entrance]
waypoint:
name: portal_in2
portals:
- entrance: portal_in2
exitInfo:
exit: portal_out1
subworldName: root
consistent: true
upperleft: [-1, 1]
map: |
b..b..b..b
.p.....P..
b..b..b..b
world:
name: root
default: [blank]
palette:
'.': [grass]
'B': [grass, null, base]
'p':
cell: [grass, telepad exit]
waypoint:
name: portal_out1
'P':
cell: [grass, telepad entrance]
waypoint:
name: portal_in1
upperleft: [-1, 1]
portals:
- entrance: portal_in1
exitInfo:
exit: portal_out2
subworldName: underground
consistent: true
map: |
..........
.p.B....P.
..........
82 changes: 70 additions & 12 deletions src/Swarm/Game/Scenario/Topography/Navigation/Portal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@
-- SPDX-License-Identifier: BSD-3-Clause
module Swarm.Game.Scenario.Topography.Navigation.Portal where

import Control.Arrow ((&&&))
import Control.Lens (view)
import Control.Monad (forM, forM_, unless)
import Data.Aeson (FromJSON)
import Data.Aeson
import Data.Bifunctor (first)
import Data.BoolExpr (Signed (..))
import Data.Coerce
import Data.Function (on)
import Data.Functor.Identity
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as M
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Text qualified as T
import Data.Tuple (swap)
import GHC.Generics (Generic)
import Linear (negated)
import Swarm.Game.Location
import Swarm.Game.Scenario.Topography.Navigation.Waypoint
import Swarm.Game.Universe
import Swarm.Util (binTuples, quote)
import Swarm.Util (allEqual, binTuples, both, quote)

type WaypointMap = M.Map WaypointName (NonEmpty Location)

data AnnotatedDestination a = AnnotatedDestination
{ enforceConsistency :: Bool
, cosmoLocation :: Cosmo a
}
deriving (Show, Eq)

-- | Parameterized on the portal specification method.
-- At the subworld parsing level, we only can obtain the planar location
-- for portal /entrances/. At the Scenario-parsing level, we finally have
Expand All @@ -34,7 +46,7 @@ data Navigation a b = Navigation
-- ^ Note that waypoints defined at the "root" level are still relative to
-- the top-left corner of the map rectangle; they are not in absolute world
-- coordinates (as with applying the "ul" offset).
, portals :: M.Map (Cosmo Location) (Cosmo b)
, portals :: M.Map (Cosmo Location) (AnnotatedDestination b)
}

deriving instance (Eq (a WaypointMap), Eq b) => Eq (Navigation a b)
Expand All @@ -50,9 +62,18 @@ data PortalExit = PortalExit
data Portal = Portal
{ entrance :: WaypointName
, exitInfo :: PortalExit
, consistent :: Maybe Bool
, consistent :: Bool
}
deriving (Show, Eq, Generic, FromJSON)
deriving (Show, Eq)

instance FromJSON Portal where
parseJSON = withObject "Portal" $ \v ->
Portal
<$> v
.: "entrance"
<*> v
.: "exitInfo"
<*> v .:? "consistent" .!= False

failUponDuplication ::
(MonadFail m, Show a, Show b) =>
Expand Down Expand Up @@ -114,15 +135,15 @@ validatePartialNavigation ::
validatePartialNavigation currentSubworldName upperLeft unmergedWaypoints portalDefs = do
failUponDuplication "is required to be unique, but is duplicated in:" waypointsWithUniqueFlag

nestedPortalPairs <- forM portalDefs $ \(Portal entranceName (PortalExit exitName maybeExitSubworldName) _) -> do
nestedPortalPairs <- forM portalDefs $ \(Portal entranceName (PortalExit exitName maybeExitSubworldName) isConsistent) -> do
-- Portals can have multiple entrances but only a single exit.
-- That is, the pairings of entries to exits must form a proper mathematical "function".
-- Multiple occurrences of entrance waypoints of a given name will result in
-- multiple portal entrances.
entranceLocs <- getLocs entranceName

let sw = fromMaybe currentSubworldName maybeExitSubworldName
f = (,Cosmo sw exitName) . extractLoc
f = (,AnnotatedDestination isConsistent $ Cosmo sw exitName) . extractLoc
return $ map f $ NE.toList entranceLocs

let reconciledPortalPairs = concat nestedPortalPairs
Expand All @@ -149,9 +170,9 @@ validatePartialNavigation currentSubworldName upperLeft unmergedWaypoints portal
validatePortals ::
MonadFail m =>
Navigation (M.Map SubworldName) WaypointName ->
m (M.Map (Cosmo Location) (Cosmo Location))
m (M.Map (Cosmo Location) (AnnotatedDestination Location))
validatePortals (Navigation wpUniverse partialPortals) = do
portalPairs <- forM (M.toList partialPortals) $ \(portalEntrance, portalExit@(Cosmo swName (WaypointName rawExitName))) -> do
portalPairs <- forM (M.toList partialPortals) $ \(portalEntrance, AnnotatedDestination isConsistent portalExit@(Cosmo swName (WaypointName rawExitName))) -> do
firstExitLoc :| otherExits <- getLocs portalExit
unless (null otherExits)
. fail
Expand All @@ -161,7 +182,9 @@ validatePortals (Navigation wpUniverse partialPortals) = do
, quote rawExitName
, "for portal"
]
return (portalEntrance, Cosmo swName firstExitLoc)
return (portalEntrance, AnnotatedDestination isConsistent $ Cosmo swName firstExitLoc)

ensureSpatialConsistency portalPairs

return $ M.fromList portalPairs
where
Expand Down Expand Up @@ -198,6 +221,41 @@ validatePortals (Navigation wpUniverse partialPortals) = do
-- * The resulting \"vector\" from every pair must be equal.
ensureSpatialConsistency ::
MonadFail m =>
-- Navigation (M.Map SubworldName) WaypointName ->
[(Cosmo Location, AnnotatedDestination Location)] ->
m ()
ensureSpatialConsistency = return () -- TODO
ensureSpatialConsistency xs =
kostmo marked this conversation as resolved.
Show resolved Hide resolved
unless (null nonUniform) $
fail $
unwords
[ "Non-uniform portal distances:"
, show nonUniform
]
where
consistentPairs :: [(Cosmo Location, Cosmo Location)]
consistentPairs = map (fmap cosmoLocation) $ filter (enforceConsistency . snd) xs

interWorldPairs = filter (uncurry ((/=) `on` view subworld)) consistentPairs
kostmo marked this conversation as resolved.
Show resolved Hide resolved
normalizedOrdering = map normalizePairOrder interWorldPairs

normalizePairOrder pair =
if uncurry ((>) `on` view subworld) pair
then Negative $ swap pair
else Positive pair

tuplify = both (view subworld) &&& both (view planar)

nest ::
Signed (b, a) ->
(b, Signed a)
nest = \case
Positive x -> fmap Positive x
Negative x -> fmap Negative x
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought sequenceA would work here instead, but the compiler doesn't like it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved this question in b1a771e.


reExtract = \case
kostmo marked this conversation as resolved.
Show resolved Hide resolved
Positive x -> x
Negative x -> negated x

groupedBySubworldPair = binTuples $ map (nest . fmap tuplify) normalizedOrdering
vectorized = M.map (NE.map (reExtract . fmap (uncurry (.-.)))) groupedBySubworldPair

nonUniform = M.filter ((not . allEqual) . NE.toList) vectorized
4 changes: 2 additions & 2 deletions src/Swarm/Game/Step.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import Swarm.Game.ResourceLoading (getDataFileNameSafe)
import Swarm.Game.Robot
import Swarm.Game.Scenario.Objective qualified as OB
import Swarm.Game.Scenario.Objective.WinCheck qualified as WC
import Swarm.Game.Scenario.Topography.Navigation.Portal (Navigation (..))
import Swarm.Game.Scenario.Topography.Navigation.Portal (Navigation (..), cosmoLocation)
import Swarm.Game.Scenario.Topography.Navigation.Waypoint (WaypointName (..))
import Swarm.Game.State
import Swarm.Game.Universe
Expand Down Expand Up @@ -2753,7 +2753,7 @@ updateRobotLocation oldLoc newLoc
where
applyPortal loc = do
lms <- use worldNavigation
return $ M.findWithDefault loc loc $ portals lms
return . M.findWithDefault loc loc . M.map cosmoLocation $ portals lms

-- | Execute a stateful action on a target robot --- whether the
-- current one or another.
Expand Down
4 changes: 4 additions & 0 deletions src/Swarm/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module Swarm.Util (
histogram,
findDup,
both,
allEqual,

-- * Directory utilities
readFileMay,
Expand Down Expand Up @@ -189,6 +190,9 @@ findDup = go S.empty
both :: Bifunctor p => (a -> d) -> p a a -> p d d
both f = bimap f f

allEqual :: (Ord a) => [a] -> Bool
allEqual = (== 1) . S.size . S.fromList
kostmo marked this conversation as resolved.
Show resolved Hide resolved

------------------------------------------------------------
-- Directory stuff

Expand Down