packages feed

swarm 0.2.0.0 → 0.3.0.0

raw patch · 183 files changed

+11542/−3008 lines, 183 filesdep +text-ropedep ~brickdep ~lineardep ~lspPVP ok

version bump matches the API change (PVP)

Dependencies added: text-rope

Dependency ranges changed: brick, linear, lsp, megaparsec

API changes (from Hackage documentation)

- Swarm.Game.CESK: RobotUpdate :: (Inventory -> Inventory) -> RobotUpdate
- Swarm.Game.CESK: WorldUpdate :: (World Int Entity -> Either Exn (World Int Entity)) -> WorldUpdate
- Swarm.Game.CESK: [robotUpdateInventory] :: RobotUpdate -> Inventory -> Inventory
- Swarm.Game.CESK: [worldUpdate] :: WorldUpdate -> World Int Entity -> Either Exn (World Int Entity)
- Swarm.Game.CESK: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.CESK.WorldUpdate
- Swarm.Game.CESK: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.CESK.WorldUpdate
- Swarm.Game.CESK: instance GHC.Classes.Eq Swarm.Game.CESK.WorldUpdate
- Swarm.Game.CESK: instance GHC.Show.Show Swarm.Game.CESK.WorldUpdate
- Swarm.Game.CESK: newtype RobotUpdate
- Swarm.Game.CESK: newtype WorldUpdate
- Swarm.Game.CESK: prettyCESK :: CESK -> String
- Swarm.Game.CESK: prettyCont :: Cont -> String
- Swarm.Game.CESK: prettyFrame :: Frame -> String
- Swarm.Game.CESK: type Loc = Int
- Swarm.Game.Exception: FixByInstall :: IncapableFix
- Swarm.Game.Robot: ErrorTrace :: LogSource
- Swarm.Game.Robot: LogEntry :: Integer -> LogSource -> Text -> Int -> V2 Int64 -> Text -> LogEntry
- Swarm.Game.Robot: Logged :: LogSource
- Swarm.Game.Robot: Said :: LogSource
- Swarm.Game.Robot: [_leLocation] :: LogEntry -> V2 Int64
- Swarm.Game.Robot: [_leRobotID] :: LogEntry -> Int
- Swarm.Game.Robot: [_leRobotName] :: LogEntry -> Text
- Swarm.Game.Robot: [_leSaid] :: LogEntry -> LogSource
- Swarm.Game.Robot: [_leText] :: LogEntry -> Text
- Swarm.Game.Robot: [_leTime] :: LogEntry -> Integer
- Swarm.Game.Robot: data LogEntry
- Swarm.Game.Robot: data LogSource
- Swarm.Game.Robot: installedDevices :: Lens' Robot Inventory
- Swarm.Game.Robot: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: instance GHC.Classes.Eq Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance GHC.Classes.Eq Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: instance GHC.Classes.Ord Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance GHC.Classes.Ord Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: instance GHC.Generics.Generic Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance GHC.Generics.Generic Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: instance GHC.Show.Show Swarm.Game.Robot.LogEntry
- Swarm.Game.Robot: instance GHC.Show.Show Swarm.Game.Robot.LogSource
- Swarm.Game.Robot: leLocation :: Lens' LogEntry (V2 Int64)
- Swarm.Game.Robot: leRobotID :: Lens' LogEntry Int
- Swarm.Game.Robot: leRobotName :: Lens' LogEntry Text
- Swarm.Game.Robot: leSaid :: Lens' LogEntry LogSource
- Swarm.Game.Robot: leText :: Lens' LogEntry Text
- Swarm.Game.Robot: leTime :: Lens' LogEntry Integer
- Swarm.Game.Scenario: data Cell
- Swarm.Game.Scenario: data Objective
- Swarm.Game.Scenario: data WorldDescription
- Swarm.Game.Scenario: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Objective
- Swarm.Game.Scenario: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective
- Swarm.Game.Scenario: instance GHC.Classes.Eq Swarm.Game.Scenario.Cell
- Swarm.Game.Scenario: instance GHC.Classes.Eq Swarm.Game.Scenario.Objective
- Swarm.Game.Scenario: instance GHC.Classes.Eq Swarm.Game.Scenario.WorldDescription
- Swarm.Game.Scenario: instance GHC.Classes.Eq Swarm.Game.Scenario.WorldPalette
- Swarm.Game.Scenario: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective
- Swarm.Game.Scenario: instance GHC.Show.Show Swarm.Game.Scenario.Cell
- Swarm.Game.Scenario: instance GHC.Show.Show Swarm.Game.Scenario.Objective
- Swarm.Game.Scenario: instance GHC.Show.Show Swarm.Game.Scenario.WorldDescription
- Swarm.Game.Scenario: instance GHC.Show.Show Swarm.Game.Scenario.WorldPalette
- Swarm.Game.Scenario: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotMap) Swarm.Game.Scenario.Cell
- Swarm.Game.Scenario: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotMap) Swarm.Game.Scenario.WorldDescription
- Swarm.Game.Scenario: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotMap) Swarm.Game.Scenario.WorldPalette
- Swarm.Game.Scenario: objectiveCondition :: Lens' Objective ProcessedTerm
- Swarm.Game.Scenario: objectiveGoal :: Lens' Objective [Text]
- Swarm.Game.State: _Won :: Prism' WinCondition Bool
- Swarm.Game.Value: [VBind] :: Maybe Var -> Term -> Term -> Env -> Value
- Swarm.Game.Value: [VBool] :: Bool -> Value
- Swarm.Game.Value: [VCApp] :: Const -> [Value] -> Value
- Swarm.Game.Value: [VClo] :: Var -> Term -> Env -> Value
- Swarm.Game.Value: [VDef] :: Bool -> Var -> Term -> Env -> Value
- Swarm.Game.Value: [VDelay] :: Term -> Env -> Value
- Swarm.Game.Value: [VDir] :: Direction -> Value
- Swarm.Game.Value: [VInj] :: Bool -> Value -> Value
- Swarm.Game.Value: [VInt] :: Integer -> Value
- Swarm.Game.Value: [VPair] :: Value -> Value -> Value
- Swarm.Game.Value: [VRef] :: Int -> Value
- Swarm.Game.Value: [VResult] :: Value -> Env -> Value
- Swarm.Game.Value: [VRobot] :: Int -> Value
- Swarm.Game.Value: [VText] :: Text -> Value
- Swarm.Game.Value: [VUnit] :: Value
- Swarm.Game.Value: data Value
- Swarm.Game.Value: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Value.Value
- Swarm.Game.Value: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Value.Value
- Swarm.Game.Value: instance GHC.Classes.Eq Swarm.Game.Value.Value
- Swarm.Game.Value: instance GHC.Generics.Generic Swarm.Game.Value.Value
- Swarm.Game.Value: instance GHC.Show.Show Swarm.Game.Value.Value
- Swarm.Game.Value: prettyValue :: Value -> Text
- Swarm.Game.Value: type Env = Ctx Value
- Swarm.Game.Value: valueToTerm :: Value -> Term
- Swarm.Language.Capability: CInstall :: Capability
- Swarm.Language.Syntax: Install :: Const
- Swarm.Language.Syntax: Installed :: Const
- Swarm.Language.Syntax: Location :: Int -> Int -> Location
- Swarm.Language.Syntax: Syntax :: Location -> Term -> Syntax
- Swarm.Language.Syntax: [dirAbs] :: DirInfo -> Maybe (V2 Int64)
- Swarm.Language.Syntax: [sLoc] :: Syntax -> Location
- Swarm.Language.Syntax: [sTerm] :: Syntax -> Term
- Swarm.Language.Syntax: data Location
- Swarm.Language.Syntax: data Syntax
- Swarm.Language.Syntax: data Term
- Swarm.Language.Syntax: fv :: Traversal' Term Var
- Swarm.Language.Syntax: fvT :: Traversal' Term Term
- Swarm.Language.Syntax: instance Control.Lens.Plated.Plated Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSONKey Swarm.Language.Syntax.Direction
- Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSONKey Swarm.Language.Syntax.Direction
- Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance GHC.Base.Monoid Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance GHC.Base.Semigroup Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance GHC.Enum.Bounded Swarm.Language.Syntax.Direction
- Swarm.Language.Syntax: instance GHC.Enum.Enum Swarm.Language.Syntax.Direction
- Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.Location
- Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.Syntax
- Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.Term
- Swarm.Language.Syntax: mapFree1 :: Var -> (Term -> Term) -> Term -> Term
- Swarm.Language.Typecheck: getTypeErrLocation :: TypeErr -> Maybe Location
- Swarm.Language.Typecheck: instance Swarm.Language.Typecheck.HasBindings Swarm.Language.Types.UModule
- Swarm.Language.Types: BRobot :: BaseTy
- Swarm.Language.Types: Module :: s -> Ctx t -> Module s t
- Swarm.Language.Types: [moduleCtx] :: Module s t -> Ctx t
- Swarm.Language.Types: [moduleTy] :: Module s t -> s
- Swarm.Language.Types: data Module s t
- Swarm.Language.Types: instance (Data.Aeson.Types.FromJSON.FromJSON s, Data.Aeson.Types.FromJSON.FromJSON t) => Data.Aeson.Types.FromJSON.FromJSON (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: instance (Data.Aeson.Types.ToJSON.ToJSON t, Data.Aeson.Types.ToJSON.ToJSON s) => Data.Aeson.Types.ToJSON.ToJSON (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: instance (Data.Data.Data s, Data.Data.Data t) => Data.Data.Data (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: instance (GHC.Classes.Eq s, GHC.Classes.Eq t) => GHC.Classes.Eq (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: instance (GHC.Show.Show s, GHC.Show.Show t) => GHC.Show.Show (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: instance GHC.Base.Functor (Swarm.Language.Types.Module s)
- Swarm.Language.Types: instance GHC.Generics.Generic (Swarm.Language.Types.Module s t)
- Swarm.Language.Types: pattern TyRobot :: Type
- Swarm.Language.Types: pattern UTyRobot :: UType
- Swarm.Language.Types: trivMod :: s -> Module s t
- Swarm.Language.Types: type TModule = Module Polytype Polytype
- Swarm.Language.Types: type UModule = Module UType UPolytype
- Swarm.TUI.Model: InstalledEntry :: Entity -> InventoryListEntry
- Swarm.TUI.Model: _InstalledEntry :: Prism' InventoryListEntry Entity
- Swarm.TUI.Model: accumulatedTime :: Lens' UIState TimeSpec
- Swarm.TUI.Model: appData :: Lens' UIState (Map Text Text)
- Swarm.TUI.Model: data ButtonSelection
- Swarm.TUI.Model: data UIState
- Swarm.TUI.Model: defaultPrompt :: REPLPrompt
- Swarm.TUI.Model: frameCount :: Lens' UIState Int
- Swarm.TUI.Model: frameTickCount :: Lens' UIState Int
- Swarm.TUI.Model: initAppState :: AppOpts -> ExceptT Text IO AppState
- Swarm.TUI.Model: initFocusRing :: FocusRing Name
- Swarm.TUI.Model: initLgTicksPerSecond :: Int
- Swarm.TUI.Model: initREPLState :: REPLHistory -> REPLState
- Swarm.TUI.Model: initUIState :: Bool -> Bool -> ExceptT Text IO UIState
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.InventoryListEntry
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.ModalType
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.Name
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.REPLHistItem
- Swarm.TUI.Model: instance GHC.Classes.Eq Swarm.TUI.Model.TimeDir
- Swarm.TUI.Model: instance GHC.Classes.Ord Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Classes.Ord Swarm.TUI.Model.Name
- Swarm.TUI.Model: instance GHC.Classes.Ord Swarm.TUI.Model.REPLHistItem
- Swarm.TUI.Model: instance GHC.Classes.Ord Swarm.TUI.Model.TimeDir
- Swarm.TUI.Model: instance GHC.Enum.Bounded Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Enum.Enum Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Read.Read Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Read.Read Swarm.TUI.Model.Name
- Swarm.TUI.Model: instance GHC.Read.Read Swarm.TUI.Model.REPLHistItem
- Swarm.TUI.Model: instance GHC.Show.Show Swarm.TUI.Model.MainMenuEntry
- Swarm.TUI.Model: instance GHC.Show.Show Swarm.TUI.Model.ModalType
- Swarm.TUI.Model: instance GHC.Show.Show Swarm.TUI.Model.Name
- Swarm.TUI.Model: instance GHC.Show.Show Swarm.TUI.Model.REPLHistItem
- Swarm.TUI.Model: instance GHC.Show.Show Swarm.TUI.Model.TimeDir
- Swarm.TUI.Model: lastEntry :: Text -> REPLHistory -> Maybe Text
- Swarm.TUI.Model: lastFrameTime :: Lens' UIState TimeSpec
- Swarm.TUI.Model: lastInfoTime :: Lens' UIState TimeSpec
- Swarm.TUI.Model: lgTicksPerSecond :: Lens' UIState Int
- Swarm.TUI.Model: restartGame :: (MonadIO m, MonadState AppState m) => Seed -> ScenarioInfoPair -> m ()
- Swarm.TUI.Model: scenarioRef :: Lens' UIState (Maybe ScenarioInfoPair)
- Swarm.TUI.Model: scenarioToAppState :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe Seed -> Maybe CodeToRun -> m ()
- Swarm.TUI.Model: startGame :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe CodeToRun -> m ()
- Swarm.TUI.Model: tickCount :: Lens' UIState Int
- Swarm.TUI.Model: uiCheatMode :: Lens' UIState Bool
- Swarm.TUI.Model: uiError :: Lens' UIState (Maybe Text)
- Swarm.TUI.Model: uiFPS :: Lens' UIState Double
- Swarm.TUI.Model: uiFocusRing :: Lens' UIState (FocusRing Name)
- Swarm.TUI.Model: uiGoal :: Lens' UIState (Maybe [Text])
- Swarm.TUI.Model: uiHideRobotsUntil :: Lens' UIState TimeSpec
- Swarm.TUI.Model: uiInventory :: Lens' UIState (Maybe (Int, List Name InventoryListEntry))
- Swarm.TUI.Model: uiInventoryShouldUpdate :: Lens' UIState Bool
- Swarm.TUI.Model: uiInventorySort :: Lens' UIState InventorySortOptions
- Swarm.TUI.Model: uiMenu :: Lens' UIState Menu
- Swarm.TUI.Model: uiModal :: Lens' UIState (Maybe Modal)
- Swarm.TUI.Model: uiMoreInfoBot :: Lens' UIState Bool
- Swarm.TUI.Model: uiMoreInfoTop :: Lens' UIState Bool
- Swarm.TUI.Model: uiPlaying :: Lens' UIState Bool
- Swarm.TUI.Model: uiREPL :: Lens' UIState REPLState
- Swarm.TUI.Model: uiScrollToEnd :: Lens' UIState Bool
- Swarm.TUI.Model: uiShowFPS :: Lens' UIState Bool
- Swarm.TUI.Model: uiShowRobots :: Getter UIState Bool
- Swarm.TUI.Model: uiShowZero :: Lens' UIState Bool
- Swarm.TUI.Model: uiTPF :: Lens' UIState Double
- Swarm.TUI.Model: uiWorldCursor :: Lens' UIState (Maybe Coords)
- Swarm.Util: getSwarmDataPath :: Bool -> IO FilePath
- Swarm.Util: instance Data.Aeson.Types.FromJSON.FromJSON (Linear.V2.V2 GHC.Int.Int64)
- Swarm.Util: instance Data.Aeson.Types.FromJSON.FromJSONKey (Linear.V2.V2 GHC.Int.Int64)
- Swarm.Util: instance Data.Aeson.Types.ToJSON.ToJSON (Linear.V2.V2 GHC.Int.Int64)
- Swarm.Util: instance Data.Aeson.Types.ToJSON.ToJSONKey (Linear.V2.V2 GHC.Int.Int64)
+ Data.BoolExpr: (/\) :: Boolean f => f a -> f a -> f a
+ Data.BoolExpr: (\/) :: Boolean f => f a -> f a -> f a
+ Data.BoolExpr: BAnd :: BoolExpr a -> BoolExpr a -> BoolExpr a
+ Data.BoolExpr: BConst :: Signed a -> BoolExpr a
+ Data.BoolExpr: BFalse :: BoolExpr a
+ Data.BoolExpr: BNot :: BoolExpr a -> BoolExpr a
+ Data.BoolExpr: BOr :: BoolExpr a -> BoolExpr a -> BoolExpr a
+ Data.BoolExpr: BTrue :: BoolExpr a
+ Data.BoolExpr: CNF :: Conj (Disj (Signed a)) -> CNF a
+ Data.BoolExpr: Conj :: [a] -> Conj a
+ Data.BoolExpr: DNF :: Disj (Conj (Signed a)) -> DNF a
+ Data.BoolExpr: Disj :: [a] -> Disj a
+ Data.BoolExpr: Eval :: ((a -> b) -> b) -> Eval b a
+ Data.BoolExpr: Negative :: a -> Signed a
+ Data.BoolExpr: Positive :: a -> Signed a
+ Data.BoolExpr: [runEval] :: Eval b a -> (a -> b) -> b
+ Data.BoolExpr: [unCNF] :: CNF a -> Conj (Disj (Signed a))
+ Data.BoolExpr: [unConj] :: Conj a -> [a]
+ Data.BoolExpr: [unDNF] :: DNF a -> Disj (Conj (Signed a))
+ Data.BoolExpr: [unDisj] :: Disj a -> [a]
+ Data.BoolExpr: bConst :: Boolean f => Signed a -> f a
+ Data.BoolExpr: bFalse :: Boolean f => f a
+ Data.BoolExpr: bNot :: Boolean f => f a -> f a
+ Data.BoolExpr: bTrue :: Boolean f => f a
+ Data.BoolExpr: boolTreeToCNF :: BoolExpr a -> CNF a
+ Data.BoolExpr: boolTreeToDNF :: BoolExpr a -> DNF a
+ Data.BoolExpr: class Boolean f
+ Data.BoolExpr: constants :: BoolExpr a -> [Signed a]
+ Data.BoolExpr: data BoolExpr a
+ Data.BoolExpr: data Signed a
+ Data.BoolExpr: dualize :: Boolean f => BoolExpr a -> f a
+ Data.BoolExpr: evalBoolExpr :: (a -> Bool) -> BoolExpr a -> Bool
+ Data.BoolExpr: evalSigned :: (a -> Bool) -> Signed a -> Bool
+ Data.BoolExpr: fromBoolExpr :: Boolean f => BoolExpr a -> f a
+ Data.BoolExpr: infix 9 \/
+ Data.BoolExpr: instance (b GHC.Types.~ GHC.Types.Bool) => Data.BoolExpr.Boolean (Data.BoolExpr.Eval b)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.BoolExpr a)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.CNF a)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.Conj a)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.DNF a)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.Disj a)
+ Data.BoolExpr: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.BoolExpr.Signed a)
+ Data.BoolExpr: instance Data.BoolExpr.Boolean Data.BoolExpr.BoolExpr
+ Data.BoolExpr: instance Data.BoolExpr.Boolean Data.BoolExpr.CNF
+ Data.BoolExpr: instance Data.BoolExpr.Boolean Data.BoolExpr.DNF
+ Data.BoolExpr: instance Data.Foldable.Foldable Data.BoolExpr.BoolExpr
+ Data.BoolExpr: instance Data.Foldable.Foldable Data.BoolExpr.Signed
+ Data.BoolExpr: instance Data.Traversable.Traversable Data.BoolExpr.BoolExpr
+ Data.BoolExpr: instance Data.Traversable.Traversable Data.BoolExpr.Signed
+ Data.BoolExpr: instance GHC.Base.Applicative Data.BoolExpr.Signed
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.BoolExpr
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.CNF
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.Conj
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.DNF
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.Disj
+ Data.BoolExpr: instance GHC.Base.Functor Data.BoolExpr.Signed
+ Data.BoolExpr: instance GHC.Base.Monad Data.BoolExpr.Signed
+ Data.BoolExpr: instance GHC.Base.Monoid (Data.BoolExpr.CNF a)
+ Data.BoolExpr: instance GHC.Base.Monoid (Data.BoolExpr.Conj a)
+ Data.BoolExpr: instance GHC.Base.Monoid (Data.BoolExpr.DNF a)
+ Data.BoolExpr: instance GHC.Base.Monoid (Data.BoolExpr.Disj a)
+ Data.BoolExpr: instance GHC.Base.Semigroup (Data.BoolExpr.CNF a)
+ Data.BoolExpr: instance GHC.Base.Semigroup (Data.BoolExpr.Conj a)
+ Data.BoolExpr: instance GHC.Base.Semigroup (Data.BoolExpr.DNF a)
+ Data.BoolExpr: instance GHC.Base.Semigroup (Data.BoolExpr.Disj a)
+ Data.BoolExpr: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.BoolExpr.BoolExpr a)
+ Data.BoolExpr: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.BoolExpr.Signed a)
+ Data.BoolExpr: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.BoolExpr.BoolExpr a)
+ Data.BoolExpr: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.BoolExpr.Signed a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.BoolExpr a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.CNF a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.Conj a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.DNF a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.Disj a)
+ Data.BoolExpr: instance GHC.Generics.Generic (Data.BoolExpr.Signed a)
+ Data.BoolExpr: instance GHC.Read.Read a => GHC.Read.Read (Data.BoolExpr.Signed a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.BoolExpr a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.CNF a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.Conj a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.DNF a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.Disj a)
+ Data.BoolExpr: instance GHC.Show.Show a => GHC.Show.Show (Data.BoolExpr.Signed a)
+ Data.BoolExpr: negateConstant :: Boolean f => Signed a -> f a
+ Data.BoolExpr: negateSigned :: Signed a -> Signed a
+ Data.BoolExpr: newtype CNF a
+ Data.BoolExpr: newtype Conj a
+ Data.BoolExpr: newtype DNF a
+ Data.BoolExpr: newtype Disj a
+ Data.BoolExpr: newtype Eval b a
+ Data.BoolExpr: pushNotInwards :: Boolean f => BoolExpr a -> f a
+ Data.BoolExpr: reduceBoolExpr :: BoolExpr Bool -> Bool
+ Data.BoolExpr: runEvalId :: Eval a a -> a
+ Data.BoolExpr.Simplify: cannotBeTrue :: Ord a => BoolExpr a -> Bool
+ Data.BoolExpr.Simplify: replace :: Ord a => Map a Bool -> BoolExpr a -> BoolExpr a
+ Swarm.Game.CESK: AddEntity :: Count -> Entity -> RobotUpdate
+ Swarm.Game.CESK: FDiscardEnv :: Frame
+ Swarm.Game.CESK: FMeetAll :: Value -> [Int] -> Frame
+ Swarm.Game.CESK: LearnEntity :: Entity -> RobotUpdate
+ Swarm.Game.CESK: ReplaceEntity :: Location -> e -> Maybe e -> WorldUpdate e
+ Swarm.Game.CESK: [newEntity] :: WorldUpdate e -> Maybe e
+ Swarm.Game.CESK: [originalEntity] :: WorldUpdate e -> e
+ Swarm.Game.CESK: [updatedLoc] :: WorldUpdate e -> Location
+ Swarm.Game.CESK: data RobotUpdate
+ Swarm.Game.CESK: data WorldUpdate e
+ Swarm.Game.CESK: instance GHC.Classes.Ord Swarm.Game.CESK.RobotUpdate
+ Swarm.Game.CESK: instance GHC.Generics.Generic Swarm.Game.CESK.RobotUpdate
+ Swarm.Game.CESK: instance Swarm.Language.Pretty.PrettyPrec Swarm.Game.CESK.CESK
+ Swarm.Game.CESK: type Addr = Int
+ Swarm.Game.Entity: entitiesByCapability :: Inventory -> Map Capability (NonEmpty Entity)
+ Swarm.Game.Entity: extantElemsWithCapability :: Capability -> Inventory -> [Entity]
+ Swarm.Game.Exception: FixByEquip :: IncapableFix
+ Swarm.Game.Log: Critical :: ErrorLevel
+ Swarm.Game.Log: Debug :: ErrorLevel
+ Swarm.Game.Log: Error :: ErrorLevel
+ Swarm.Game.Log: ErrorTrace :: ErrorLevel -> LogSource
+ Swarm.Game.Log: LogEntry :: Integer -> LogSource -> Text -> Int -> Location -> Text -> LogEntry
+ Swarm.Game.Log: Logged :: LogSource
+ Swarm.Game.Log: Said :: LogSource
+ Swarm.Game.Log: Warning :: ErrorLevel
+ Swarm.Game.Log: [_leLocation] :: LogEntry -> Location
+ Swarm.Game.Log: [_leRobotID] :: LogEntry -> Int
+ Swarm.Game.Log: [_leRobotName] :: LogEntry -> Text
+ Swarm.Game.Log: [_leSource] :: LogEntry -> LogSource
+ Swarm.Game.Log: [_leText] :: LogEntry -> Text
+ Swarm.Game.Log: [_leTime] :: LogEntry -> Integer
+ Swarm.Game.Log: data ErrorLevel
+ Swarm.Game.Log: data LogEntry
+ Swarm.Game.Log: data LogSource
+ Swarm.Game.Log: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Log.LogSource
+ Swarm.Game.Log: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Log.LogSource
+ Swarm.Game.Log: instance GHC.Classes.Eq Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance GHC.Classes.Eq Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance GHC.Classes.Eq Swarm.Game.Log.LogSource
+ Swarm.Game.Log: instance GHC.Classes.Ord Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance GHC.Classes.Ord Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance GHC.Classes.Ord Swarm.Game.Log.LogSource
+ Swarm.Game.Log: instance GHC.Generics.Generic Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance GHC.Generics.Generic Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance GHC.Generics.Generic Swarm.Game.Log.LogSource
+ Swarm.Game.Log: instance GHC.Show.Show Swarm.Game.Log.ErrorLevel
+ Swarm.Game.Log: instance GHC.Show.Show Swarm.Game.Log.LogEntry
+ Swarm.Game.Log: instance GHC.Show.Show Swarm.Game.Log.LogSource
+ Swarm.Game.Log: leLocation :: Lens' LogEntry Location
+ Swarm.Game.Log: leRobotID :: Lens' LogEntry Int
+ Swarm.Game.Log: leRobotName :: Lens' LogEntry Text
+ Swarm.Game.Log: leSource :: Lens' LogEntry LogSource
+ Swarm.Game.Log: leText :: Lens' LogEntry Text
+ Swarm.Game.Log: leTime :: Lens' LogEntry Integer
+ Swarm.Game.Robot: AddEntity :: Count -> Entity -> RobotUpdate
+ Swarm.Game.Robot: LearnEntity :: Entity -> RobotUpdate
+ Swarm.Game.Robot: data RobotUpdate
+ Swarm.Game.Robot: equippedDevices :: Lens' Robot Inventory
+ Swarm.Game.Robot: trobotContext :: Lens' TRobot RobotContext
+ Swarm.Game.Scenario: data PCell e
+ Swarm.Game.Scenario: data PWorldDescription e
+ Swarm.Game.Scenario: scenarioAttrs :: Lens' Scenario [CustomAttr]
+ Swarm.Game.Scenario: type Cell = PCell Entity
+ Swarm.Game.Scenario: type WorldDescription = PWorldDescription Entity
+ Swarm.Game.Scenario.Cell: Cell :: TerrainType -> Maybe e -> [IndexedTRobot] -> PCell e
+ Swarm.Game.Scenario.Cell: [cellEntity] :: PCell e -> Maybe e
+ Swarm.Game.Scenario.Cell: [cellRobots] :: PCell e -> [IndexedTRobot]
+ Swarm.Game.Scenario.Cell: [cellTerrain] :: PCell e -> TerrainType
+ Swarm.Game.Scenario.Cell: data PCell e
+ Swarm.Game.Scenario.Cell: instance GHC.Classes.Eq e => GHC.Classes.Eq (Swarm.Game.Scenario.Cell.PCell e)
+ Swarm.Game.Scenario.Cell: instance GHC.Show.Show e => GHC.Show.Show (Swarm.Game.Scenario.Cell.PCell e)
+ Swarm.Game.Scenario.Cell: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotLookup.RobotMap) Swarm.Game.Scenario.Cell.Cell
+ Swarm.Game.Scenario.Cell: type Cell = PCell Entity
+ Swarm.Game.Scenario.Objective: CompletionBuckets :: [Objective] -> [Objective] -> [Objective] -> CompletionBuckets
+ Swarm.Game.Scenario.Objective: Objective :: [Text] -> Maybe Text -> ProcessedTerm -> Maybe ObjectiveLabel -> Bool -> Maybe PrerequisiteConfig -> Bool -> Maybe AchievementInfo -> Objective
+ Swarm.Game.Scenario.Objective: ObjectiveCompletion :: CompletionBuckets -> Set ObjectiveLabel -> ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: PrerequisiteConfig :: Bool -> Prerequisite ObjectiveLabel -> PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: [_objectiveAchievement] :: Objective -> Maybe AchievementInfo
+ Swarm.Game.Scenario.Objective: [_objectiveCondition] :: Objective -> ProcessedTerm
+ Swarm.Game.Scenario.Objective: [_objectiveGoal] :: Objective -> [Text]
+ Swarm.Game.Scenario.Objective: [_objectiveHidden] :: Objective -> Bool
+ Swarm.Game.Scenario.Objective: [_objectiveId] :: Objective -> Maybe ObjectiveLabel
+ Swarm.Game.Scenario.Objective: [_objectiveOptional] :: Objective -> Bool
+ Swarm.Game.Scenario.Objective: [_objectivePrerequisite] :: Objective -> Maybe PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: [_objectiveTeaser] :: Objective -> Maybe Text
+ Swarm.Game.Scenario.Objective: [completedIDs] :: ObjectiveCompletion -> Set ObjectiveLabel
+ Swarm.Game.Scenario.Objective: [completed] :: CompletionBuckets -> [Objective]
+ Swarm.Game.Scenario.Objective: [completionBuckets] :: ObjectiveCompletion -> CompletionBuckets
+ Swarm.Game.Scenario.Objective: [incomplete] :: CompletionBuckets -> [Objective]
+ Swarm.Game.Scenario.Objective: [logic] :: PrerequisiteConfig -> Prerequisite ObjectiveLabel
+ Swarm.Game.Scenario.Objective: [previewable] :: PrerequisiteConfig -> Bool
+ Swarm.Game.Scenario.Objective: [unwinnable] :: CompletionBuckets -> [Objective]
+ Swarm.Game.Scenario.Objective: addCompleted :: Objective -> ObjectiveCompletion -> ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: addIncomplete :: Objective -> ObjectiveCompletion -> ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: addUnwinnable :: Objective -> ObjectiveCompletion -> ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: data CompletionBuckets
+ Swarm.Game.Scenario.Objective: data Objective
+ Swarm.Game.Scenario.Objective: data ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: data PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: extractIncomplete :: ObjectiveCompletion -> (ObjectiveCompletion, [Objective])
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Objective.CompletionBuckets
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Objective.Objective
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Objective.ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Objective.PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.CompletionBuckets
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Objective
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: instance GHC.Classes.Eq Swarm.Game.Scenario.Objective.Objective
+ Swarm.Game.Scenario.Objective: instance GHC.Classes.Eq Swarm.Game.Scenario.Objective.PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.CompletionBuckets
+ Swarm.Game.Scenario.Objective: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Objective
+ Swarm.Game.Scenario.Objective: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: instance GHC.Show.Show Swarm.Game.Scenario.Objective.CompletionBuckets
+ Swarm.Game.Scenario.Objective: instance GHC.Show.Show Swarm.Game.Scenario.Objective.Objective
+ Swarm.Game.Scenario.Objective: instance GHC.Show.Show Swarm.Game.Scenario.Objective.ObjectiveCompletion
+ Swarm.Game.Scenario.Objective: instance GHC.Show.Show Swarm.Game.Scenario.Objective.PrerequisiteConfig
+ Swarm.Game.Scenario.Objective: listAllObjectives :: CompletionBuckets -> [Objective]
+ Swarm.Game.Scenario.Objective: objectiveAchievement :: Lens' Objective (Maybe AchievementInfo)
+ Swarm.Game.Scenario.Objective: objectiveCondition :: Lens' Objective ProcessedTerm
+ Swarm.Game.Scenario.Objective: objectiveGoal :: Lens' Objective [Text]
+ Swarm.Game.Scenario.Objective: objectiveHidden :: Lens' Objective Bool
+ Swarm.Game.Scenario.Objective: objectiveId :: Lens' Objective (Maybe Text)
+ Swarm.Game.Scenario.Objective: objectiveOptional :: Lens' Objective Bool
+ Swarm.Game.Scenario.Objective: objectivePrerequisite :: Lens' Objective (Maybe PrerequisiteConfig)
+ Swarm.Game.Scenario.Objective: objectiveTeaser :: Lens' Objective (Maybe Text)
+ Swarm.Game.Scenario.Objective: setIncomplete :: ([Objective] -> [Objective]) -> ObjectiveCompletion -> ObjectiveCompletion
+ Swarm.Game.Scenario.Objective.Graph: GraphInfo :: Graph -> Bool -> [SCC Objective] -> [ObjectiveId] -> GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: Label :: Signed ObjectiveLabel -> ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: Ordinal :: Int -> ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: [actualGraph] :: GraphInfo -> Graph
+ Swarm.Game.Scenario.Objective.Graph: [isAcyclic] :: GraphInfo -> Bool
+ Swarm.Game.Scenario.Objective.Graph: [nodeIDs] :: GraphInfo -> [ObjectiveId]
+ Swarm.Game.Scenario.Objective.Graph: [sccInfo] :: GraphInfo -> [SCC Objective]
+ Swarm.Game.Scenario.Objective.Graph: assignIds :: [Objective] -> Map ObjectiveId Objective
+ Swarm.Game.Scenario.Objective.Graph: data GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: data ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: getConstFromSigned :: Signed a -> a
+ Swarm.Game.Scenario.Objective.Graph: getNegatedIds :: [Objective] -> Map ObjectiveLabel Objective
+ Swarm.Game.Scenario.Objective.Graph: getObjectivesById :: [Objective] -> Map ObjectiveLabel Objective
+ Swarm.Game.Scenario.Objective.Graph: instance Data.Aeson.Types.ToJSON.ToJSON (Data.Graph.SCC Swarm.Game.Scenario.Objective.Objective)
+ Swarm.Game.Scenario.Objective.Graph: instance Data.Aeson.Types.ToJSON.ToJSON Data.Graph.Graph
+ Swarm.Game.Scenario.Objective.Graph: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Graph.GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Graph.ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Classes.Eq Swarm.Game.Scenario.Objective.Graph.ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Classes.Ord Swarm.Game.Scenario.Objective.Graph.ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Graph.GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Graph.ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Show.Show Swarm.Game.Scenario.Objective.Graph.GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: instance GHC.Show.Show Swarm.Game.Scenario.Objective.Graph.ObjectiveId
+ Swarm.Game.Scenario.Objective.Graph: isAcyclicGraph :: [SCC Objective] -> Bool
+ Swarm.Game.Scenario.Objective.Graph: makeGraph :: Edges -> Graph
+ Swarm.Game.Scenario.Objective.Graph: makeGraphEdges :: [Objective] -> Edges
+ Swarm.Game.Scenario.Objective.Graph: makeGraphInfo :: ObjectiveCompletion -> GraphInfo
+ Swarm.Game.Scenario.Objective.Graph: type Edges = [(Objective, ObjectiveId, [ObjectiveId])]
+ Swarm.Game.Scenario.Objective.Logic: And :: NonEmpty (Prerequisite a) -> Prerequisite a
+ Swarm.Game.Scenario.Objective.Logic: Id :: a -> Prerequisite a
+ Swarm.Game.Scenario.Objective.Logic: Not :: Prerequisite a -> Prerequisite a
+ Swarm.Game.Scenario.Objective.Logic: Or :: NonEmpty (Prerequisite a) -> Prerequisite a
+ Swarm.Game.Scenario.Objective.Logic: data Prerequisite a
+ Swarm.Game.Scenario.Objective.Logic: instance Data.Aeson.Types.FromJSON.FromJSON (Swarm.Game.Scenario.Objective.Logic.Prerequisite Swarm.Game.Scenario.Objective.Logic.ObjectiveLabel)
+ Swarm.Game.Scenario.Objective.Logic: instance Data.Aeson.Types.ToJSON.ToJSON (Swarm.Game.Scenario.Objective.Logic.Prerequisite Swarm.Game.Scenario.Objective.Logic.ObjectiveLabel)
+ Swarm.Game.Scenario.Objective.Logic: instance Data.Foldable.Foldable Swarm.Game.Scenario.Objective.Logic.Prerequisite
+ Swarm.Game.Scenario.Objective.Logic: instance GHC.Base.Functor Swarm.Game.Scenario.Objective.Logic.Prerequisite
+ Swarm.Game.Scenario.Objective.Logic: instance GHC.Classes.Eq a => GHC.Classes.Eq (Swarm.Game.Scenario.Objective.Logic.Prerequisite a)
+ Swarm.Game.Scenario.Objective.Logic: instance GHC.Generics.Generic (Swarm.Game.Scenario.Objective.Logic.Prerequisite a)
+ Swarm.Game.Scenario.Objective.Logic: instance GHC.Show.Show a => GHC.Show.Show (Swarm.Game.Scenario.Objective.Logic.Prerequisite a)
+ Swarm.Game.Scenario.Objective.Logic: prerequisiteOptions :: Options
+ Swarm.Game.Scenario.Objective.Logic: toBoolExpr :: Prerequisite a -> BoolExpr a
+ Swarm.Game.Scenario.Objective.Logic: type ObjectiveLabel = Text
+ Swarm.Game.Scenario.Objective.Presentation.Model: Active :: GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: Completed :: GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: Failed :: GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: Goal :: GoalStatus -> Objective -> GoalEntry
+ Swarm.Game.Scenario.Objective.Presentation.Model: GoalDisplay :: GoalTracking -> List Name GoalEntry -> FocusRing Name -> GoalDisplay
+ Swarm.Game.Scenario.Objective.Presentation.Model: GoalTracking :: [Announcement] -> CategorizedGoals -> GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: Header :: GoalStatus -> GoalEntry
+ Swarm.Game.Scenario.Objective.Presentation.Model: ObjectiveCompleted :: Objective -> Announcement
+ Swarm.Game.Scenario.Objective.Presentation.Model: Upcoming :: GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: [_focus] :: GoalDisplay -> FocusRing Name
+ Swarm.Game.Scenario.Objective.Presentation.Model: [_goalsContent] :: GoalDisplay -> GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: [_listWidget] :: GoalDisplay -> List Name GoalEntry
+ Swarm.Game.Scenario.Objective.Presentation.Model: [announcements] :: GoalTracking -> [Announcement]
+ Swarm.Game.Scenario.Objective.Presentation.Model: [goals] :: GoalTracking -> CategorizedGoals
+ Swarm.Game.Scenario.Objective.Presentation.Model: constructGoalMap :: Bool -> ObjectiveCompletion -> CategorizedGoals
+ Swarm.Game.Scenario.Objective.Presentation.Model: data GoalDisplay
+ Swarm.Game.Scenario.Objective.Presentation.Model: data GoalEntry
+ Swarm.Game.Scenario.Objective.Presentation.Model: data GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: data GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: emptyGoalDisplay :: GoalDisplay
+ Swarm.Game.Scenario.Objective.Presentation.Model: focus :: Lens' GoalDisplay (FocusRing Name)
+ Swarm.Game.Scenario.Objective.Presentation.Model: goalsContent :: Lens' GoalDisplay GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: hasAnythingToShow :: GoalTracking -> Bool
+ Swarm.Game.Scenario.Objective.Presentation.Model: hasMultipleGoals :: GoalTracking -> Bool
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Presentation.Model.Announcement
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.Presentation.Model.GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance Data.Aeson.Types.ToJSON.ToJSONKey Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Classes.Eq Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Classes.Ord Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Enum.Bounded Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Enum.Enum Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Presentation.Model.Announcement
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.Presentation.Model.GoalTracking
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Show.Show Swarm.Game.Scenario.Objective.Presentation.Model.Announcement
+ Swarm.Game.Scenario.Objective.Presentation.Model: instance GHC.Show.Show Swarm.Game.Scenario.Objective.Presentation.Model.GoalStatus
+ Swarm.Game.Scenario.Objective.Presentation.Model: isHeader :: GoalEntry -> Bool
+ Swarm.Game.Scenario.Objective.Presentation.Model: listWidget :: Lens' GoalDisplay (List Name GoalEntry)
+ Swarm.Game.Scenario.Objective.Presentation.Model: newtype Announcement
+ Swarm.Game.Scenario.Objective.Presentation.Model: type CategorizedGoals = Map GoalStatus (NonEmpty Objective)
+ Swarm.Game.Scenario.Objective.Presentation.Render: drawGoalListItem :: Bool -> GoalEntry -> Widget Name
+ Swarm.Game.Scenario.Objective.Presentation.Render: getCompletionIcon :: Objective -> GoalStatus -> Widget Name
+ Swarm.Game.Scenario.Objective.Presentation.Render: makeListWidget :: GoalTracking -> List Name GoalEntry
+ Swarm.Game.Scenario.Objective.Presentation.Render: renderGoalsDisplay :: GoalDisplay -> Widget Name
+ Swarm.Game.Scenario.Objective.Presentation.Render: singleGoalDetails :: GoalEntry -> Widget Name
+ Swarm.Game.Scenario.Objective.Validation: validateObjectives :: MonadFail m => [Objective] -> m [Objective]
+ Swarm.Game.Scenario.Objective.WinCheck: PrereqSatisfaction :: Objective -> Set (Signed ObjectiveLabel) -> Bool -> PrereqSatisfaction
+ Swarm.Game.Scenario.Objective.WinCheck: [deps] :: PrereqSatisfaction -> Set (Signed ObjectiveLabel)
+ Swarm.Game.Scenario.Objective.WinCheck: [objective] :: PrereqSatisfaction -> Objective
+ Swarm.Game.Scenario.Objective.WinCheck: [prereqsSatisfied] :: PrereqSatisfaction -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: data PrereqSatisfaction
+ Swarm.Game.Scenario.Objective.WinCheck: didLose :: ObjectiveCompletion -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: didWin :: ObjectiveCompletion -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: getActiveObjectives :: ObjectiveCompletion -> [Objective]
+ Swarm.Game.Scenario.Objective.WinCheck: getDistinctConstants :: Ord a => Prerequisite a -> Set (Signed a)
+ Swarm.Game.Scenario.Objective.WinCheck: getSatisfaction :: ObjectiveCompletion -> [PrereqSatisfaction]
+ Swarm.Game.Scenario.Objective.WinCheck: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.Scenario.Objective.WinCheck.PrereqSatisfaction
+ Swarm.Game.Scenario.Objective.WinCheck: instance GHC.Generics.Generic Swarm.Game.Scenario.Objective.WinCheck.PrereqSatisfaction
+ Swarm.Game.Scenario.Objective.WinCheck: isPrereqsSatisfied :: ObjectiveCompletion -> Objective -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: isUnwinnable :: ObjectiveCompletion -> Objective -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: isUnwinnablePrereq :: Set ObjectiveLabel -> Prerequisite ObjectiveLabel -> Bool
+ Swarm.Game.Scenario.Objective.WinCheck: partitionActiveObjectives :: ObjectiveCompletion -> ([Objective], [Objective])
+ Swarm.Game.Scenario.RobotLookup: buildRobotMap :: [TRobot] -> RobotMap
+ Swarm.Game.Scenario.RobotLookup: getEntity :: Text -> ParserE EntityMap Entity
+ Swarm.Game.Scenario.RobotLookup: getRobot :: Text -> ParserE RobotMap IndexedTRobot
+ Swarm.Game.Scenario.RobotLookup: getThing :: String -> (Text -> m -> Maybe a) -> Text -> ParserE m a
+ Swarm.Game.Scenario.RobotLookup: type IndexedTRobot = (Int, TRobot)
+ Swarm.Game.Scenario.RobotLookup: type RobotMap = Map Text IndexedTRobot
+ Swarm.Game.Scenario.Style: Blink :: StyleFlag
+ Swarm.Game.Scenario.Style: Bold :: StyleFlag
+ Swarm.Game.Scenario.Style: CustomAttr :: String -> Maybe HexColor -> Maybe HexColor -> Maybe (Set StyleFlag) -> CustomAttr
+ Swarm.Game.Scenario.Style: Dim :: StyleFlag
+ Swarm.Game.Scenario.Style: HexColor :: Text -> HexColor
+ Swarm.Game.Scenario.Style: Italic :: StyleFlag
+ Swarm.Game.Scenario.Style: ReverseVideo :: StyleFlag
+ Swarm.Game.Scenario.Style: Standout :: StyleFlag
+ Swarm.Game.Scenario.Style: Strikethrough :: StyleFlag
+ Swarm.Game.Scenario.Style: Underline :: StyleFlag
+ Swarm.Game.Scenario.Style: [bg] :: CustomAttr -> Maybe HexColor
+ Swarm.Game.Scenario.Style: [fg] :: CustomAttr -> Maybe HexColor
+ Swarm.Game.Scenario.Style: [name] :: CustomAttr -> String
+ Swarm.Game.Scenario.Style: [style] :: CustomAttr -> Maybe (Set StyleFlag)
+ Swarm.Game.Scenario.Style: data CustomAttr
+ Swarm.Game.Scenario.Style: data StyleFlag
+ Swarm.Game.Scenario.Style: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Style.CustomAttr
+ Swarm.Game.Scenario.Style: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Style.HexColor
+ Swarm.Game.Scenario.Style: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.Scenario.Style.StyleFlag
+ Swarm.Game.Scenario.Style: instance GHC.Classes.Eq Swarm.Game.Scenario.Style.CustomAttr
+ Swarm.Game.Scenario.Style: instance GHC.Classes.Eq Swarm.Game.Scenario.Style.HexColor
+ Swarm.Game.Scenario.Style: instance GHC.Classes.Eq Swarm.Game.Scenario.Style.StyleFlag
+ Swarm.Game.Scenario.Style: instance GHC.Classes.Ord Swarm.Game.Scenario.Style.StyleFlag
+ Swarm.Game.Scenario.Style: instance GHC.Generics.Generic Swarm.Game.Scenario.Style.CustomAttr
+ Swarm.Game.Scenario.Style: instance GHC.Generics.Generic Swarm.Game.Scenario.Style.HexColor
+ Swarm.Game.Scenario.Style: instance GHC.Generics.Generic Swarm.Game.Scenario.Style.StyleFlag
+ Swarm.Game.Scenario.Style: instance GHC.Show.Show Swarm.Game.Scenario.Style.CustomAttr
+ Swarm.Game.Scenario.Style: instance GHC.Show.Show Swarm.Game.Scenario.Style.HexColor
+ Swarm.Game.Scenario.Style: instance GHC.Show.Show Swarm.Game.Scenario.Style.StyleFlag
+ Swarm.Game.Scenario.Style: newtype HexColor
+ Swarm.Game.Scenario.Style: styleFlagJsonOptions :: Options
+ Swarm.Game.Scenario.WorldDescription: WorldDescription :: Maybe (PCell e) -> Bool -> WorldPalette e -> Location -> [[PCell e]] -> PWorldDescription e
+ Swarm.Game.Scenario.WorldDescription: WorldPalette :: KeyMap (PCell e) -> WorldPalette e
+ Swarm.Game.Scenario.WorldDescription: [area] :: PWorldDescription e -> [[PCell e]]
+ Swarm.Game.Scenario.WorldDescription: [defaultTerrain] :: PWorldDescription e -> Maybe (PCell e)
+ Swarm.Game.Scenario.WorldDescription: [offsetOrigin] :: PWorldDescription e -> Bool
+ Swarm.Game.Scenario.WorldDescription: [palette] :: PWorldDescription e -> WorldPalette e
+ Swarm.Game.Scenario.WorldDescription: [ul] :: PWorldDescription e -> Location
+ Swarm.Game.Scenario.WorldDescription: [unPalette] :: WorldPalette e -> KeyMap (PCell e)
+ Swarm.Game.Scenario.WorldDescription: data PWorldDescription e
+ Swarm.Game.Scenario.WorldDescription: instance GHC.Classes.Eq e => GHC.Classes.Eq (Swarm.Game.Scenario.WorldDescription.PWorldDescription e)
+ Swarm.Game.Scenario.WorldDescription: instance GHC.Classes.Eq e => GHC.Classes.Eq (Swarm.Game.Scenario.WorldDescription.WorldPalette e)
+ Swarm.Game.Scenario.WorldDescription: instance GHC.Show.Show e => GHC.Show.Show (Swarm.Game.Scenario.WorldDescription.PWorldDescription e)
+ Swarm.Game.Scenario.WorldDescription: instance GHC.Show.Show e => GHC.Show.Show (Swarm.Game.Scenario.WorldDescription.WorldPalette e)
+ Swarm.Game.Scenario.WorldDescription: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotLookup.RobotMap) (Swarm.Game.Scenario.WorldDescription.WorldPalette Swarm.Game.Entity.Entity)
+ Swarm.Game.Scenario.WorldDescription: instance Swarm.Util.Yaml.FromJSONE (Swarm.Game.Entity.EntityMap, Swarm.Game.Scenario.RobotLookup.RobotMap) Swarm.Game.Scenario.WorldDescription.WorldDescription
+ Swarm.Game.Scenario.WorldDescription: newtype WorldPalette e
+ Swarm.Game.Scenario.WorldDescription: paintMap :: MonadFail m => WorldPalette e -> Text -> m [[PCell e]]
+ Swarm.Game.Scenario.WorldDescription: type WorldDescription = PWorldDescription Entity
+ Swarm.Game.State: ObjectiveCompleted :: Objective -> Announcement
+ Swarm.Game.State: ObjectiveCompletion :: CompletionBuckets -> Set ObjectiveLabel -> ObjectiveCompletion
+ Swarm.Game.State: Ongoing :: WinStatus
+ Swarm.Game.State: Unwinnable :: Bool -> WinStatus
+ Swarm.Game.State: [completedIDs] :: ObjectiveCompletion -> Set ObjectiveLabel
+ Swarm.Game.State: [completionBuckets] :: ObjectiveCompletion -> CompletionBuckets
+ Swarm.Game.State: announcementQueue :: Lens' GameState (Seq Announcement)
+ Swarm.Game.State: data ObjectiveCompletion
+ Swarm.Game.State: data WinStatus
+ Swarm.Game.State: gameAchievements :: Lens' GameState (Map GameplayAchievement Attainment)
+ Swarm.Game.State: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Game.State.WinStatus
+ Swarm.Game.State: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Game.State.WinStatus
+ Swarm.Game.State: instance GHC.Generics.Generic Swarm.Game.State.WinStatus
+ Swarm.Game.State: instance GHC.Show.Show Swarm.Game.State.WinStatus
+ Swarm.Game.State: newtype Announcement
+ Swarm.Game.Step: CompletionsWithExceptions :: [Text] -> ObjectiveCompletion -> [Objective] -> CompletionsWithExceptions
+ Swarm.Game.Step: [completionAnnouncementQueue] :: CompletionsWithExceptions -> [Objective]
+ Swarm.Game.Step: [completions] :: CompletionsWithExceptions -> ObjectiveCompletion
+ Swarm.Game.Step: [exceptions] :: CompletionsWithExceptions -> [Text]
+ Swarm.Game.Step: cmdExnWithAchievement :: Const -> [Text] -> GameplayAchievement -> Exn
+ Swarm.Game.Step: data CompletionsWithExceptions
+ Swarm.Game.Step: formatDevices :: Set Entity -> Text
+ Swarm.Game.Step: hypotheticalWinCheck :: (Has (State GameState) sig m, Has (Lift IO) sig m) => EntityMap -> GameState -> WinStatus -> ObjectiveCompletion -> m ()
+ Swarm.Game.Step: updateWorld :: (Has (State GameState) sig m, Has (Throw Exn) sig m) => Const -> WorldUpdate Entity -> m ()
+ Swarm.Game.World: ReplaceEntity :: Location -> e -> Maybe e -> WorldUpdate e
+ Swarm.Game.World: [newEntity] :: WorldUpdate e -> Maybe e
+ Swarm.Game.World: [originalEntity] :: WorldUpdate e -> e
+ Swarm.Game.World: [updatedLoc] :: WorldUpdate e -> Location
+ Swarm.Game.World: data WorldUpdate e
+ Swarm.Game.World: instance Data.Aeson.Types.FromJSON.FromJSON e => Data.Aeson.Types.FromJSON.FromJSON (Swarm.Game.World.WorldUpdate e)
+ Swarm.Game.World: instance Data.Aeson.Types.ToJSON.ToJSON e => Data.Aeson.Types.ToJSON.ToJSON (Swarm.Game.World.WorldUpdate e)
+ Swarm.Game.World: instance GHC.Classes.Eq e => GHC.Classes.Eq (Swarm.Game.World.WorldUpdate e)
+ Swarm.Game.World: instance GHC.Classes.Ord e => GHC.Classes.Ord (Swarm.Game.World.WorldUpdate e)
+ Swarm.Game.World: instance GHC.Generics.Generic (Swarm.Game.World.WorldUpdate e)
+ Swarm.Game.World: instance GHC.Show.Show e => GHC.Show.Show (Swarm.Game.World.WorldUpdate e)
+ Swarm.Language.Capability: CCode :: Capability
+ Swarm.Language.Capability: CEquip :: Capability
+ Swarm.Language.Capability: CMeet :: Capability
+ Swarm.Language.Capability: CUnequip :: Capability
+ Swarm.Language.Elaborate: sForce :: Syntax' Polytype
+ Swarm.Language.LSP: diagnosticSourcePrefix :: Text
+ Swarm.Language.LSP.Hover: explain :: ExplainableType ty => Syntax' ty -> Tree Text
+ Swarm.Language.LSP.Hover: instance Swarm.Language.LSP.Hover.ExplainableType ()
+ Swarm.Language.LSP.Hover: instance Swarm.Language.LSP.Hover.ExplainableType Swarm.Language.Types.Polytype
+ Swarm.Language.LSP.Hover: narrowToPosition :: ExplainableType ty => Syntax' ty -> Int -> Syntax' ty
+ Swarm.Language.LSP.Hover: renderDoc :: Int -> Text -> Text
+ Swarm.Language.LSP.Hover: showHoverInfo :: NormalizedUri -> TextDocumentVersion -> Position -> VirtualFile -> Maybe (Text, Maybe Range)
+ Swarm.Language.LSP.Hover: treeToMarkdown :: Int -> Tree Text -> Text
+ Swarm.Language.LSP.VarUsage: Bind :: BindingType
+ Swarm.Language.LSP.VarUsage: Lambda :: BindingType
+ Swarm.Language.LSP.VarUsage: Let :: BindingType
+ Swarm.Language.LSP.VarUsage: Usage :: Set LocVar -> [VarUsage] -> Usage
+ Swarm.Language.LSP.VarUsage: VarUsage :: LocVar -> BindingType -> VarUsage
+ Swarm.Language.LSP.VarUsage: [problems] :: Usage -> [VarUsage]
+ Swarm.Language.LSP.VarUsage: [usages] :: Usage -> Set LocVar
+ Swarm.Language.LSP.VarUsage: checkOccurrences :: BindingSites -> LocVar -> BindingType -> [Syntax] -> Usage
+ Swarm.Language.LSP.VarUsage: data BindingType
+ Swarm.Language.LSP.VarUsage: data Usage
+ Swarm.Language.LSP.VarUsage: data VarUsage
+ Swarm.Language.LSP.VarUsage: getUsage :: BindingSites -> Syntax -> Usage
+ Swarm.Language.LSP.VarUsage: instance GHC.Base.Monoid Swarm.Language.LSP.VarUsage.Usage
+ Swarm.Language.LSP.VarUsage: instance GHC.Base.Semigroup Swarm.Language.LSP.VarUsage.Usage
+ Swarm.Language.LSP.VarUsage: instance GHC.Classes.Eq Swarm.Language.LSP.VarUsage.BindingType
+ Swarm.Language.LSP.VarUsage: instance GHC.Show.Show Swarm.Language.LSP.VarUsage.BindingType
+ Swarm.Language.LSP.VarUsage: toErrPos :: Text -> VarUsage -> Maybe (Range, Text)
+ Swarm.Language.LSP.VarUsage: type BindingSites = Map Var (NonEmpty SrcLoc)
+ Swarm.Language.Module: Module :: Syntax' s -> Ctx t -> Module s t
+ Swarm.Language.Module: [moduleAST] :: Module s t -> Syntax' s
+ Swarm.Language.Module: [moduleCtx] :: Module s t -> Ctx t
+ Swarm.Language.Module: data Module s t
+ Swarm.Language.Module: instance (Data.Aeson.Types.FromJSON.FromJSON s, Data.Aeson.Types.FromJSON.FromJSON t) => Data.Aeson.Types.FromJSON.FromJSON (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: instance (Data.Aeson.Types.ToJSON.ToJSON t, Data.Aeson.Types.ToJSON.ToJSON s) => Data.Aeson.Types.ToJSON.ToJSON (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: instance (Data.Data.Data s, Data.Data.Data t) => Data.Data.Data (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: instance (GHC.Classes.Eq s, GHC.Classes.Eq t) => GHC.Classes.Eq (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: instance (GHC.Show.Show s, GHC.Show.Show t) => GHC.Show.Show (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: instance GHC.Base.Functor (Swarm.Language.Module.Module s)
+ Swarm.Language.Module: instance GHC.Generics.Generic (Swarm.Language.Module.Module s t)
+ Swarm.Language.Module: trivMod :: Syntax' s -> Module s t
+ Swarm.Language.Module: type TModule = Module Polytype Polytype
+ Swarm.Language.Module: type UModule = Module UType UPolytype
+ Swarm.Language.Parse: unTuple :: Syntax' ty -> [Syntax' ty]
+ Swarm.Language.Pretty: instance Swarm.Language.Pretty.PrettyPrec (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: CharAt :: Const
+ Swarm.Language.Syntax: DAbsolute :: AbsoluteDir -> Direction
+ Swarm.Language.Syntax: DRelative :: RelativeDir -> Direction
+ Swarm.Language.Syntax: Equip :: Const
+ Swarm.Language.Syntax: Equipped :: Const
+ Swarm.Language.Syntax: Heading :: Const
+ Swarm.Language.Syntax: Isempty :: Const
+ Swarm.Language.Syntax: LV :: SrcLoc -> Var -> LocVar
+ Swarm.Language.Syntax: Meet :: Const
+ Swarm.Language.Syntax: MeetAll :: Const
+ Swarm.Language.Syntax: SrcLoc :: Int -> Int -> SrcLoc
+ Swarm.Language.Syntax: Syntax' :: SrcLoc -> Term' ty -> ty -> Syntax' ty
+ Swarm.Language.Syntax: ToChar :: Const
+ Swarm.Language.Syntax: Unequip :: Const
+ Swarm.Language.Syntax: [_sLoc] :: Syntax' ty -> SrcLoc
+ Swarm.Language.Syntax: [_sTerm] :: Syntax' ty -> Term' ty
+ Swarm.Language.Syntax: [_sType] :: Syntax' ty -> ty
+ Swarm.Language.Syntax: [lvSrcLoc] :: LocVar -> SrcLoc
+ Swarm.Language.Syntax: [lvVar] :: LocVar -> Var
+ Swarm.Language.Syntax: data AbsoluteDir
+ Swarm.Language.Syntax: data LocVar
+ Swarm.Language.Syntax: data RelativeDir
+ Swarm.Language.Syntax: data SrcLoc
+ Swarm.Language.Syntax: data Syntax' ty
+ Swarm.Language.Syntax: data Term' ty
+ Swarm.Language.Syntax: erase :: Term' ty -> Term
+ Swarm.Language.Syntax: eraseS :: Syntax' ty -> Term
+ Swarm.Language.Syntax: freeVarsS :: forall ty. Traversal' (Syntax' ty) (Syntax' ty)
+ Swarm.Language.Syntax: freeVarsT :: forall ty. Traversal' (Syntax' ty) (Term' ty)
+ Swarm.Language.Syntax: freeVarsV :: Traversal' (Syntax' ty) Var
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON ty => Data.Aeson.Types.FromJSON.FromJSON (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSON ty => Data.Aeson.Types.FromJSON.FromJSON (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance Data.Aeson.Types.FromJSON.FromJSONKey Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON ty => Data.Aeson.Types.ToJSON.ToJSON (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSON ty => Data.Aeson.Types.ToJSON.ToJSON (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance Data.Aeson.Types.ToJSON.ToJSONKey Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance Data.Data.Data Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance Data.Data.Data ty => Control.Lens.Plated.Plated (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance Data.Data.Data ty => Control.Lens.Plated.Plated (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance Data.Data.Data ty => Data.Data.Data (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance Data.Data.Data ty => Data.Data.Data (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance Data.Foldable.Foldable Swarm.Language.Syntax.Syntax'
+ Swarm.Language.Syntax: instance Data.Foldable.Foldable Swarm.Language.Syntax.Term'
+ Swarm.Language.Syntax: instance Data.Hashable.Class.Hashable Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance Data.Hashable.Class.Hashable Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance Data.Traversable.Traversable Swarm.Language.Syntax.Syntax'
+ Swarm.Language.Syntax: instance Data.Traversable.Traversable Swarm.Language.Syntax.Term'
+ Swarm.Language.Syntax: instance GHC.Base.Functor Swarm.Language.Syntax.Syntax'
+ Swarm.Language.Syntax: instance GHC.Base.Functor Swarm.Language.Syntax.Term'
+ Swarm.Language.Syntax: instance GHC.Base.Monoid Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Base.Semigroup Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Classes.Eq Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Classes.Eq ty => GHC.Classes.Eq (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance GHC.Classes.Eq ty => GHC.Classes.Eq (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance GHC.Classes.Ord Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Classes.Ord Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance GHC.Classes.Ord Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Classes.Ord Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Enum.Bounded Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Enum.Bounded Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Enum.Enum Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Enum.Enum Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Generics.Generic (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance GHC.Generics.Generic (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Generics.Generic Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Read.Read Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Read.Read Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.AbsoluteDir
+ Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.LocVar
+ Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.RelativeDir
+ Swarm.Language.Syntax: instance GHC.Show.Show Swarm.Language.Syntax.SrcLoc
+ Swarm.Language.Syntax: instance GHC.Show.Show ty => GHC.Show.Show (Swarm.Language.Syntax.Syntax' ty)
+ Swarm.Language.Syntax: instance GHC.Show.Show ty => GHC.Show.Show (Swarm.Language.Syntax.Term' ty)
+ Swarm.Language.Syntax: locVarToSyntax' :: LocVar -> ty -> Syntax' ty
+ Swarm.Language.Syntax: mapFreeS :: Var -> (Syntax' ty -> Syntax' ty) -> Syntax' ty -> Syntax' ty
+ Swarm.Language.Syntax: pattern Syntax :: SrcLoc -> Term -> Syntax
+ Swarm.Language.Syntax: sLoc :: forall ty_a1DXw. Lens' (Syntax' ty_a1DXw) SrcLoc
+ Swarm.Language.Syntax: sTerm :: forall ty_a1DXw. Lens' (Syntax' ty_a1DXw) (Term' ty_a1DXw)
+ Swarm.Language.Syntax: sType :: forall ty_a1DXw. Lens' (Syntax' ty_a1DXw) ty_a1DXw
+ Swarm.Language.Syntax: type Syntax = Syntax' ()
+ Swarm.Language.Syntax: type Term = Term' ()
+ Swarm.Language.Syntax: unfoldApps :: Syntax' ty -> NonEmpty (Syntax' ty)
+ Swarm.Language.Typecheck: getTypeErrSrcLoc :: TypeErr -> Maybe SrcLoc
+ Swarm.Language.Typecheck: instance (Swarm.Language.Typecheck.HasBindings u, Data.Data.Data u) => Swarm.Language.Typecheck.HasBindings (Swarm.Language.Syntax.Syntax' u)
+ Swarm.Language.Typecheck: instance (Swarm.Language.Typecheck.HasBindings u, Data.Data.Data u) => Swarm.Language.Typecheck.HasBindings (Swarm.Language.Syntax.Term' u)
+ Swarm.Language.Typecheck: instance Swarm.Language.Typecheck.HasBindings Swarm.Language.Module.UModule
+ Swarm.Language.Types: BActor :: BaseTy
+ Swarm.Language.Types: pattern TyActor :: Type
+ Swarm.Language.Types: pattern UTyActor :: UType
+ Swarm.Language.Value: [VBind] :: Maybe Var -> Term -> Term -> Env -> Value
+ Swarm.Language.Value: [VBool] :: Bool -> Value
+ Swarm.Language.Value: [VCApp] :: Const -> [Value] -> Value
+ Swarm.Language.Value: [VClo] :: Var -> Term -> Env -> Value
+ Swarm.Language.Value: [VDef] :: Bool -> Var -> Term -> Env -> Value
+ Swarm.Language.Value: [VDelay] :: Term -> Env -> Value
+ Swarm.Language.Value: [VDir] :: Direction -> Value
+ Swarm.Language.Value: [VInj] :: Bool -> Value -> Value
+ Swarm.Language.Value: [VInt] :: Integer -> Value
+ Swarm.Language.Value: [VPair] :: Value -> Value -> Value
+ Swarm.Language.Value: [VRef] :: Int -> Value
+ Swarm.Language.Value: [VResult] :: Value -> Env -> Value
+ Swarm.Language.Value: [VRobot] :: Int -> Value
+ Swarm.Language.Value: [VText] :: Text -> Value
+ Swarm.Language.Value: [VUnit] :: Value
+ Swarm.Language.Value: data Value
+ Swarm.Language.Value: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Language.Value.Value
+ Swarm.Language.Value: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Language.Value.Value
+ Swarm.Language.Value: instance GHC.Classes.Eq Swarm.Language.Value.Value
+ Swarm.Language.Value: instance GHC.Generics.Generic Swarm.Language.Value.Value
+ Swarm.Language.Value: instance GHC.Show.Show Swarm.Language.Value.Value
+ Swarm.Language.Value: prettyValue :: Value -> Text
+ Swarm.Language.Value: stripVResult :: Value -> Value
+ Swarm.Language.Value: type Env = Ctx Value
+ Swarm.Language.Value: valueToTerm :: Value -> Term
+ Swarm.TUI.Attr: lightCyanAttr :: AttrName
+ Swarm.TUI.Attr: magentaAttr :: AttrName
+ Swarm.TUI.Controller.Util: isRunningModal :: ModalType -> Bool
+ Swarm.TUI.Controller.Util: openModal :: ModalType -> EventM Name AppState ()
+ Swarm.TUI.Controller.Util: pattern CharKey :: Char -> BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern ControlChar :: Char -> BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern EscapeKey :: BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern FKey :: Int -> BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern Key :: Key -> BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern MetaChar :: Char -> BrickEvent n e
+ Swarm.TUI.Controller.Util: pattern ShiftKey :: Key -> BrickEvent n e
+ Swarm.TUI.Controller.Util: setFocus :: FocusablePanel -> EventM Name AppState ()
+ Swarm.TUI.Model: AchievementList :: Name
+ Swarm.TUI.Model: Achievements :: MainMenuEntry
+ Swarm.TUI.Model: AchievementsMenu :: List Name CategorizedAchievement -> Menu
+ Swarm.TUI.Model: AppState :: GameState -> UIState -> RuntimeState -> AppState
+ Swarm.TUI.Model: Button :: Button -> Name
+ Swarm.TUI.Model: Cancel :: ButtonAction
+ Swarm.TUI.Model: ColorMode16 :: ColorMode
+ Swarm.TUI.Model: ColorMode240 :: !Word8 -> ColorMode
+ Swarm.TUI.Model: ColorMode8 :: ColorMode
+ Swarm.TUI.Model: EquippedEntry :: Entity -> InventoryListEntry
+ Swarm.TUI.Model: FocusablePanel :: FocusablePanel -> Name
+ Swarm.TUI.Model: FullColor :: ColorMode
+ Swarm.TUI.Model: GoalWidgets :: GoalWidget -> Name
+ Swarm.TUI.Model: KeepPlaying :: ButtonAction
+ Swarm.TUI.Model: LoseModal :: ModalType
+ Swarm.TUI.Model: Next :: ScenarioInfoPair -> ButtonAction
+ Swarm.TUI.Model: NoColor :: ColorMode
+ Swarm.TUI.Model: Piloting :: ReplControlMode
+ Swarm.TUI.Model: QuitAction :: ButtonAction
+ Swarm.TUI.Model: StartOver :: Seed -> ScenarioInfoPair -> ButtonAction
+ Swarm.TUI.Model: Typing :: ReplControlMode
+ Swarm.TUI.Model: [colorMode] :: AppOpts -> Maybe ColorMode
+ Swarm.TUI.Model: _EquippedEntry :: Prism' InventoryListEntry Entity
+ Swarm.TUI.Model: data Button
+ Swarm.TUI.Model: data ButtonAction
+ Swarm.TUI.Model: data ColorMode
+ Swarm.TUI.Model: data FocusablePanel
+ Swarm.TUI.Model: data ReplControlMode
+ Swarm.TUI.Model: replControlMode :: Lens' REPLState ReplControlMode
+ Swarm.TUI.Model.Achievement.Attainment: Attainment :: CategorizedAchievement -> Maybe FilePath -> ZonedTime -> Attainment
+ Swarm.TUI.Model.Achievement.Attainment: [_achievement] :: Attainment -> CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Attainment: [_maybeScenarioPath] :: Attainment -> Maybe FilePath
+ Swarm.TUI.Model.Achievement.Attainment: [_obtainedAt] :: Attainment -> ZonedTime
+ Swarm.TUI.Model.Achievement.Attainment: achievement :: Lens' Attainment CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Attainment: achievementJsonOptions :: Options
+ Swarm.TUI.Model.Achievement.Attainment: data Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance GHC.Base.Semigroup Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance GHC.Classes.Ord Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Attainment.Attainment
+ Swarm.TUI.Model.Achievement.Attainment: maybeScenarioPath :: Lens' Attainment (Maybe FilePath)
+ Swarm.TUI.Model.Achievement.Attainment: obtainedAt :: Lens' Attainment ZonedTime
+ Swarm.TUI.Model.Achievement.Definitions: AchievementInfo :: Text -> Maybe FlavorText -> Text -> ExpectedEffort -> Bool -> AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: AttemptSelfDestructBase :: GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: CompletedAllTutorials :: GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: CompletedSingleTutorial :: GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: CraftedBitcoin :: GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: DestroyedBase :: GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: Easy :: ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: FTQuotation :: Quotation -> FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: Freeform :: Text -> FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: GameplayAchievement :: GameplayAchievement -> CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: GlobalAchievement :: GlobalAchievement -> CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: Gruelling :: ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: LookedAtAboutScreen :: GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: LoseScenario :: GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: Moderate :: ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: Quotation :: Text -> Text -> Quotation
+ Swarm.TUI.Model.Achievement.Definitions: RobotIntoWater :: GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: Trivial :: ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: [attainmentProcess] :: AchievementInfo -> Text
+ Swarm.TUI.Model.Achievement.Definitions: [attribution] :: Quotation -> Text
+ Swarm.TUI.Model.Achievement.Definitions: [content] :: Quotation -> Text
+ Swarm.TUI.Model.Achievement.Definitions: [effort] :: AchievementInfo -> ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: [humorousElaboration] :: AchievementInfo -> Maybe FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: [isObfuscated] :: AchievementInfo -> Bool
+ Swarm.TUI.Model.Achievement.Definitions: [title] :: AchievementInfo -> Text
+ Swarm.TUI.Model.Achievement.Definitions: categorizedAchievementJsonOptions :: Options
+ Swarm.TUI.Model.Achievement.Definitions: data AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: data CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: data ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: data FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: data GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: data GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: data Quotation
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.TUI.Model.Achievement.Definitions.Quotation
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.TUI.Model.Achievement.Definitions.Quotation
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Eq Swarm.TUI.Model.Achievement.Definitions.Quotation
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Ord Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Ord Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Ord Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Classes.Ord Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Bounded Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Bounded Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Bounded Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Enum Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Enum Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Enum.Enum Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Generics.Generic Swarm.TUI.Model.Achievement.Definitions.Quotation
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.AchievementInfo
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.CategorizedAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.ExpectedEffort
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.FlavorText
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.GameplayAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.GlobalAchievement
+ Swarm.TUI.Model.Achievement.Definitions: instance GHC.Show.Show Swarm.TUI.Model.Achievement.Definitions.Quotation
+ Swarm.TUI.Model.Achievement.Definitions: listAchievements :: [CategorizedAchievement]
+ Swarm.TUI.Model.Achievement.Description: describe :: CategorizedAchievement -> AchievementInfo
+ Swarm.TUI.Model.Achievement.Persistence: getSwarmAchievementsPath :: Bool -> IO FilePath
+ Swarm.TUI.Model.Achievement.Persistence: loadAchievementsInfo :: IO ([SystemFailure], [Attainment])
+ Swarm.TUI.Model.Achievement.Persistence: saveAchievementsInfo :: [Attainment] -> IO ()
+ Swarm.TUI.Model.Failure: Achievement :: Asset
+ Swarm.TUI.Model.Failure: AssetNotLoaded :: Asset -> FilePath -> LoadingFailure -> SystemFailure
+ Swarm.TUI.Model.Failure: CanNotParse :: ParseException -> LoadingFailure
+ Swarm.TUI.Model.Failure: Data :: Asset
+ Swarm.TUI.Model.Failure: Directory :: Entry
+ Swarm.TUI.Model.Failure: DoesNotExist :: Entry -> LoadingFailure
+ Swarm.TUI.Model.Failure: EntryNot :: Entry -> LoadingFailure
+ Swarm.TUI.Model.Failure: File :: Entry
+ Swarm.TUI.Model.Failure: History :: Asset
+ Swarm.TUI.Model.Failure: Save :: Asset
+ Swarm.TUI.Model.Failure: data Asset
+ Swarm.TUI.Model.Failure: data Entry
+ Swarm.TUI.Model.Failure: data LoadingFailure
+ Swarm.TUI.Model.Failure: data SystemFailure
+ Swarm.TUI.Model.Failure: instance GHC.Classes.Eq Swarm.TUI.Model.Failure.Asset
+ Swarm.TUI.Model.Failure: instance GHC.Classes.Eq Swarm.TUI.Model.Failure.Entry
+ Swarm.TUI.Model.Failure: instance GHC.Show.Show Swarm.TUI.Model.Failure.Asset
+ Swarm.TUI.Model.Failure: instance GHC.Show.Show Swarm.TUI.Model.Failure.Entry
+ Swarm.TUI.Model.Failure: prettyFailure :: SystemFailure -> Text
+ Swarm.TUI.Model.Failure: prettyLoadingFailure :: LoadingFailure -> Text
+ Swarm.TUI.Model.Failure: tShow :: Show a => a -> Text
+ Swarm.TUI.Model.Failure: tShowLow :: Show a => a -> Text
+ Swarm.TUI.Model.Menu: About :: MainMenuEntry
+ Swarm.TUI.Model.Menu: AboutMenu :: Menu
+ Swarm.TUI.Model.Menu: Achievements :: MainMenuEntry
+ Swarm.TUI.Model.Menu: AchievementsMenu :: List Name CategorizedAchievement -> Menu
+ Swarm.TUI.Model.Menu: Cancel :: ButtonAction
+ Swarm.TUI.Model.Menu: CommandsModal :: ModalType
+ Swarm.TUI.Model.Menu: DescriptionModal :: Entity -> ModalType
+ Swarm.TUI.Model.Menu: EquippedEntry :: Entity -> InventoryListEntry
+ Swarm.TUI.Model.Menu: GoalModal :: ModalType
+ Swarm.TUI.Model.Menu: HelpModal :: ModalType
+ Swarm.TUI.Model.Menu: InventoryEntry :: Count -> Entity -> InventoryListEntry
+ Swarm.TUI.Model.Menu: KeepPlaying :: ButtonAction
+ Swarm.TUI.Model.Menu: KeepPlayingModal :: ModalType
+ Swarm.TUI.Model.Menu: LoseModal :: ModalType
+ Swarm.TUI.Model.Menu: MainMenu :: List Name MainMenuEntry -> Menu
+ Swarm.TUI.Model.Menu: Messages :: MainMenuEntry
+ Swarm.TUI.Model.Menu: MessagesMenu :: Menu
+ Swarm.TUI.Model.Menu: MessagesModal :: ModalType
+ Swarm.TUI.Model.Menu: Modal :: ModalType -> Dialog ButtonAction Name -> Modal
+ Swarm.TUI.Model.Menu: NewGame :: MainMenuEntry
+ Swarm.TUI.Model.Menu: NewGameMenu :: NonEmpty (List Name ScenarioItem) -> Menu
+ Swarm.TUI.Model.Menu: Next :: ScenarioInfoPair -> ButtonAction
+ Swarm.TUI.Model.Menu: NoMenu :: Menu
+ Swarm.TUI.Model.Menu: Quit :: MainMenuEntry
+ Swarm.TUI.Model.Menu: QuitAction :: ButtonAction
+ Swarm.TUI.Model.Menu: QuitModal :: ModalType
+ Swarm.TUI.Model.Menu: RecipesModal :: ModalType
+ Swarm.TUI.Model.Menu: RobotsModal :: ModalType
+ Swarm.TUI.Model.Menu: Separator :: Text -> InventoryListEntry
+ Swarm.TUI.Model.Menu: StartOver :: Seed -> ScenarioInfoPair -> ButtonAction
+ Swarm.TUI.Model.Menu: Tutorial :: MainMenuEntry
+ Swarm.TUI.Model.Menu: WinModal :: ModalType
+ Swarm.TUI.Model.Menu: [_modalDialog] :: Modal -> Dialog ButtonAction Name
+ Swarm.TUI.Model.Menu: [_modalType] :: Modal -> ModalType
+ Swarm.TUI.Model.Menu: _AboutMenu :: Prism' Menu ()
+ Swarm.TUI.Model.Menu: _AchievementsMenu :: Prism' Menu (List Name CategorizedAchievement)
+ Swarm.TUI.Model.Menu: _EquippedEntry :: Prism' InventoryListEntry Entity
+ Swarm.TUI.Model.Menu: _InventoryEntry :: Prism' InventoryListEntry (Count, Entity)
+ Swarm.TUI.Model.Menu: _MainMenu :: Prism' Menu (List Name MainMenuEntry)
+ Swarm.TUI.Model.Menu: _MessagesMenu :: Prism' Menu ()
+ Swarm.TUI.Model.Menu: _NewGameMenu :: Prism' Menu (NonEmpty (List Name ScenarioItem))
+ Swarm.TUI.Model.Menu: _NoMenu :: Prism' Menu ()
+ Swarm.TUI.Model.Menu: _Separator :: Prism' InventoryListEntry Text
+ Swarm.TUI.Model.Menu: data ButtonAction
+ Swarm.TUI.Model.Menu: data InventoryListEntry
+ Swarm.TUI.Model.Menu: data MainMenuEntry
+ Swarm.TUI.Model.Menu: data Menu
+ Swarm.TUI.Model.Menu: data Modal
+ Swarm.TUI.Model.Menu: data ModalType
+ Swarm.TUI.Model.Menu: instance GHC.Classes.Eq Swarm.TUI.Model.Menu.InventoryListEntry
+ Swarm.TUI.Model.Menu: instance GHC.Classes.Eq Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Classes.Ord Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Enum.Bounded Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Enum.Enum Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Read.Read Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Show.Show Swarm.TUI.Model.Menu.MainMenuEntry
+ Swarm.TUI.Model.Menu: instance GHC.Show.Show Swarm.TUI.Model.Menu.ModalType
+ Swarm.TUI.Model.Menu: mainMenu :: MainMenuEntry -> List Name MainMenuEntry
+ Swarm.TUI.Model.Menu: mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu
+ Swarm.TUI.Model.Menu: mkScenarioList :: Bool -> ScenarioCollection -> List Name ScenarioItem
+ Swarm.TUI.Model.Menu: modalDialog :: Lens' Modal (Dialog ButtonAction Name)
+ Swarm.TUI.Model.Menu: modalType :: Lens' Modal ModalType
+ Swarm.TUI.Model.Name: AchievementList :: Name
+ Swarm.TUI.Model.Name: Button :: Button -> Name
+ Swarm.TUI.Model.Name: CancelButton :: Button
+ Swarm.TUI.Model.Name: FocusablePanel :: FocusablePanel -> Name
+ Swarm.TUI.Model.Name: GoalSummary :: GoalWidget
+ Swarm.TUI.Model.Name: GoalWidgets :: GoalWidget -> Name
+ Swarm.TUI.Model.Name: InfoPanel :: FocusablePanel
+ Swarm.TUI.Model.Name: InfoViewport :: Name
+ Swarm.TUI.Model.Name: InventoryList :: Name
+ Swarm.TUI.Model.Name: InventoryListItem :: Int -> Name
+ Swarm.TUI.Model.Name: KeepPlayingButton :: Button
+ Swarm.TUI.Model.Name: MenuList :: Name
+ Swarm.TUI.Model.Name: ModalViewport :: Name
+ Swarm.TUI.Model.Name: NextButton :: Button
+ Swarm.TUI.Model.Name: ObjectivesList :: GoalWidget
+ Swarm.TUI.Model.Name: QuitButton :: Button
+ Swarm.TUI.Model.Name: REPLInput :: Name
+ Swarm.TUI.Model.Name: REPLPanel :: FocusablePanel
+ Swarm.TUI.Model.Name: RobotPanel :: FocusablePanel
+ Swarm.TUI.Model.Name: ScenarioList :: Name
+ Swarm.TUI.Model.Name: StartOverButton :: Button
+ Swarm.TUI.Model.Name: WorldCache :: Name
+ Swarm.TUI.Model.Name: WorldExtent :: Name
+ Swarm.TUI.Model.Name: WorldPanel :: FocusablePanel
+ Swarm.TUI.Model.Name: data Button
+ Swarm.TUI.Model.Name: data FocusablePanel
+ Swarm.TUI.Model.Name: data GoalWidget
+ Swarm.TUI.Model.Name: data Name
+ Swarm.TUI.Model.Name: instance GHC.Classes.Eq Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Classes.Eq Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Classes.Eq Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Classes.Eq Swarm.TUI.Model.Name.Name
+ Swarm.TUI.Model.Name: instance GHC.Classes.Ord Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Classes.Ord Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Classes.Ord Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Classes.Ord Swarm.TUI.Model.Name.Name
+ Swarm.TUI.Model.Name: instance GHC.Enum.Bounded Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Enum.Bounded Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Enum.Bounded Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Enum.Enum Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Enum.Enum Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Enum.Enum Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Read.Read Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Read.Read Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Read.Read Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Read.Read Swarm.TUI.Model.Name.Name
+ Swarm.TUI.Model.Name: instance GHC.Show.Show Swarm.TUI.Model.Name.Button
+ Swarm.TUI.Model.Name: instance GHC.Show.Show Swarm.TUI.Model.Name.FocusablePanel
+ Swarm.TUI.Model.Name: instance GHC.Show.Show Swarm.TUI.Model.Name.GoalWidget
+ Swarm.TUI.Model.Name: instance GHC.Show.Show Swarm.TUI.Model.Name.Name
+ Swarm.TUI.Model.Repl: CmdPrompt :: [Text] -> REPLPrompt
+ Swarm.TUI.Model.Repl: Newer :: TimeDir
+ Swarm.TUI.Model.Repl: Older :: TimeDir
+ Swarm.TUI.Model.Repl: Piloting :: ReplControlMode
+ Swarm.TUI.Model.Repl: REPLEntry :: Text -> REPLHistItem
+ Swarm.TUI.Model.Repl: REPLOutput :: Text -> REPLHistItem
+ Swarm.TUI.Model.Repl: SearchPrompt :: REPLHistory -> REPLPrompt
+ Swarm.TUI.Model.Repl: Typing :: ReplControlMode
+ Swarm.TUI.Model.Repl: addREPLItem :: REPLHistItem -> REPLHistory -> REPLHistory
+ Swarm.TUI.Model.Repl: data REPLHistItem
+ Swarm.TUI.Model.Repl: data REPLHistory
+ Swarm.TUI.Model.Repl: data REPLPrompt
+ Swarm.TUI.Model.Repl: data REPLState
+ Swarm.TUI.Model.Repl: data ReplControlMode
+ Swarm.TUI.Model.Repl: data TimeDir
+ Swarm.TUI.Model.Repl: defaultPrompt :: REPLPrompt
+ Swarm.TUI.Model.Repl: getCurrentItemText :: REPLHistory -> Maybe Text
+ Swarm.TUI.Model.Repl: getLatestREPLHistoryItems :: Int -> REPLHistory -> [REPLHistItem]
+ Swarm.TUI.Model.Repl: getREPLEntry :: REPLHistItem -> Maybe Text
+ Swarm.TUI.Model.Repl: initREPLState :: REPLHistory -> REPLState
+ Swarm.TUI.Model.Repl: instance GHC.Classes.Eq Swarm.TUI.Model.Repl.REPLHistItem
+ Swarm.TUI.Model.Repl: instance GHC.Classes.Eq Swarm.TUI.Model.Repl.ReplControlMode
+ Swarm.TUI.Model.Repl: instance GHC.Classes.Eq Swarm.TUI.Model.Repl.TimeDir
+ Swarm.TUI.Model.Repl: instance GHC.Classes.Ord Swarm.TUI.Model.Repl.REPLHistItem
+ Swarm.TUI.Model.Repl: instance GHC.Classes.Ord Swarm.TUI.Model.Repl.TimeDir
+ Swarm.TUI.Model.Repl: instance GHC.Enum.Bounded Swarm.TUI.Model.Repl.ReplControlMode
+ Swarm.TUI.Model.Repl: instance GHC.Enum.Enum Swarm.TUI.Model.Repl.ReplControlMode
+ Swarm.TUI.Model.Repl: instance GHC.Read.Read Swarm.TUI.Model.Repl.REPLHistItem
+ Swarm.TUI.Model.Repl: instance GHC.Show.Show Swarm.TUI.Model.Repl.REPLHistItem
+ Swarm.TUI.Model.Repl: instance GHC.Show.Show Swarm.TUI.Model.Repl.TimeDir
+ Swarm.TUI.Model.Repl: isREPLEntry :: REPLHistItem -> Bool
+ Swarm.TUI.Model.Repl: lastEntry :: Text -> REPLHistory -> Maybe Text
+ Swarm.TUI.Model.Repl: moveReplHistIndex :: TimeDir -> Text -> REPLHistory -> REPLHistory
+ Swarm.TUI.Model.Repl: newREPLEditor :: Text -> Editor Text Name
+ Swarm.TUI.Model.Repl: newREPLHistory :: [REPLHistItem] -> REPLHistory
+ Swarm.TUI.Model.Repl: removeEntry :: Text -> REPLHistory -> REPLHistory
+ Swarm.TUI.Model.Repl: replControlMode :: Lens' REPLState ReplControlMode
+ Swarm.TUI.Model.Repl: replHistory :: Lens' REPLState REPLHistory
+ Swarm.TUI.Model.Repl: replIndex :: Lens' REPLHistory Int
+ Swarm.TUI.Model.Repl: replIndexIsAtInput :: REPLHistory -> Bool
+ Swarm.TUI.Model.Repl: replItemText :: REPLHistItem -> Text
+ Swarm.TUI.Model.Repl: replLast :: Lens' REPLState Text
+ Swarm.TUI.Model.Repl: replLength :: REPLHistory -> Int
+ Swarm.TUI.Model.Repl: replPromptEditor :: Lens' REPLState (Editor Text Name)
+ Swarm.TUI.Model.Repl: replPromptText :: Lens' REPLState Text
+ Swarm.TUI.Model.Repl: replPromptType :: Lens' REPLState REPLPrompt
+ Swarm.TUI.Model.Repl: replSeq :: Lens' REPLHistory (Seq REPLHistItem)
+ Swarm.TUI.Model.Repl: replType :: Lens' REPLState (Maybe Polytype)
+ Swarm.TUI.Model.Repl: replValid :: Lens' REPLState Bool
+ Swarm.TUI.Model.Repl: restartREPLHistory :: REPLHistory -> REPLHistory
+ Swarm.TUI.Model.StateUpdate: attainAchievement :: (MonadIO m, MonadState AppState m) => CategorizedAchievement -> m ()
+ Swarm.TUI.Model.StateUpdate: attainAchievement' :: (MonadIO m, MonadState AppState m) => ZonedTime -> Maybe FilePath -> CategorizedAchievement -> m ()
+ Swarm.TUI.Model.StateUpdate: initAppState :: AppOpts -> ExceptT Text IO AppState
+ Swarm.TUI.Model.StateUpdate: restartGame :: (MonadIO m, MonadState AppState m) => Seed -> ScenarioInfoPair -> m ()
+ Swarm.TUI.Model.StateUpdate: scenarioToAppState :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe Seed -> Maybe CodeToRun -> m ()
+ Swarm.TUI.Model.StateUpdate: startGame :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe CodeToRun -> m ()
+ Swarm.TUI.Model.UI: GoalDisplay :: GoalTracking -> List Name GoalEntry -> FocusRing Name -> GoalDisplay
+ Swarm.TUI.Model.UI: UIState :: Menu -> Bool -> Bool -> FocusRing Name -> Maybe Coords -> REPLState -> Maybe (Int, List Name InventoryListEntry) -> InventorySortOptions -> Bool -> Bool -> Bool -> Maybe Text -> Maybe Modal -> GoalDisplay -> Map CategorizedAchievement Attainment -> Bool -> Bool -> TimeSpec -> Bool -> Double -> Double -> Int -> Int -> Int -> Int -> TimeSpec -> TimeSpec -> TimeSpec -> Map Text Text -> AttrMap -> Maybe ScenarioInfoPair -> UIState
+ Swarm.TUI.Model.UI: [_accumulatedTime] :: UIState -> TimeSpec
+ Swarm.TUI.Model.UI: [_appData] :: UIState -> Map Text Text
+ Swarm.TUI.Model.UI: [_focus] :: GoalDisplay -> FocusRing Name
+ Swarm.TUI.Model.UI: [_frameCount] :: UIState -> Int
+ Swarm.TUI.Model.UI: [_frameTickCount] :: UIState -> Int
+ Swarm.TUI.Model.UI: [_goalsContent] :: GoalDisplay -> GoalTracking
+ Swarm.TUI.Model.UI: [_lastFrameTime] :: UIState -> TimeSpec
+ Swarm.TUI.Model.UI: [_lastInfoTime] :: UIState -> TimeSpec
+ Swarm.TUI.Model.UI: [_lgTicksPerSecond] :: UIState -> Int
+ Swarm.TUI.Model.UI: [_listWidget] :: GoalDisplay -> List Name GoalEntry
+ Swarm.TUI.Model.UI: [_scenarioRef] :: UIState -> Maybe ScenarioInfoPair
+ Swarm.TUI.Model.UI: [_tickCount] :: UIState -> Int
+ Swarm.TUI.Model.UI: [_uiAchievements] :: UIState -> Map CategorizedAchievement Attainment
+ Swarm.TUI.Model.UI: [_uiAttrMap] :: UIState -> AttrMap
+ Swarm.TUI.Model.UI: [_uiCheatMode] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiError] :: UIState -> Maybe Text
+ Swarm.TUI.Model.UI: [_uiFPS] :: UIState -> Double
+ Swarm.TUI.Model.UI: [_uiFocusRing] :: UIState -> FocusRing Name
+ Swarm.TUI.Model.UI: [_uiGoal] :: UIState -> GoalDisplay
+ Swarm.TUI.Model.UI: [_uiHideRobotsUntil] :: UIState -> TimeSpec
+ Swarm.TUI.Model.UI: [_uiInventoryShouldUpdate] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiInventorySort] :: UIState -> InventorySortOptions
+ Swarm.TUI.Model.UI: [_uiInventory] :: UIState -> Maybe (Int, List Name InventoryListEntry)
+ Swarm.TUI.Model.UI: [_uiMenu] :: UIState -> Menu
+ Swarm.TUI.Model.UI: [_uiModal] :: UIState -> Maybe Modal
+ Swarm.TUI.Model.UI: [_uiMoreInfoBot] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiMoreInfoTop] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiPlaying] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiREPL] :: UIState -> REPLState
+ Swarm.TUI.Model.UI: [_uiScrollToEnd] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiShowFPS] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiShowZero] :: UIState -> Bool
+ Swarm.TUI.Model.UI: [_uiTPF] :: UIState -> Double
+ Swarm.TUI.Model.UI: [_uiWorldCursor] :: UIState -> Maybe Coords
+ Swarm.TUI.Model.UI: accumulatedTime :: Lens' UIState TimeSpec
+ Swarm.TUI.Model.UI: appData :: Lens' UIState (Map Text Text)
+ Swarm.TUI.Model.UI: data GoalDisplay
+ Swarm.TUI.Model.UI: data UIState
+ Swarm.TUI.Model.UI: frameCount :: Lens' UIState Int
+ Swarm.TUI.Model.UI: frameTickCount :: Lens' UIState Int
+ Swarm.TUI.Model.UI: initFocusRing :: FocusRing Name
+ Swarm.TUI.Model.UI: initLgTicksPerSecond :: Int
+ Swarm.TUI.Model.UI: initUIState :: Bool -> Bool -> ExceptT Text IO ([SystemFailure], UIState)
+ Swarm.TUI.Model.UI: lastFrameTime :: Lens' UIState TimeSpec
+ Swarm.TUI.Model.UI: lastInfoTime :: Lens' UIState TimeSpec
+ Swarm.TUI.Model.UI: lgTicksPerSecond :: Lens' UIState Int
+ Swarm.TUI.Model.UI: scenarioRef :: Lens' UIState (Maybe ScenarioInfoPair)
+ Swarm.TUI.Model.UI: tickCount :: Lens' UIState Int
+ Swarm.TUI.Model.UI: uiAchievements :: Lens' UIState (Map CategorizedAchievement Attainment)
+ Swarm.TUI.Model.UI: uiAttrMap :: Lens' UIState AttrMap
+ Swarm.TUI.Model.UI: uiCheatMode :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiError :: Lens' UIState (Maybe Text)
+ Swarm.TUI.Model.UI: uiFPS :: Lens' UIState Double
+ Swarm.TUI.Model.UI: uiFocusRing :: Lens' UIState (FocusRing Name)
+ Swarm.TUI.Model.UI: uiGoal :: Lens' UIState GoalDisplay
+ Swarm.TUI.Model.UI: uiHideRobotsUntil :: Lens' UIState TimeSpec
+ Swarm.TUI.Model.UI: uiInventory :: Lens' UIState (Maybe (Int, List Name InventoryListEntry))
+ Swarm.TUI.Model.UI: uiInventoryShouldUpdate :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiInventorySort :: Lens' UIState InventorySortOptions
+ Swarm.TUI.Model.UI: uiMenu :: Lens' UIState Menu
+ Swarm.TUI.Model.UI: uiModal :: Lens' UIState (Maybe Modal)
+ Swarm.TUI.Model.UI: uiMoreInfoBot :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiMoreInfoTop :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiPlaying :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiREPL :: Lens' UIState REPLState
+ Swarm.TUI.Model.UI: uiScrollToEnd :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiShowFPS :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiShowRobots :: Getter UIState Bool
+ Swarm.TUI.Model.UI: uiShowZero :: Lens' UIState Bool
+ Swarm.TUI.Model.UI: uiTPF :: Lens' UIState Double
+ Swarm.TUI.Model.UI: uiWorldCursor :: Lens' UIState (Maybe Coords)
+ Swarm.TUI.View.Achievement: drawAchievementListItem :: Map CategorizedAchievement Attainment -> CategorizedAchievement -> Widget Name
+ Swarm.TUI.View.Achievement: drawAchievementsMenuUI :: AppState -> List Name CategorizedAchievement -> Widget Name
+ Swarm.TUI.View.Achievement: getCompletionIcon :: Bool -> Widget Name
+ Swarm.TUI.View.Achievement: padAllEvenly :: Int -> Widget Name -> Widget Name
+ Swarm.TUI.View.Achievement: singleAchievementDetails :: Map CategorizedAchievement Attainment -> CategorizedAchievement -> Widget Name
+ Swarm.TUI.View.CellDisplay: HideAllEntities :: HideEntity
+ Swarm.TUI.View.CellDisplay: HideEntityUnknownTo :: Robot -> HideEntity
+ Swarm.TUI.View.CellDisplay: HideNoEntity :: HideEntity
+ Swarm.TUI.View.CellDisplay: data HideEntity
+ Swarm.TUI.View.CellDisplay: displayEntityCell :: GameState -> Coords -> [Display]
+ Swarm.TUI.View.CellDisplay: displayLoc :: Bool -> GameState -> Coords -> Display
+ Swarm.TUI.View.CellDisplay: displayRobotCell :: GameState -> Coords -> [Display]
+ Swarm.TUI.View.CellDisplay: displayTerrainCell :: GameState -> Coords -> Display
+ Swarm.TUI.View.CellDisplay: drawLoc :: Bool -> GameState -> Coords -> Widget Name
+ Swarm.TUI.View.CellDisplay: hidingMode :: GameState -> HideEntity
+ Swarm.TUI.View.CustomStyling: toAttrColor :: HexColor -> Color
+ Swarm.TUI.View.CustomStyling: toAttrPair :: CustomAttr -> (AttrName, Attr)
+ Swarm.TUI.View.CustomStyling: toStyle :: StyleFlag -> Style
+ Swarm.TUI.View.Util: curMenuName :: AppState -> Maybe Text
+ Swarm.TUI.View.Util: descriptionTitle :: Entity -> String
+ Swarm.TUI.View.Util: displayParagraphs :: [Text] -> Widget Name
+ Swarm.TUI.View.Util: drawType :: Polytype -> Widget Name
+ Swarm.TUI.View.Util: generateModal :: AppState -> ModalType -> Modal
+ Swarm.TUI.View.Util: maxModalWindowWidth :: Int
+ Swarm.TUI.View.Util: maybeScroll :: (Ord n, Show n) => n -> Widget n -> Widget n
+ Swarm.TUI.View.Util: quitMsg :: Menu -> Text
+ Swarm.TUI.View.Util: withEllipsis :: Text -> Widget Name
+ Swarm.Util: binTuples :: (Foldable t, Ord a) => t (a, b) -> Map a (NonEmpty b)
+ Swarm.Util: bquote :: Text -> Text
+ Swarm.Util: getSwarmXdgDataFile :: Bool -> FilePath -> IO FilePath
+ Swarm.Util: getSwarmXdgDataSubdir :: Bool -> FilePath -> IO FilePath
+ Swarm.Util: listEnums :: (Enum e, Bounded e) => [e]
+ Swarm.Util.Location: (.+^) :: (Affine p, Num a) => p a -> Diff p a -> p a
+ Swarm.Util.Location: (.-.) :: (Affine p, Num a) => p a -> p a -> Diff p a
+ Swarm.Util.Location: (.-^) :: (Affine p, Num a) => p a -> Diff p a -> p a
+ Swarm.Util.Location: class Additive Diff p => Affine (p :: Type -> Type) where {
+ Swarm.Util.Location: infixl 6 .-^
+ Swarm.Util.Location: instance Data.Aeson.Types.FromJSON.FromJSON (Linear.V2.V2 GHC.Int.Int32)
+ Swarm.Util.Location: instance Data.Aeson.Types.FromJSON.FromJSON Swarm.Util.Location.Location
+ Swarm.Util.Location: instance Data.Aeson.Types.FromJSON.FromJSONKey (Linear.V2.V2 GHC.Int.Int32)
+ Swarm.Util.Location: instance Data.Aeson.Types.ToJSON.ToJSON (Linear.V2.V2 GHC.Int.Int32)
+ Swarm.Util.Location: instance Data.Aeson.Types.ToJSON.ToJSON Swarm.Util.Location.Location
+ Swarm.Util.Location: instance Data.Aeson.Types.ToJSON.ToJSONKey (Linear.V2.V2 GHC.Int.Int32)
+ Swarm.Util.Location: origin :: forall (f :: Type -> Type) a. (Additive f, Num a) => Point f a
+ Swarm.Util.Location: pattern Location :: Int32 -> Int32 -> Location
+ Swarm.Util.Location: type Heading = V2 Int32
+ Swarm.Util.Location: type Location = Point V2 Int32
+ Swarm.Util.Location: type family Diff (p :: Type -> Type) :: Type -> Type;
+ Swarm.Util.Location: }
- Swarm.Game.CESK: FImmediate :: WorldUpdate -> RobotUpdate -> Frame
+ Swarm.Game.CESK: FImmediate :: Const -> [WorldUpdate Entity] -> [RobotUpdate] -> Frame
- Swarm.Game.CESK: FUpdate :: Loc -> Frame
+ Swarm.Game.CESK: FUpdate :: Addr -> Frame
- Swarm.Game.CESK: allocate :: Env -> Term -> Store -> (Loc, Store)
+ Swarm.Game.CESK: allocate :: Env -> Term -> Store -> (Addr, Store)
- Swarm.Game.CESK: lookupCell :: Loc -> Store -> Maybe Cell
+ Swarm.Game.CESK: lookupCell :: Addr -> Store -> Maybe Cell
- Swarm.Game.CESK: setCell :: Loc -> Cell -> Store -> Store
+ Swarm.Game.CESK: setCell :: Addr -> Cell -> Store -> Store
- Swarm.Game.Display: orientationMap :: Lens' Display (Map Direction Char)
+ Swarm.Game.Display: orientationMap :: Lens' Display (Map AbsoluteDir Char)
- Swarm.Game.Entity: entityCapabilities :: Lens' Entity [Capability]
+ Swarm.Game.Entity: entityCapabilities :: Lens' Entity (Set Capability)
- Swarm.Game.Entity: entityOrientation :: Lens' Entity (Maybe (V2 Int64))
+ Swarm.Game.Entity: entityOrientation :: Lens' Entity (Maybe Heading)
- Swarm.Game.Exception: CmdFailed :: Const -> Text -> Exn
+ Swarm.Game.Exception: CmdFailed :: Const -> Text -> Maybe GameplayAchievement -> Exn
- Swarm.Game.Recipe: make :: (Inventory, Inventory) -> Recipe Entity -> Either [MissingIngredient] (Inventory, Inventory -> Inventory, Recipe Entity)
+ Swarm.Game.Recipe: make :: (Inventory, Inventory) -> Recipe Entity -> Either [MissingIngredient] (Inventory, IngredientList Entity, Recipe Entity)
- Swarm.Game.Robot: mkRobot :: RobotID phase -> Maybe Int -> Text -> [Text] -> RobotLocation phase -> V2 Int64 -> Display -> CESK -> [Entity] -> [(Count, Entity)] -> Bool -> Bool -> TimeSpec -> RobotR phase
+ Swarm.Game.Robot: mkRobot :: RobotID phase -> Maybe Int -> Text -> [Text] -> RobotLocation phase -> Heading -> Display -> CESK -> [Entity] -> [(Count, Entity)] -> Bool -> Bool -> TimeSpec -> RobotR phase
- Swarm.Game.Robot: robotLocation :: Getter Robot (V2 Int64)
+ Swarm.Game.Robot: robotLocation :: Getter Robot Location
- Swarm.Game.Robot: robotOrientation :: Lens' Robot (Maybe (V2 Int64))
+ Swarm.Game.Robot: robotOrientation :: Lens' Robot (Maybe Heading)
- Swarm.Game.Robot: trobotLocation :: Lens' TRobot (Maybe (V2 Int64))
+ Swarm.Game.Robot: trobotLocation :: Lens' TRobot (Maybe Location)
- Swarm.Game.Robot: unsafeSetRobotLocation :: V2 Int64 -> Robot -> Robot
+ Swarm.Game.Robot: unsafeSetRobotLocation :: Location -> Robot -> Robot
- Swarm.Game.Scenario: Cell :: TerrainType -> Maybe Entity -> [IndexedTRobot] -> Cell
+ Swarm.Game.Scenario: Cell :: TerrainType -> Maybe e -> [IndexedTRobot] -> PCell e
- Swarm.Game.Scenario: WorldDescription :: Maybe Cell -> Bool -> WorldPalette -> V2 Int64 -> [[Cell]] -> WorldDescription
+ Swarm.Game.Scenario: WorldDescription :: Maybe (PCell e) -> Bool -> WorldPalette e -> Location -> [[PCell e]] -> PWorldDescription e
- Swarm.Game.Scenario: [area] :: WorldDescription -> [[Cell]]
+ Swarm.Game.Scenario: [area] :: PWorldDescription e -> [[PCell e]]
- Swarm.Game.Scenario: [cellEntity] :: Cell -> Maybe Entity
+ Swarm.Game.Scenario: [cellEntity] :: PCell e -> Maybe e
- Swarm.Game.Scenario: [cellRobots] :: Cell -> [IndexedTRobot]
+ Swarm.Game.Scenario: [cellRobots] :: PCell e -> [IndexedTRobot]
- Swarm.Game.Scenario: [cellTerrain] :: Cell -> TerrainType
+ Swarm.Game.Scenario: [cellTerrain] :: PCell e -> TerrainType
- Swarm.Game.Scenario: [defaultTerrain] :: WorldDescription -> Maybe Cell
+ Swarm.Game.Scenario: [defaultTerrain] :: PWorldDescription e -> Maybe (PCell e)
- Swarm.Game.Scenario: [offsetOrigin] :: WorldDescription -> Bool
+ Swarm.Game.Scenario: [offsetOrigin] :: PWorldDescription e -> Bool
- Swarm.Game.Scenario: [palette] :: WorldDescription -> WorldPalette
+ Swarm.Game.Scenario: [palette] :: PWorldDescription e -> WorldPalette e
- Swarm.Game.Scenario: [ul] :: WorldDescription -> V2 Int64
+ Swarm.Game.Scenario: [ul] :: PWorldDescription e -> Location
- Swarm.Game.State: VCLocation :: V2 Int64 -> ViewCenterRule
+ Swarm.Game.State: VCLocation :: Location -> ViewCenterRule
- Swarm.Game.State: WinConditions :: NonEmpty Objective -> WinCondition
+ Swarm.Game.State: WinConditions :: WinStatus -> ObjectiveCompletion -> WinCondition
- Swarm.Game.State: Won :: Bool -> WinCondition
+ Swarm.Game.State: Won :: Bool -> WinStatus
- Swarm.Game.State: _WinConditions :: Prism' WinCondition (NonEmpty Objective)
+ Swarm.Game.State: _WinConditions :: Prism' WinCondition (WinStatus, ObjectiveCompletion)
- Swarm.Game.State: applyViewCenterRule :: ViewCenterRule -> IntMap Robot -> Maybe (V2 Int64)
+ Swarm.Game.State: applyViewCenterRule :: ViewCenterRule -> IntMap Robot -> Maybe Location
- Swarm.Game.State: messageIsFromNearby :: V2 Int64 -> LogEntry -> Bool
+ Swarm.Game.State: messageIsFromNearby :: Location -> LogEntry -> Bool
- Swarm.Game.State: modifyViewCenter :: (V2 Int64 -> V2 Int64) -> GameState -> GameState
+ Swarm.Game.State: modifyViewCenter :: (Location -> Location) -> GameState -> GameState
- Swarm.Game.State: notificationsContent :: forall a_a4l8d a_a4lBr. Lens (Notifications a_a4l8d) (Notifications a_a4lBr) [a_a4l8d] [a_a4lBr]
+ Swarm.Game.State: notificationsContent :: forall a_a5PKt a_a5QcT. Lens (Notifications a_a5PKt) (Notifications a_a5QcT) [a_a5PKt] [a_a5QcT]
- Swarm.Game.State: notificationsCount :: forall a_a4l8d. Lens' (Notifications a_a4l8d) Int
+ Swarm.Game.State: notificationsCount :: forall a_a5PKt. Lens' (Notifications a_a5PKt) Int
- Swarm.Game.State: robotsAtLocation :: V2 Int64 -> GameState -> [Robot]
+ Swarm.Game.State: robotsAtLocation :: Location -> GameState -> [Robot]
- Swarm.Game.State: robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)
+ Swarm.Game.State: robotsByLocation :: Lens' GameState (Map Location IntSet)
- Swarm.Game.State: robotsInArea :: V2 Int64 -> Int64 -> GameState -> [Robot]
+ Swarm.Game.State: robotsInArea :: Location -> Int32 -> GameState -> [Robot]
- Swarm.Game.State: viewCenter :: Getter GameState (V2 Int64)
+ Swarm.Game.State: viewCenter :: Getter GameState Location
- Swarm.Game.State: viewingRegion :: GameState -> (Int64, Int64) -> (Coords, Coords)
+ Swarm.Game.State: viewingRegion :: GameState -> (Int32, Int32) -> (Coords, Coords)
- Swarm.Game.Step: addSeedBot :: Has (State GameState) sig m => Entity -> (Integer, Integer) -> V2 Int64 -> TimeSpec -> m ()
+ Swarm.Game.Step: addSeedBot :: Has (State GameState) sig m => Entity -> (Integer, Integer) -> Location -> TimeSpec -> m ()
- Swarm.Game.Step: entityAt :: Has (State GameState) sig m => V2 Int64 -> m (Maybe Entity)
+ Swarm.Game.Step: entityAt :: Has (State GameState) sig m => Location -> m (Maybe Entity)
- Swarm.Game.Step: updateEntityAt :: Has (State GameState) sig m => V2 Int64 -> (Maybe Entity -> Maybe Entity) -> m ()
+ Swarm.Game.Step: updateEntityAt :: Has (State GameState) sig m => Location -> (Maybe Entity -> Maybe Entity) -> m ()
- Swarm.Game.Step: updateRobotLocation :: HasRobotStepState sig m => V2 Int64 -> V2 Int64 -> m ()
+ Swarm.Game.Step: updateRobotLocation :: HasRobotStepState sig m => Location -> Location -> m ()
- Swarm.Game.World: Coords :: (Int64, Int64) -> Coords
+ Swarm.Game.World: Coords :: (Int32, Int32) -> Coords
- Swarm.Game.World: [unCoords] :: Coords -> (Int64, Int64)
+ Swarm.Game.World: [unCoords] :: Coords -> (Int32, Int32)
- Swarm.Game.World: coordsToLoc :: Coords -> V2 Int64
+ Swarm.Game.World: coordsToLoc :: Coords -> Location
- Swarm.Game.World: locToCoords :: V2 Int64 -> Coords
+ Swarm.Game.World: locToCoords :: Location -> Coords
- Swarm.Game.World: worldFunFromArray :: Array (Int64, Int64) (t, Maybe e) -> (t, Maybe e) -> WorldFun t e
+ Swarm.Game.World: worldFunFromArray :: Array (Int32, Int32) (t, Maybe e) -> (t, Maybe e) -> WorldFun t e
- Swarm.Game.WorldGen: testWorld2FromArray :: EntityMap -> Array (Int64, Int64) (TerrainType, Maybe Entity) -> Seed -> WorldFun TerrainType Entity
+ Swarm.Game.WorldGen: testWorld2FromArray :: EntityMap -> Array (Int32, Int32) (TerrainType, Maybe Entity) -> Seed -> WorldFun TerrainType Entity
- Swarm.Language.Elaborate: elaborate :: Term -> Term
+ Swarm.Language.Elaborate: elaborate :: Syntax' Polytype -> Syntax' Polytype
- Swarm.Language.Elaborate: wrapForce :: Var -> Term -> Term
+ Swarm.Language.Elaborate: wrapForce :: Var -> Syntax' Polytype -> Syntax' Polytype
- Swarm.Language.Pipeline: ProcessedTerm :: Term -> TModule -> Requirements -> ReqCtx -> ProcessedTerm
+ Swarm.Language.Pipeline: ProcessedTerm :: TModule -> Requirements -> ReqCtx -> ProcessedTerm
- Swarm.Language.Syntax: DBack :: Direction
+ Swarm.Language.Syntax: DBack :: RelativeDir
- Swarm.Language.Syntax: DDown :: Direction
+ Swarm.Language.Syntax: DDown :: RelativeDir
- Swarm.Language.Syntax: DEast :: Direction
+ Swarm.Language.Syntax: DEast :: AbsoluteDir
- Swarm.Language.Syntax: DForward :: Direction
+ Swarm.Language.Syntax: DForward :: RelativeDir
- Swarm.Language.Syntax: DLeft :: Direction
+ Swarm.Language.Syntax: DLeft :: RelativeDir
- Swarm.Language.Syntax: DNorth :: Direction
+ Swarm.Language.Syntax: DNorth :: AbsoluteDir
- Swarm.Language.Syntax: DRight :: Direction
+ Swarm.Language.Syntax: DRight :: RelativeDir
- Swarm.Language.Syntax: DSouth :: Direction
+ Swarm.Language.Syntax: DSouth :: AbsoluteDir
- Swarm.Language.Syntax: DWest :: Direction
+ Swarm.Language.Syntax: DWest :: AbsoluteDir
- Swarm.Language.Syntax: DirInfo :: Text -> Maybe (V2 Int64) -> (V2 Int64 -> V2 Int64) -> DirInfo
+ Swarm.Language.Syntax: DirInfo :: Text -> (Heading -> Heading) -> DirInfo
- Swarm.Language.Syntax: NoLoc :: Location
+ Swarm.Language.Syntax: NoLoc :: SrcLoc
- Swarm.Language.Syntax: SApp :: Syntax -> Syntax -> Term
+ Swarm.Language.Syntax: SApp :: Syntax' ty -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SBind :: Maybe Var -> Syntax -> Syntax -> Term
+ Swarm.Language.Syntax: SBind :: Maybe LocVar -> Syntax' ty -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SDef :: Bool -> Var -> Maybe Polytype -> Syntax -> Term
+ Swarm.Language.Syntax: SDef :: Bool -> LocVar -> Maybe Polytype -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SDelay :: DelayType -> Syntax -> Term
+ Swarm.Language.Syntax: SDelay :: DelayType -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SLam :: Var -> Maybe Type -> Syntax -> Term
+ Swarm.Language.Syntax: SLam :: LocVar -> Maybe Type -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SLet :: Bool -> Var -> Maybe Polytype -> Syntax -> Syntax -> Term
+ Swarm.Language.Syntax: SLet :: Bool -> LocVar -> Maybe Polytype -> Syntax' ty -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: SPair :: Syntax -> Syntax -> Term
+ Swarm.Language.Syntax: SPair :: Syntax' ty -> Syntax' ty -> Term' ty
- Swarm.Language.Syntax: TAntiInt :: Text -> Term
+ Swarm.Language.Syntax: TAntiInt :: Text -> Term' ty
- Swarm.Language.Syntax: TAntiText :: Text -> Term
+ Swarm.Language.Syntax: TAntiText :: Text -> Term' ty
- Swarm.Language.Syntax: TBool :: Bool -> Term
+ Swarm.Language.Syntax: TBool :: Bool -> Term' ty
- Swarm.Language.Syntax: TConst :: Const -> Term
+ Swarm.Language.Syntax: TConst :: Const -> Term' ty
- Swarm.Language.Syntax: TDir :: Direction -> Term
+ Swarm.Language.Syntax: TDir :: Direction -> Term' ty
- Swarm.Language.Syntax: TInt :: Integer -> Term
+ Swarm.Language.Syntax: TInt :: Integer -> Term' ty
- Swarm.Language.Syntax: TRef :: Int -> Term
+ Swarm.Language.Syntax: TRef :: Int -> Term' ty
- Swarm.Language.Syntax: TRequire :: Int -> Text -> Term
+ Swarm.Language.Syntax: TRequire :: Int -> Text -> Term' ty
- Swarm.Language.Syntax: TRequireDevice :: Text -> Term
+ Swarm.Language.Syntax: TRequireDevice :: Text -> Term' ty
- Swarm.Language.Syntax: TRobot :: Int -> Term
+ Swarm.Language.Syntax: TRobot :: Int -> Term' ty
- Swarm.Language.Syntax: TText :: Text -> Term
+ Swarm.Language.Syntax: TText :: Text -> Term' ty
- Swarm.Language.Syntax: TUnit :: Term
+ Swarm.Language.Syntax: TUnit :: Term' ty
- Swarm.Language.Syntax: TVar :: Var -> Term
+ Swarm.Language.Syntax: TVar :: Var -> Term' ty
- Swarm.Language.Syntax: [dirApplyTurn] :: DirInfo -> V2 Int64 -> V2 Int64
+ Swarm.Language.Syntax: [dirApplyTurn] :: DirInfo -> Heading -> Heading
- Swarm.Language.Syntax: applyTurn :: Direction -> V2 Int64 -> V2 Int64
+ Swarm.Language.Syntax: applyTurn :: Direction -> Heading -> Heading
- Swarm.Language.Syntax: east :: V2 Int64
+ Swarm.Language.Syntax: east :: Heading
- Swarm.Language.Syntax: fromDirection :: Direction -> V2 Int64
+ Swarm.Language.Syntax: fromDirection :: Direction -> Heading
- Swarm.Language.Syntax: north :: V2 Int64
+ Swarm.Language.Syntax: north :: Heading
- Swarm.Language.Syntax: south :: V2 Int64
+ Swarm.Language.Syntax: south :: Heading
- Swarm.Language.Syntax: toDirection :: V2 Int64 -> Maybe Direction
+ Swarm.Language.Syntax: toDirection :: Heading -> Maybe Direction
- Swarm.Language.Syntax: west :: V2 Int64
+ Swarm.Language.Syntax: west :: Heading
- Swarm.Language.Typecheck: (=:=) :: UType -> UType -> Infer ()
+ Swarm.Language.Typecheck: (=:=) :: UType -> UType -> Infer UType
- Swarm.Language.Typecheck: CantInfer :: Location -> Term -> TypeErr
+ Swarm.Language.Typecheck: CantInfer :: SrcLoc -> Term -> TypeErr
- Swarm.Language.Typecheck: DefNotTopLevel :: Location -> Term -> TypeErr
+ Swarm.Language.Typecheck: DefNotTopLevel :: SrcLoc -> Term -> TypeErr
- Swarm.Language.Typecheck: EscapedSkolem :: Location -> Var -> TypeErr
+ Swarm.Language.Typecheck: EscapedSkolem :: SrcLoc -> Var -> TypeErr
- Swarm.Language.Typecheck: InvalidAtomic :: Location -> InvalidAtomicReason -> Term -> TypeErr
+ Swarm.Language.Typecheck: InvalidAtomic :: SrcLoc -> InvalidAtomicReason -> Term -> TypeErr
- Swarm.Language.Typecheck: Mismatch :: Location -> TypeF UType -> TypeF UType -> TypeErr
+ Swarm.Language.Typecheck: Mismatch :: SrcLoc -> TypeF UType -> TypeF UType -> TypeErr
- Swarm.Language.Typecheck: UnboundVar :: Location -> Var -> TypeErr
+ Swarm.Language.Typecheck: UnboundVar :: SrcLoc -> Var -> TypeErr
- Swarm.Language.Typecheck: check :: Syntax -> UType -> Infer ()
+ Swarm.Language.Typecheck: check :: Syntax -> UType -> Infer (Syntax' UType)
- Swarm.Language.Typecheck: infer :: Syntax -> Infer UType
+ Swarm.Language.Typecheck: infer :: Syntax -> Infer (Syntax' UType)
- Swarm.Language.Typecheck: lookup :: Location -> Var -> Infer UType
+ Swarm.Language.Typecheck: lookup :: SrcLoc -> Var -> Infer UType
- Swarm.Language.Typed: polytype :: forall v_a295J. Lens' (Typed v_a295J) Polytype
+ Swarm.Language.Typed: polytype :: forall v_a3EKy. Lens' (Typed v_a3EKy) Polytype
- Swarm.Language.Typed: requires :: forall v_a295J. Lens' (Typed v_a295J) Requirements
+ Swarm.Language.Typed: requires :: forall v_a3EKy. Lens' (Typed v_a3EKy) Requirements
- Swarm.Language.Typed: value :: forall v_a295J v_a29lp. Lens (Typed v_a295J) (Typed v_a29lp) v_a295J v_a29lp
+ Swarm.Language.Typed: value :: forall v_a3EKy v_a3F1s. Lens (Typed v_a3EKy) (Typed v_a3F1s) v_a3EKy v_a3F1s
- Swarm.TUI.Border: bottomLabels :: forall n_a2k2A. Lens' (BorderLabels n_a2k2A) (HBorderLabels n_a2k2A)
+ Swarm.TUI.Border: bottomLabels :: forall n_a111R. Lens' (BorderLabels n_a111R) (HBorderLabels n_a111R)
- Swarm.TUI.Border: centerLabel :: forall n_a2k2B. Lens' (HBorderLabels n_a2k2B) (Maybe (Widget n_a2k2B))
+ Swarm.TUI.Border: centerLabel :: forall n_a111S. Lens' (HBorderLabels n_a111S) (Maybe (Widget n_a111S))
- Swarm.TUI.Border: leftLabel :: forall n_a2k2B. Lens' (HBorderLabels n_a2k2B) (Maybe (Widget n_a2k2B))
+ Swarm.TUI.Border: leftLabel :: forall n_a111S. Lens' (HBorderLabels n_a111S) (Maybe (Widget n_a111S))
- Swarm.TUI.Border: rightLabel :: forall n_a2k2B. Lens' (HBorderLabels n_a2k2B) (Maybe (Widget n_a2k2B))
+ Swarm.TUI.Border: rightLabel :: forall n_a111S. Lens' (HBorderLabels n_a111S) (Maybe (Widget n_a111S))
- Swarm.TUI.Border: topLabels :: forall n_a2k2A. Lens' (BorderLabels n_a2k2A) (HBorderLabels n_a2k2A)
+ Swarm.TUI.Border: topLabels :: forall n_a111R. Lens' (BorderLabels n_a111R) (HBorderLabels n_a111R)
- Swarm.TUI.Controller: keyToDir :: Key -> V2 Int64
+ Swarm.TUI.Controller: keyToDir :: Key -> Heading
- Swarm.TUI.Controller: scrollView :: (V2 Int64 -> V2 Int64) -> EventM Name AppState ()
+ Swarm.TUI.Controller: scrollView :: (Location -> Location) -> EventM Name AppState ()
- Swarm.TUI.Model: AppOpts :: Maybe Seed -> Maybe FilePath -> Maybe FilePath -> Bool -> Bool -> Maybe Port -> Maybe GitInfo -> AppOpts
+ Swarm.TUI.Model: AppOpts :: Maybe Seed -> Maybe FilePath -> Maybe FilePath -> Bool -> Bool -> Maybe ColorMode -> Maybe Port -> Maybe GitInfo -> AppOpts
- Swarm.TUI.Model: CancelButton :: ButtonSelection
+ Swarm.TUI.Model: CancelButton :: Button
- Swarm.TUI.Model: GoalModal :: [Text] -> ModalType
+ Swarm.TUI.Model: GoalModal :: ModalType
- Swarm.TUI.Model: InfoPanel :: Name
+ Swarm.TUI.Model: InfoPanel :: FocusablePanel
- Swarm.TUI.Model: KeepPlayingButton :: ButtonSelection
+ Swarm.TUI.Model: KeepPlayingButton :: Button
- Swarm.TUI.Model: Modal :: ModalType -> Dialog ButtonSelection -> Modal
+ Swarm.TUI.Model: Modal :: ModalType -> Dialog ButtonAction Name -> Modal
- Swarm.TUI.Model: NextButton :: ScenarioInfoPair -> ButtonSelection
+ Swarm.TUI.Model: NextButton :: Button
- Swarm.TUI.Model: QuitButton :: ButtonSelection
+ Swarm.TUI.Model: QuitButton :: Button
- Swarm.TUI.Model: REPLPanel :: Name
+ Swarm.TUI.Model: REPLPanel :: FocusablePanel
- Swarm.TUI.Model: RobotPanel :: Name
+ Swarm.TUI.Model: RobotPanel :: FocusablePanel
- Swarm.TUI.Model: StartOverButton :: Seed -> ScenarioInfoPair -> ButtonSelection
+ Swarm.TUI.Model: StartOverButton :: Button
- Swarm.TUI.Model: WorldPanel :: Name
+ Swarm.TUI.Model: WorldPanel :: FocusablePanel
- Swarm.TUI.Model: [_modalDialog] :: Modal -> Dialog ButtonSelection
+ Swarm.TUI.Model: [_modalDialog] :: Modal -> Dialog ButtonAction Name
- Swarm.TUI.Model: modalDialog :: Lens' Modal (Dialog ButtonSelection)
+ Swarm.TUI.Model: modalDialog :: Lens' Modal (Dialog ButtonAction Name)
- Swarm.Util: getElemsInArea :: V2 Int64 -> Int64 -> Map (V2 Int64) e -> [e]
+ Swarm.Util: getElemsInArea :: Location -> Int32 -> Map Location e -> [e]
- Swarm.Util: manhattan :: V2 Int64 -> V2 Int64 -> Int64
+ Swarm.Util: manhattan :: Location -> Location -> Int32
- Swarm.Web: mkApp :: IORef GameState -> Server SwarmApi
+ Swarm.Web: mkApp :: IORef AppState -> Server SwarmApi
- Swarm.Web: startWebThread :: Maybe Port -> IORef GameState -> IO (Either String Port)
+ Swarm.Web: startWebThread :: Maybe Port -> IORef AppState -> IO (Either String Port)
- Swarm.Web: type SwarmApi = "robots" :> Get '[JSON] [Robot] :<|> "robot" :> Capture "id" Int :> Get '[JSON] (Maybe Robot)
+ Swarm.Web: type SwarmApi = "robots" :> Get '[JSON] [Robot] :<|> "robot" :> Capture "id" Int :> Get '[JSON] (Maybe Robot) :<|> "goals" :> "prereqs" :> Get '[JSON] [PrereqSatisfaction] :<|> "goals" :> "active" :> Get '[JSON] [Objective] :<|> "goals" :> "graph" :> Get '[JSON] (Maybe GraphInfo) :<|> "goals" :> "uigoal" :> Get '[JSON] GoalTracking :<|> "goals" :> Get '[JSON] WinCondition :<|> "repl" :> "history" :> "full" :> Get '[JSON] [Text]
- Swarm.Web: webMain :: Maybe (MVar (Either String ())) -> Port -> IORef GameState -> IO ()
+ Swarm.Web: webMain :: Maybe (MVar (Either String ())) -> Port -> IORef AppState -> IO ()

Files

CHANGELOG.md view
@@ -1,5 +1,83 @@ # Revision history for swarm +## **0.3.0.0** - 2023-01-30++This is the first release of 2023! It contains:+- various new features and bugfixes (see subsections below)+- UI tweaks+- fewer typos (thanks @bwignall and @kostmo)+- upgraded dependencies (`>=lsp-1.6` and `>=brick-1.5`) and raised Stack resolver to use GHC 9.4 [#1001](https://github.com/swarm-game/swarm/pull/1001)+- a ton of internal refactoring++### Fixed bugs+* copy parent robot context to child when executing `build` by @byorgey in [#817](https://github.com/swarm-game/swarm/pull/817)+* Merge new requirements context with existing when running from REPL by @byorgey in [#965](https://github.com/swarm-game/swarm/pull/965)+* Reset `lastFrameTime` when starting a new scenario by @byorgey in [#855](https://github.com/swarm-game/swarm/pull/855)+* Fix capability checking, and refactor/add lots of comments by @byorgey in [#959](https://github.com/swarm-game/swarm/pull/959)+* Fix `EntityMap` merging by @byorgey in [#962](https://github.com/swarm-game/swarm/pull/962)+* Fix: add first heard message to log by @xsebek in [#842](https://github.com/swarm-game/swarm/pull/842)+* Fix hypothetical result of condition by @xsebek in [#940](https://github.com/swarm-game/swarm/pull/940)+* One file per achievement record by @kostmo in [#954](https://github.com/swarm-game/swarm/pull/954)+* Show duplicate robot logs by @xsebek in [#1022](https://github.com/swarm-game/swarm/pull/1022)+* Copy requirements map to robot context when loading a new `ProcessedTerm` by @byorgey in [#827](https://github.com/swarm-game/swarm/pull/827)+* Set REPL to `Working` when base has `program` field specified by @byorgey in [#846](https://github.com/swarm-game/swarm/pull/846)+* Create swarm subdirectories as necessary by @xsebek in [#943](https://github.com/swarm-game/swarm/pull/943)++### New Features++#### Swarm language+* implement Equip/Unequip by @kostmo in [#887](https://github.com/swarm-game/swarm/pull/887)+  * The great `install`/`equip` switch by @byorgey in [#989](https://github.com/swarm-game/swarm/pull/989)+* Meeting other robots by @byorgey in [#920](https://github.com/swarm-game/swarm/pull/920)+* New `heading` command by @byorgey in [#955](https://github.com/swarm-game/swarm/pull/955)+* Add `isempty : cmd bool` to check whether current cell has an entity by @byorgey in [#968](https://github.com/swarm-game/swarm/pull/968)++#### Entities+* Add `GPS receiver` device to provide `senseloc` capability by @byorgey in [#956](https://github.com/swarm-game/swarm/pull/956)+* Device with recipe for 'appear' capability by @kostmo in [#1027](https://github.com/swarm-game/swarm/pull/1027)+* device for setname by @kostmo in [#1028](https://github.com/swarm-game/swarm/pull/1028)++#### LSP improvements+* swarm-lang IDE hover by @kostmo in [#972](https://github.com/swarm-game/swarm/pull/972)+* Include `SrcLoc` info with variable binding sites by @byorgey in [#993](https://github.com/swarm-game/swarm/pull/993)+* Annotate ASTs with types at every node by @byorgey in [#991](https://github.com/swarm-game/swarm/pull/991)+* Improve OnHover for Def/Let, App and Const by @xsebek in [#986](https://github.com/swarm-game/swarm/pull/986)+* warn of unused variables by @kostmo in [#983](https://github.com/swarm-game/swarm/pull/983)++#### Web API+* Expose REPL history via web interface by @kostmo in [#821](https://github.com/swarm-game/swarm/pull/821)++#### Command line options+* Add full color mode option by @xsebek in [#851](https://github.com/swarm-game/swarm/pull/851)++#### New scenarios+* New ranching scenario by @kostmo in [#835](https://github.com/swarm-game/swarm/pull/835)+* new bind tutorial by @kostmo in [#895](https://github.com/swarm-game/swarm/pull/895)+* A "bucket brigade" of robots by @kostmo in [#885](https://github.com/swarm-game/swarm/pull/885)+* Ice Cream Shop by @kostmo in [#1008](https://github.com/swarm-game/swarm/pull/1008)+* word search by @kostmo in [#999](https://github.com/swarm-game/swarm/pull/999)+* Wolf, Goat, Cabbage scenario by @kostmo in [#944](https://github.com/swarm-game/swarm/pull/944)+* swarm logo animation by @kostmo in [#1018](https://github.com/swarm-game/swarm/pull/1018)++#### Scenario development+* Boolean expressions of objective prerequisites by @kostmo in [#927](https://github.com/swarm-game/swarm/pull/927)+* support 'orientationMap' by @kostmo in [#1023](https://github.com/swarm-game/swarm/pull/1023)+* support custom attributes defined in scenarios by @kostmo in [#1058](https://github.com/swarm-game/swarm/pull/1058)++#### UI enhancements+* show devices that enable commands by @kostmo in [#899](https://github.com/swarm-game/swarm/pull/899)+* Add a "driving mode" to the REPL by @kostmo in [#819](https://github.com/swarm-game/swarm/pull/819)++#### Achievements+* achievements board by @kostmo in [#796](https://github.com/swarm-game/swarm/pull/796)+* first tutorial completion achievement by @kostmo in [#925](https://github.com/swarm-game/swarm/pull/925)+* Add achievement for losing by @kostmo in [#945](https://github.com/swarm-game/swarm/pull/945)++#### Debugging+* Better CESK machine pretty-printing by @byorgey in [#948](https://github.com/swarm-game/swarm/pull/948)+* Add the full expected location for the data directory by @TristanCacqueray in [#908](https://github.com/swarm-game/swarm/pull/908)+* Extend logs for warnings by @xsebek in [#982](https://github.com/swarm-game/swarm/pull/982)+ ## **0.2.0.0** - 2022-11-01  A bunch of small fixes and improvements; special thanks to new
app/Main.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} @@ -14,11 +15,12 @@ import Swarm.DocGen (EditorType (..), GenerateDocs (..), PageAddress (..), SheetType (..), generateDocs) import Swarm.Language.LSP (lspMain) import Swarm.Language.Pipeline (processTerm)-import Swarm.TUI.Model (AppOpts (..))+import Swarm.TUI.Model (AppOpts (..), ColorMode (..)) import Swarm.Version import Swarm.Web (defaultPort) import System.Exit (exitFailure, exitSuccess) import System.IO (hPrint, stderr)+import Text.Read (readMaybe)  gitInfo :: Maybe GitInfo gitInfo = either (const Nothing) Just ($$tGitInfoCwdTry)@@ -45,7 +47,17 @@         , command "version" (info (pure Version) (progDesc "Get current and upstream version."))         ]     )-    <|> Run <$> (AppOpts <$> seed <*> scenario <*> run <*> autoplay <*> cheat <*> webPort <*> pure gitInfo)+    <|> Run+      <$> ( AppOpts+              <$> seed+              <*> scenario+              <*> run+              <*> autoplay+              <*> cheat+              <*> color+              <*> webPort+              <*> pure gitInfo+          )  where   format :: Parser CLI   format =@@ -98,13 +110,24 @@             <> help ("Set the web service port (or disable it with 0). Default to " <> show defaultPort <> ".")         )   scenario :: Parser (Maybe String)-  scenario = optional $ strOption (long "scenario" <> short 'c' <> metavar "FILE" <> help "Name of a scenario to load")+  scenario = optional $ strOption (long "scenario" <> short 'i' <> metavar "FILE" <> help "Name of an input scenario to load")   run :: Parser (Maybe String)   run = optional $ strOption (long "run" <> short 'r' <> metavar "FILE" <> help "Run the commands in a file at startup")   autoplay :: Parser Bool   autoplay = switch (long "autoplay" <> short 'a' <> help "Automatically run the solution defined in the scenario, if there is one. Mutually exclusive with --run.")   cheat :: Parser Bool   cheat = switch (long "cheat" <> short 'x' <> help "Enable cheat mode. This allows toggling Creative Mode with Ctrl+v and unlocks \"Testing\" scenarios in the menu.")+  color :: Parser (Maybe ColorMode)+  color = optional $ option colorModeParser (long "color" <> short 'c' <> metavar "MODE" <> help "Use none/8/16/full color mode.")+  colorModeParser =+    Data.Foldable.asum+      [ ColorMode8 <$ text "8"+      , ColorMode16 <$ text "16"+      , ColorMode240 <$> maybeReader (\case ('2' : '4' : '0' : '_' : w) -> readMaybe w; _ -> Nothing)+      , FullColor <$ text "full"+      , NoColor <$ text "none"+      ]+  text t = maybeReader (\x -> if x == t then Just x else Nothing)  cliInfo :: ParserInfo CLI cliInfo =
bench/Benchmark.hs view
@@ -9,8 +9,6 @@ import Control.Monad.State (evalStateT, execStateT) import Criterion.Main (Benchmark, bench, bgroup, defaultConfig, defaultMainWith, whnfAppIO) import Criterion.Types (Config (timeLimit))-import Data.Int (Int64)-import Linear.V2 (V2 (V2)) import Swarm.Game.CESK (emptyStore, initMachine) import Swarm.Game.Display (defaultRobotDisplay) import Swarm.Game.Robot (TRobot, mkRobot)@@ -22,13 +20,14 @@ import Swarm.Language.Pipeline (ProcessedTerm) import Swarm.Language.Pipeline.QQ (tmQ) import Swarm.Language.Syntax (north)+import Swarm.Util.Location  -- | The program of a robot that does nothing. idleProgram :: ProcessedTerm idleProgram = [tmQ| {} |]  -- | The program of a robot which waits a random number of ticks, changes its---   appearence, then waits another random number of ticks, places a tree, and+--   appearance, then waits another random number of ticks, places a tree, and --   then self-destructs. treeProgram :: ProcessedTerm treeProgram =@@ -70,14 +69,14 @@   |]  -- | Initializes a robot with program prog at location loc facing north.-initRobot :: ProcessedTerm -> V2 Int64 -> TRobot-initRobot prog loc = mkRobot () Nothing "" [] (Just north) loc defaultRobotDisplay (initMachine prog Context.empty emptyStore) [] [] False False 0+initRobot :: ProcessedTerm -> Location -> TRobot+initRobot prog loc = mkRobot () Nothing "" [] (Just loc) north defaultRobotDisplay (initMachine prog Context.empty emptyStore) [] [] False False 0  -- | Creates a GameState with numRobot copies of robot on a blank map, aligned --   in a row starting at (0,0) and spreading east.-mkGameState :: (V2 Int64 -> TRobot) -> Int -> IO GameState+mkGameState :: (Location -> TRobot) -> Int -> IO GameState mkGameState robotMaker numRobots = do-  let robots = [robotMaker (V2 (fromIntegral x) 0) | x <- [0 .. numRobots - 1]]+  let robots = [robotMaker (Location (fromIntegral x) 0) | x <- [0 .. numRobots - 1]]   Right initState <- runExceptT classicGame0   execStateT     (mapM addTRobot robots)
data/entities.yaml view
@@ -74,6 +74,16 @@     Can be used to build a simple furnace, among other things.   properties: [portable] +- name: handle+  display:+    attr: device+    char: 'h'+  description:+  - Ergonomic affordance for manipulating objects. Also grants the "setname" capability, allowing one+    to assign a "handle" to robots.+  properties: [portable]+  capabilities: [setname]+ - name: lodestone   display:     attr: iron@@ -84,6 +94,15 @@   properties: [portable]   capabilities: [negation] +- name: beaglepuss+  display:+    attr: rubber+    char: 'B'+  description:+  - Iconic novelty disguise. Renders one either completely inconspicuous, or the opposite.+  properties: [portable]+  capabilities: [appear]+ - name: boulder   display:     attr: rock@@ -106,7 +125,7 @@     char: 'Å'     priority: 11   description:-  - A tunnel in a mountain through which robots can freely move.+  - A tunnel in a mountain through which anything can freely move.   properties: []  - name: copper ore@@ -326,15 +345,43 @@     information, made of twisted cotton fibers.  Multiple strings can     also be woven into larger configurations such as cloth or nets.   - |-    A string device enables two commands: the first, `format : a ->-    text`, can turn any value into a suitable text representation.-    The second is the infix operator `++ : text -> text -> text`-    which can be used to concatenate two text values.  For example,+    An equipped `string` device enables several commands for working with+    `text` values:   - |+    `format : a -> text` can turn any value into a suitable text+    representation.+  - |+    The infix operator `++ : text -> text -> text`+    can be used to concatenate two text values.  For example,+  - |     "Number of widgets: " ++ format numWidgets+  - |+    `chars : text -> int` computes the number of characters in a+    `text` value.+  - |+    `split : int -> text -> text * text` splits a `text` value into+    two pieces, one before the given index and one after.   properties: [portable]   capabilities: [text] +- name: decoder ring+  display:+    attr: silver+    char: 'O'+  description:+  - |+    A handy lookup table for converting from characters to numeric codes and+    back, shaped for some reason into a ring.  When equipped, it+    enables two functions:+  - |+    `charAt : int -> text -> int` returns the numeric code of the+    character at a specific index in a (0-indexed) `text` value.+  - |+    `toChar : int -> text` creates a singleton (length-1) `text`+    value containing a character with the given numeric code.+  properties: [portable]+  capabilities: [code]+ - name: lambda   display:     attr: flower@@ -379,11 +426,11 @@     attr: wood     char: 'B'   description:-  - A robot with a boat installed can float on top of water without drowning.+  - A robot with a boat equipped can float on top of water without drowning.   - |-    Note: most devices are automatically installed on robots that+    Note: most devices are automatically equipped on robots that     will require them; but this doesn't work in the case of boats since floating is not-    associated with any particular command.  To manually ensure a boat is installed on+    associated with any particular command.  To manually ensure a boat is equipped on     a robot, just add the special command `require "boat"` to the robot's program.   properties: [portable]   capabilities: [float]@@ -645,7 +692,7 @@     attr: device     char: '%'   description:-  - Installing treads on a robot allows it to move and turn.+  - Equipping treads on a robot allows it to move and turn.   - The `move` command moves the robot forward one unit.   - 'Example:'   - '  move; move; // move two units'@@ -673,8 +720,8 @@     char: '<'   description:   - A grabber arm is an all-purpose, hydraulically controlled device that can-    manipulate other items and robots via the `grab`, `place`, `give`,-    and `install` commands.+    manipulate other items and robots via the `grab`, `place`, and `give`+    commands.   - The `grab` command takes no arguments; it simply grabs whatever is     available, and also returns the name of the grabbed thing as a string.     It raises an exception if run in a cell that does not contain an item.@@ -682,13 +729,10 @@     place.  The item is removed from the robot's inventory and placed     in the robot's current cell (which must be empty).  Raises an     exception if the operation fails.-  - "The `give` command takes two arguments: the robot to+  - "The `give` command takes two arguments: the actor to     give an item to (which can be at most 1 cell away), and the name     of the item to give. Raises an exception if the operation fails."-  - "The `install` command takes two arguments: the robot-    on which to install a device (which can be at most 1 cell away),-    and the name of the device to install."-  capabilities: [grab, give, place, install]+  capabilities: [grab, give, place]   properties: [portable]  - name: fast grabber@@ -697,7 +741,7 @@     char: '≪'   description:   - A fast grabber is an improved version of the basic grabber - not only-    can it 'grab', 'place', 'give', and 'install', it can also 'swap'.+    can it 'grab', 'place', and 'give', it can also 'swap'.   - The 'swap' command allows the robot to execute grab and place at the     same time so that the location where the robot is standing does not     become empty.@@ -706,9 +750,26 @@   - In addition you retain the capability to use the 'atomic' command,     with which you can implement other commands that are safe when     run in parallel.-  capabilities: [grab, swap, give, place, install, atomic]+  capabilities: [grab, swap, give, place, atomic]   properties: [portable] +- name: welder+  display:+    attr: device+    char: 'f'+  description:+  - |+    A welder can be used to affix other gadgets to one's robot, providing+    the `equip` and `unequip` commands.+  - |+    The `equip` command takes one argument: the name of the device to equip.+    Raises an exception if the device is not in your inventory.+  - |+    The `unequip` command takes one argument: the name of the device to unequip.+    Raises an exception if the device is not equipped.+  capabilities: [equip, unequip]+  properties: [portable]+ - name: harvester   display:     attr: device@@ -869,9 +930,13 @@     about their surroundings.  Simply give `scan` a direction in which to scan,     and information about the scanned item (if any) will be added to the robot's     inventory."-  - "A scanner also enables the `blocked` command, which returns a+  - "A scanner also enables `blocked : cmd bool`, which returns a      boolean value indicating whether the robot's path is blocked-     (i.e. whether executing a `move` command would fail)."+     (i.e. whether executing a `move` command would fail);+     `ishere : text -> cmd bool` for checking whether the current+     cell contains a particular entity; and `isempty : cmd bool` for+     checking whether the current cell is empty of entities. Note that+     `ishere` and `isempty` do not detect robots, only entities."   - "Finally, robots can use the `upload` command to copy their accumulated     knowledge to another nearby robot; for example, `upload base`."   properties: [portable]@@ -921,10 +986,10 @@     attr: device     char: '@'   description:-  - "Allows a robot to hear what nearby robots are saying."-  - "Simply having this device installed will automatically-     add messages said by nearby robots to this robot's log,-     assuming it has a logger installed."+  - "Allows a robot to hear anything being said nearby."+  - "Simply having this device equipped will automatically+     add messages said by nearby actors to this robot's log,+     assuming it has a logger equipped."   - "That way you can view any heard message later either in      the logger or the message window."   - "To wait for a message and get the string value, use:"@@ -996,9 +1061,16 @@     attr: device     char: 'N'   description:-  - "A compass gives a robot the ability to orient using cardinal directions: north, south, west, and east."-  - "Example:"-  - "turn west; move; turn north"+  - |+    A compass gives a robot the ability to orient using the cardinal+    directions north, south, west, and east; for example, `turn west;+    move; turn north`.+  - |+    It also enables the `heading : cmd dir` command, which returns the+    robot's current heading. For example, the following code moves+    east and then restores the same heading as before:+  - |+    d <- heading; turn east; move; turn d   properties: [portable]   capabilities: [orient] @@ -1031,7 +1103,7 @@     attr: device     char: 'Ю'   description:-  - An I/O cable can be used to communicate with an adjacent robot.+  - An I/O cable can be used to communicate with adjacent actors.   properties: [portable]  - name: rubber band@@ -1048,7 +1120,7 @@   - "This seems like a safe way to execute `grab` only when there is a     rock to grab.  However, it is actually possible for the `grab` to     fail, if some other robot B snatches the rock right after robot A sensed-    it and before robot A got around to grab it on the next game tick."+    it and before robot A got around to grabbing it on the next game tick."   - "This will make robot A very sad and it will crash."   - "To prevent this situation, robot A can wrap the commands in `atomic`, like so:"   - |@@ -1063,9 +1135,46 @@     char: '#'   description:   - A net is a device woven out of many strings.  With a net-    installed, you can use the `try` command to catch errors. For example,+    equipped, you can use the `try` command to catch errors. For example,   - |     `try {move} {turn left}`   - will attempt to move, but if that fails, turn left instead.   properties: [portable]   capabilities: [try]++- name: antenna+  display:+    attr: silver+    char: 'Y'+  description:+  - A device for transforming electric current into radio waves, and+    vice versa.+  - |+    It can be used to locate nearby actors, by bouncing radio+    waves off them and listening for the echo.  This capability can be+    accessed via two commands:+  - |+    `meet : cmd (() + actor)` tries to locate a+    nearby actor (a robot, or... something else?) up to one cell away.+    It returns a reference to the nearest actor, or a unit value if+    none are found.+  - |+    `meetAll : (b -> actor -> cmd b) -> b -> cmd b` runs a command on+    every nearby actor (other than oneself), folding over the results+    to compute a final result of type `b`.  For example, if `x`, `y`,+    and `z` are nearby actors, then `meetAll f b0` is equivalent to+    `b1 <- f b0 x; b2 <- f b1 y; f b2 z`.+  properties: [portable]+  capabilities: [meet]++- name: GPS receiver+  display:+    attr: device+    char: 'G'+  description:+  - |+    A GPS receiver triangulates your current (x,y) coordinates from+    some convenient satellite signals,+    enabling the command `whereami : cmd (int * int)`.+  properties: [portable]+  capabilities: [senseloc]
data/recipes.yaml view
@@ -459,6 +459,8 @@   - [4, big motor]   - [64, iron plate]   - [16, rubber]+  required:+  - [1, welder]   out:   - [1, tank treads] @@ -478,6 +480,21 @@   - [1, fast grabber]  - in:+  - [2, copper wire]+  - [1, copper pipe]+  - [1, iron plate]+  - [1, I/O cable]+  out:+  - [1, welder]++- in:+  - [1, copper pipe]+  out:+  - [1, handle]+  required:+  - [1, welder]++- in:   - [4, circuit]   - [1, iron plate]   - [3, copper pipe]@@ -508,6 +525,20 @@   out:   - [1, bitcoin] +- in:+  - [2, silver]+  - [2, copper wire]+  out:+  - [1, antenna]++- in:+  - [1, antenna]+  - [1, clock]+  - [1, circuit]+  - [1, compass]+  out:+  - [1, GPS receiver]+ ## MAGIC  - in:@@ -703,6 +734,13 @@   - [1, strange loop]   out:   - [1, rubber band]++- in:+  - [1, rubber]+  - [1, glass]+  - [1, string]+  out:+  - [1, beaglepuss]  ######################################### ##                COTTON               ##
data/scenarios/Challenges/00-ORDER.txt view
@@ -1,5 +1,11 @@ chess_horse.yaml teleport.yaml 2048.yaml+word-search.yaml+ice-cream.yaml hanoi.yaml+bucket-brigade.yaml+wolf-goat-cabbage.yaml+friend.yaml Mazes+Ranching
data/scenarios/Challenges/Mazes/easy_cave_maze.yaml view
@@ -15,7 +15,6 @@   def until = \p. \c. b <- p; if b {} {c; until p c} end;   def fwd = until blocked move end;   build {-    require "branch predictor"; // #540     turn east;     until (ishere "goal") (fwd; turn right; fwd; turn left);     grab
data/scenarios/Challenges/Mazes/easy_spiral_maze.yaml view
@@ -16,7 +16,6 @@   def until = \p. \c. b <- p; if b {} {c; until p c} end;   def fwd = until blocked move end;   build {-    require "branch predictor"; // #540     turn west;     until (ishere "goal") (fwd; turn right);     grab
data/scenarios/Challenges/Mazes/invisible_maze.yaml view
@@ -25,9 +25,6 @@   def rightBlocked = tR; b <- blocked; tL; return b end;   def mazeStep = ifC rightBlocked {ifC blocked {tL} {move}} {tR; move} end;   build {-    require "treads"; require "branch predictor"; require "lambda";-    require "strange loop"; require "scanner"; require "grabber";   // #540-     forever (mazeStep; ifC (ishere "goal") {grab; return ()} {})   } robots:
data/scenarios/Challenges/Mazes/loopy_maze_sol.sw view
@@ -14,9 +14,5 @@   tB; move; tB end; build {-  require "treads"; require "scanner"; require "lambda";-  require "strange loop"; require "branch predictor";-  require "grabber";  // #540-   require 500 "rock"; DFS }
+ data/scenarios/Challenges/Ranching/00-ORDER.txt view
@@ -0,0 +1,1 @@+gated-paddock.yaml
+ data/scenarios/Challenges/Ranching/_gated-paddock/enclosure-checking.sw view
@@ -0,0 +1,255 @@+// Algorithm:+// ----------+// Maintain current direction until a wall is encountered.+// Then enter "wall-following mode".+// This mode presumes the wall is not a loop.+// Wall-following mode exploits recursion to keep track of how many left turns were made+// and then unwinds them again by ensuring each is paired with a right turn.+// Once the recursion is fully unwound, the robot proceeds along its original direction+// (though it may now be laterally displaced).+//+// (If it was a loop, then an "oriented breadcrumb" would need to be left.+// The breadcrumb is oriented in case a single-width passage is backtracked+// along the opposite wall.)++/** A "gate" is walkable, so we need to supplement the "blocked" check with this function.+Since fences are "unwalkable", they do not need to be mentioned in this function.+*/+def isFenced =+    s <- scan forward;+    return (+        case s+            (\_. false)+            (\x. x == "gate")+    );+    end;++def isBlockedOrFenced =+    b <- blocked;+    f <- isFenced;+    return (b || f);+    end;++// Returns true if we've already placed two+// breadcrumbs on a given tile, false otherwise.+def leaveBreadcrumbs =+    +    let bc1 = "fresh breadcrumb" in+    let bc2 = "treaded breadcrumb" in++    wasTraversedOnce <- ishere bc1;+    if wasTraversedOnce {+        _crumb <- grab;+        make bc2;+        place bc2;+        return false;+    } {+        wasTraversedTwice <- ishere bc2;+        if wasTraversedTwice {+            return true;+        } {+            // Make sure nothing's in the way before we place+            // our breadcrumb:+            x <- scan down;+            case x return (\y.+                // If we're on a water tile, get rid of+                // it with our special "drilling" recipe+                if (y == "water") {+                    drill down;+                    // Nothing will remain on the ground.+                    // after making the "steam" via+                    // the drilling recipe.+                } {+                    grab;+                    return ();+                };+            );++            make bc1;+            place bc1;+            return false;+        };+    };+    end;++def goForwardToPatrol = \wasBlocked.+    b <- isBlockedOrFenced;+    if b {+        turn left;+        goForwardToPatrol true;+        turn right;+        goForwardToPatrol false;+    } {+        if wasBlocked {+            isLoop <- leaveBreadcrumbs;+            if isLoop {+                fail "loop";+            } {};+        } {};+        move;+    };+    end;++/**+There should only be one place in the+code where an exception is thrown: that is,+if a treaded breadcrumb is encountered.+*/+def checkIsEnclosedInner =+    try {+        goForwardToPatrol false;+        // Water is the outer boundary+        hasWater <- ishere "water";+        if hasWater {+            return false;+        } {+            checkIsEnclosedInner;+        };+    } {+        return true;+    };+    end;++def checkIsEnclosed =++    // The "evaporator" drill is used+    // to clear water tiles.+    let specialDrill = "evaporator" in+    create specialDrill;+    equip specialDrill;++    // NOTE: System robots can walk on water+    // so we only need this if we want to+    // demo the algorithm with a player robot.+//    create "boat";+//    equip "boat";++    checkIsEnclosedInner;+    end;++def boolToInt = \b. if (b) {return 1} {return 0}; end;++def countAdjacentBlockages =++    turn left;+    b1 <- isBlockedOrFenced;+    c1 <- boolToInt b1;++    turn left;+    b2 <- isBlockedOrFenced;+    c2 <- boolToInt b2;++    turn left;+    b3 <- isBlockedOrFenced;+    c3 <- boolToInt b3;++    turn left;+    b4 <- isBlockedOrFenced;+    c4 <- boolToInt b4;++    return $ c1 + c2 + c3 + c4;+    end;++// Step forward, observing left and right.+def observeLeftAndRight =+    move;+    turn left;+    amBlockedLeft <- isBlockedOrFenced;+    val1 <- boolToInt amBlockedLeft;++    turn back;+    amBlockedRight <- isBlockedOrFenced;+    val2 <- boolToInt amBlockedRight;++    turn right;+    move;+    return $ val1 + val2;+    end;+++/** If the four cardinal directions have at most+one blockage, then there will exist an orientation+where both that direction and its opposite direction+are clear.+So we can step that direction, check to the left and+right of us, then step in the opposite direction+and do the same. This allows us to check the 4+blocks that touch the corners of the center block.+*/+def countDiagonalBlockages =+    // First, orient to the clear front-to-back path+    amBlocked <- isBlockedOrFenced;+    if amBlocked {turn left;} {};++    // Second, step to both sides+    fwdCount <- observeLeftAndRight;+    backCount <- observeLeftAndRight;+    return $ fwdCount + backCount;+    end;++def isStandingOnBridge =+    onFence <- ishere "fence";+    onGate <- ishere "gate";+    if (onFence || onGate) {+        adjCount <- countAdjacentBlockages;+        if (adjCount > 1) {+            return true;+        } {+            diagCount <- countDiagonalBlockages;+            return $ (adjCount + diagCount) > 1;+        };+    } {return false};+    end;++def getValForSheepIndex = \predicateCmd. \i.+    try {+        // This will throw an exception if+        // the sheep has already drowned.+        r <- robotnumbered i;+        didSucceed <- as r {predicateCmd};++        boolToInt didSucceed;+    } {+        return 0;+    }+    end;++/**+There are 3 sheep.+They have indices 1, 2, 3.+(The base has index 0).++THIS DOES NOT WORK!+*/+def countSheepWithRecursive = \predicateCmd. \i.++    if (i > 0) {+        val <- getValForSheepIndex predicateCmd i;+        recursiveCount <- countSheepWithRecursive predicateCmd $ i - 1;+        return $ val + recursiveCount;+    } {+        return 0;+    }+    end;+++def countSheepWith = \predicateCmd.++    val1 <- getValForSheepIndex predicateCmd 1;+    val2 <- getValForSheepIndex predicateCmd 2;+    val3 <- getValForSheepIndex predicateCmd 3;+    return $ val1 + val2 + val3;++    end;+++justFilledGap <- as base {+    isStandingOnBridge;+};++if (justFilledGap) {+    enclosedCount <- countSheepWith checkIsEnclosed;+    return $ enclosedCount >= 1;+} {+    return false;+}
+ data/scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw view
@@ -0,0 +1,217 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def processTree =+    grab;+    fenceCount <- count "fence";+    if (fenceCount < 90) {+        make "log";+        make "board";+        doN 2 $ make "fence";+    } {};+    end;++def makeHarvester =+    doN 3 $ make "log";+    doN 3 $ make "board";+    make "box";+    doN 2 $ make "wooden gear";+    make "harvester";+    equip "harvester";+    end;++def grabTwoRows =+    doN 7 (processTree; move);+    processTree;+    turn right;+    move;+    turn right;++    doN 7 (processTree; move);+    processTree;+    end;+++def grabTrees =+    doN 7 move;+    turn right;++    grabTwoRows;++    turn left;+    move;+    turn left;++    grabTwoRows;++    turn left;+    move;+    turn left;++    grabTwoRows;++    end;+++def harvestIfClover =+    x <- scan down;+    case x return (\y.+        if (y == "clover") {+            harvest;+            return ();+        } {};+    );+    end;+++def buildFence =+    doN 6 move;+    turn right;+    doN 4 (place "fence"; move);+    turn left;+    doN 30 (place "fence"; move);+    turn left;+    doN 15 (place "fence"; move);+    turn left;+    doN 30 (place "fence"; move);+    turn left;+    doN 10 (place "fence"; move);+    make "gate";+    place "gate";+    turn right;+    end;++def gatherClover =+    doN 8 move;+    turn left;+    doN 8 move;+    turn left;++    doN 19 (harvestIfClover; move;);+    turn right;+    move;+    turn right;+    doN 19 (harvestIfClover; move;);+    turn right;+    end;++def plantCloverColumn = \direction. \extraStep.+    doN 6 (+        extraStep;+        harvest;+        move;+    );++    turn direction;+    move;+    turn direction;+    move;+    end;++def plantCloverField =+    doN 4 move;+    +    plantCloverColumn right $place "clover";+    plantCloverColumn left $ place "clover";+    plantCloverColumn right $ place "clover";+    plantCloverColumn left $ place "clover";+    plantCloverColumn right $ place "clover";+    end;+++def harvestCloverField =+    doN 5 move;+    turn right;+    doN 5 move;+    turn right;+    wait 200;+    +    plantCloverColumn right $ return ();+    plantCloverColumn left $ return ();+    plantCloverColumn right $ return ();+    plantCloverColumn left $ return ();+    plantCloverColumn right $ return ();+    end;+++def travelRow = \action.+    doN 28 (action; move);+    action;+    end;++def placeCloverRow =+    travelRow $ place "clover";+    end;+++def distributeCloverInPaddock =+    turn left;+    doN 4 move;+    placeCloverRow;+    turn left;+    doN 2 move;+    turn left;+    placeCloverRow;+    turn right;+    doN 2 move;+    turn right;+    placeCloverRow;+    turn left;+    doN 2 move;+    turn left;+    placeCloverRow;+    end;+++def pickupWool =+    x <- scan down;+    case x return (\y.+        if (y == "wool") {+            grab;+            return ();+        } {};+    );+    end;++def sweep2rows =+    travelRow pickupWool;+    turn right;+    move;+    turn right;+    travelRow pickupWool;+    turn left;+    move;+    turn left;+    end;+++def sweepAreaForWool =+    doN 5 sweep2rows;+    turn left;+    doN 10 move;+    turn right;++    woolCount <- count "wool";+    if (woolCount >= 3) {+        make "sweater";+    } {};++    end;++def collectWool =+    wait 500;++    turn back;++    let forever : cmd unit -> cmd unit = \c. c ; forever c in+    forever sweepAreaForWool;+    end;+++grabTrees;+makeHarvester;+buildFence;+gatherClover;+plantCloverField;+doN 3 harvestCloverField;+distributeCloverInPaddock;+collectWool;
+ data/scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw view
@@ -0,0 +1,144 @@+// A "sheep" that wanders around randomly.++/** A "gate" is walkable, so we need to supplement the "blocked" check with this function.+Since fences are "unwalkable", they do not need to be mentioned in this function.+*/+def isFenced =+    s <- scan forward;+    return (+        case s+            (\_. false)+            (\x. x == "gate")+    );+    end;++def isBlockedOrFenced =+    b <- blocked;+    f <- isFenced;+    return (b || f);+    end;++def elif = \p.\t.\e. {if p t e} end;+++def turnToClover = \direction.++  x <- scan direction;+  case x (\_. return false;) (\y.+    if (y == "clover") {+      turn direction;+      return true;+    } {+      return false;+    };+  );+  end;+++/**+If there is adjacent clover,+turn that direction.+*/+def turnCloverDirection =++    foundN <- turnToClover north;+    if (foundN) {return true} {+      foundE <- turnToClover east;  +      if (foundE) {return true} {+        foundS <- turnToClover south;+        if (foundS) {return true} {+          turnToClover west;+        }+      }+    }+    end;++def decideDirection =++  let randdir : cmd dir =+    d <- random 4;+    return $ if (d == 0) {+        north+      } $ elif (d == 1) {+        east+      } $ elif (d == 2) {+        south+      } {+        west+      }+    in++  cloverCount <- count "clover";+  if (cloverCount > 4) {+    d <- randdir;+    turn d;+  } {+    nearClover <- turnCloverDirection;+    if (nearClover) {} {+      d <- randdir;+      turn d;+    }+  }+  end;++let forever : cmd unit -> cmd unit = \c. c ; forever c in+let repeat : int -> cmd unit -> cmd unit =+  \n. \c. if (n == 0) {} {c ; repeat (n-1) c} in+++forever (+  n <- random 30;+  wait (30 + n);++  decideDirection;++  dist <- random 3;+  repeat dist (++    b <- isBlockedOrFenced;+    if b {} {+      move;+    };++    // Sheep can drown.+    hasWater <- ishere "water";+    if hasWater {+      say "The jolly jumbuck sprang into the billabong. \"You'll never catch me alive!\", said he.";+      selfdestruct;+    } {};++    // Eat clover.+    x <- scan down;+    case x return (\y.+        if (y == "clover") {+            harvest;+            cloverCount <- count "clover";+            if (cloverCount < 2) {+              say "yum!"+            } {};+        } {};+    );+  );+  r <- random 30;+  if (r == 0) { say "baaa" } {};++  hasClover <- has "clover";+  if (hasClover) {+    r <- random 8;+    if (r == 0) { +      let item = "wool" in+      hasWool <- has item;+      if (hasWool) {+        // Make sure nothing's in the way before we place+        // our wool:+        x <- scan down;+        case x return (\_.+          grab;+          return ();+        );++        place item;+      } {};+    } {};+  } {};+)
+ data/scenarios/Challenges/Ranching/gated-paddock.yaml view
@@ -0,0 +1,543 @@+version: 1+name: Pastoral Island+author: Karl Ostmo+description: |+  Start a wool industry from the local fauna.+creative: false+objectives:+  - id: enclose_sheep+    teaser: Paddock sheep+    goal:+      - |+        You've homesteaded on a small island in the ocean.+        It's time to gather resources to trade.+      - |+        You encounter some feral sheep (@). They slowly wander the island and eat grass.+        Your mind wanders to textiles...+      - |+        First, paddock at least one sheep so they don't drown.+        Make sure there are no gaps in the fence!+      - |+        Note that you can use the "drill" command (by way of the "post puller" tool)+        to demolish a fence that has been "placed".+    condition: |-+      // Algorithm:+      // ----------+      // Maintain current direction until a wall is encountered.+      // Then enter "wall-following mode".+      // This mode presumes the wall is not a loop.+      // Wall-following mode exploits recursion to keep track of how many left turns were made+      // and then unwinds them again by ensuring each is paired with a right turn.+      // Once the recursion is fully unwound, the robot proceeds along its original direction+      // (though it may now be laterally displaced).+      //+      // (If it was a loop, then an "oriented breadcrumb" would need to be left.+      // The breadcrumb is oriented in case a single-width passage is backtracked+      // along the opposite wall.)++      /** A "gate" is walkable, so we need to supplement the "blocked" check with this function.+      Since fences are "unwalkable", they do not need to be mentioned in this function.+      */+      def isFenced =+          s <- scan forward;+          return (+              case s+                  (\_. false)+                  (\x. x == "gate")+          );+          end;++      def isBlockedOrFenced =+          b <- blocked;+          f <- isFenced;+          return (b || f);+          end;++      // Returns true if we've already placed two+      // breadcrumbs on a given tile, false otherwise.+      def leaveBreadcrumbs =++          let bc1 = "fresh breadcrumb" in+          let bc2 = "treaded breadcrumb" in++          wasTraversedOnce <- ishere bc1;+          if wasTraversedOnce {+              _crumb <- grab;+              make bc2;+              place bc2;+              return false;+          } {+              wasTraversedTwice <- ishere bc2;+              if wasTraversedTwice {+                  return true;+              } {+                  // Make sure nothing's in the way before we place+                  // our breadcrumb:+                  x <- scan down;+                  case x return (\y.+                      // If we're on a water tile, get rid of+                      // it with our special "drilling" recipe+                      if (y == "water") {+                          drill down;+                          // Nothing will remain on the ground.+                          // after making the "steam" via+                          // the drilling recipe.+                      } {+                          grab;+                          return ();+                      };+                  );++                  make bc1;+                  place bc1;+                  return false;+              };+          };+          end;++      def goForwardToPatrol = \wasBlocked.+          b <- isBlockedOrFenced;+          if b {+              turn left;+              goForwardToPatrol true;+              turn right;+              goForwardToPatrol false;+          } {+              if wasBlocked {+                  isLoop <- leaveBreadcrumbs;+                  if isLoop {+                      fail "loop";+                  } {};+              } {};+              move;+          };+          end;++      /**+      There should only be one place in the+      code where an exception is thrown: that is,+      if a treaded breadcrumb is encountered.+      */+      def checkIsEnclosedInner =+          try {+              goForwardToPatrol false;+              // Water is the outer boundary+              hasWater <- ishere "water";+              if hasWater {+                  return false;+              } {+                  checkIsEnclosedInner;+              };+          } {+              return true;+          };+          end;++      def checkIsEnclosed =++          // The "evaporator" drill is used+          // to clear water tiles.+          let specialDrill = "evaporator" in+          create specialDrill;+          equip specialDrill;++          // NOTE: System robots can walk on water+          // so we only need this if we want to+          // demo the algorithm with a player robot.+      //    create "boat";+      //    equip "boat";++          checkIsEnclosedInner;+          end;++      def boolToInt = \b. if (b) {return 1} {return 0}; end;++      def countAdjacentBlockages =++          turn left;+          b1 <- isBlockedOrFenced;+          c1 <- boolToInt b1;++          turn left;+          b2 <- isBlockedOrFenced;+          c2 <- boolToInt b2;++          turn left;+          b3 <- isBlockedOrFenced;+          c3 <- boolToInt b3;++          turn left;+          b4 <- isBlockedOrFenced;+          c4 <- boolToInt b4;++          return $ c1 + c2 + c3 + c4;+          end;++      // Step forward, observing left and right.+      def observeLeftAndRight =+          move;+          turn left;+          amBlockedLeft <- isBlockedOrFenced;+          val1 <- boolToInt amBlockedLeft;++          turn back;+          amBlockedRight <- isBlockedOrFenced;+          val2 <- boolToInt amBlockedRight;++          turn right;+          move;+          return $ val1 + val2;+          end;+++      /** If the four cardinal directions have at most+      one blockage, then there will exist an orientation+      where both that direction and its opposite direction+      are clear.+      So we can step that direction, check to the left and+      right of us, then step in the opposite direction+      and do the same. This allows us to check the 4+      blocks that touch the corners of the center block.+      */+      def countDiagonalBlockages =+          // First, orient to the clear front-to-back path+          amBlocked <- isBlockedOrFenced;+          if amBlocked {turn left;} {};++          // Second, step to both sides+          fwdCount <- observeLeftAndRight;+          backCount <- observeLeftAndRight;+          return $ fwdCount + backCount;+          end;++      def isStandingOnBridge =+          onFence <- ishere "fence";+          onGate <- ishere "gate";+          if (onFence || onGate) {+              adjCount <- countAdjacentBlockages;+              if (adjCount > 1) {+                  return true;+              } {+                  diagCount <- countDiagonalBlockages;+                  return $ (adjCount + diagCount) > 1;+              };+          } {return false};+          end;++      def getValForSheepIndex = \predicateCmd. \i.+          try {+              // This will throw an exception if+              // the sheep has already drowned.+              r <- robotnumbered i;+              didSucceed <- as r {predicateCmd};++              boolToInt didSucceed;+          } {+              return 0;+          }+          end;++      /**+      There are 3 sheep.+      They have indices 1, 2, 3.+      (The base has index 0).++      THIS DOES NOT WORK!+      */+      def countSheepWithRecursive = \predicateCmd. \i.++          if (i > 0) {+              val <- getValForSheepIndex predicateCmd i;+              recursiveCount <- countSheepWithRecursive predicateCmd $ i - 1;+              return $ val + recursiveCount;+          } {+              return 0;+          }+          end;+++      def countSheepWith = \predicateCmd.++          val1 <- getValForSheepIndex predicateCmd 1;+          val2 <- getValForSheepIndex predicateCmd 2;+          val3 <- getValForSheepIndex predicateCmd 3;+          return $ val1 + val2 + val3;++          end;+++      justFilledGap <- as base {+          isStandingOnBridge;+      };++      if (justFilledGap) {+          enclosedCount <- countSheepWith checkIsEnclosed;+          return $ enclosedCount >= 1;+      } {+          return false;+      }+  - id: feed_sheep+    teaser: Feed sheep+    goal:+      - |+        Safe! Your sheep are now hungry.+        Offer them something tasty and you may be rewarded.+      - |+        The sheep will move toward something edible on+        an adjacent tile and will eat it if they walk over it.+      - |+        You may want to add a gate to the fence+        to give yourself easier access.+    condition: |-+      def getTruthForSheepIndex = \predicateCmd. \i.+          try {+              // This will throw an exception if+              // the sheep has already drowned.+              r <- robotnumbered i;+              as r {predicateCmd};+          } {+              return false;+          };+          end;++      def anySheep = \predicateCmd. \i.++          if (i > 0) {+              didSucceed <- getTruthForSheepIndex predicateCmd i;+              if didSucceed {+                  return true;+              } {+                  anySheep predicateCmd $ i - 1;+              };+          } {+              return false;+          };+          end;++      anySheep (has "clover") 3;+    prerequisite: enclose_sheep+  - goal:+      - |+        Yum! Contented, well-fed sheep may drop wool.+      - |+        Winter is coming! Collect three wool bundles to make a sweater.+      - |+        Each sheep drops a finite amount over+        their lifetime.+    condition: |-+      as base {+        has "sweater";+      };+    prerequisite: feed_sheep+robots:+  - name: base+    dir: [0, 1]+    devices:+      - treads+      - scanner+      - dictionary+      - branch predictor+      - strange loop+      - clock+      - ADT calculator+      - comparator+      - workbench+      - grabber+      - welder+      - lambda+      - logger+      - hearing aid+      - counter+      - mirror+      - post puller+    inventory:+      - [0, fence]+      - [0, gate]+      - [10, hinge]+  - name: sheep+    description:+      - meandering livestock+    display:+      invisible: false+      char: '@'+    system: true+    dir: [0, 1]+    inventory:+      - [4, wool]+    program: |+      run "scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw";+entities:+  - name: fence+    display:+      char: '#'+    description:+      - Keeps sheep in. And some other things out.+    properties: [known, portable, unwalkable]+  - name: post puller+    display:+      char: 'P'+      attr: rock+    capabilities: [drill]+    description:+      - Good for dismantling fences.+    properties: [known, portable]+  - name: scrap wood+    display:+      char: '\'+    description:+      - Scrap wood. Can be reconditioned into boards.+    properties: [known, portable]+  - name: sweater+    display:+      attr: gold+      char: 'S'+    description:+      - A warm wool sweater. Just in time for winter!+    properties: [known, portable]+  - name: clover+    display:+      attr: flower+      char: '%'+    description:+      - A tasty stack for fluffy ruminants.+    properties: [portable, growable]+    growth: [80, 100]+  - name: gate+    display:+      char: '/'+      attr: rock+    description:+      - A gate permits the player to pass through, but sheep cannot.+    properties: [known]+  - name: hinge+    display:+      char: 'U'+      attr: rock+    description:+      - Facilitates swinging action.+    properties: [known, portable]+  - name: cabin+    display:+      char: Π+      attr: rock+    description:+      - Home sweet home.+    properties: [known, unwalkable]+  - name: pier+    display:+      char: 且+      attr: rock+    description:+      - Docking area for ships+    properties: [known]+  - name: fresh breadcrumb+    display:+      char: '.'+    description:+      - A marker that can be put down and found again. Used only by judge robot.+    properties: [portable]+  - name: treaded breadcrumb+    display:+      char: 'x'+    description:+      - A marker that can be put down and found again (for a second time). Used only by judge robot.+    properties: [portable]+  - name: evaporator+    display:+      char: 'E'+    description:+      - A tool that allows clearing a water tile. Used only by judge robot.+    properties: [portable]+    capabilities: [drill]+  - name: wool+    display:+      char: 'ω'+      attr: gold+    description:+      - A bundle of raw animal fiber.+    properties: [portable]+  - name: steam+    display:+      char: 'Z'+    description:+      - What's left after evaporating water. Used only by judge robot.+    properties: [portable]+recipes:+  - in:+      - [2, board]+    out:+      - [1, fence]+  - in:+      - [3, wool]+    out:+      - [1, sweater]+  - in:+      - [1, scrap wood]+    out:+      - [1, board]+  - in:+      - [0, fresh breadcrumb]+    out:+      - [1, fresh breadcrumb]+  - in:+      - [1, fresh breadcrumb]+    out:+      - [1, treaded breadcrumb]+  - in:+      - [1, fence]+    out:+      - [1, scrap wood]+    required:+      - [1, post puller]+  - in:+      - [1, water]+    out:+      - [1, steam]+    required:+      - [1, evaporator]+  - in:+      - [1, hinge]+      - [1, fence]+    out:+      - [1, gate]+known: [mountain, tree, water]+seed: 0+solution: |+  run "scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw"+world:+  default: [dirt, water]+  palette:+    'B': [grass, null, base]+    '.': [grass]+    't': [dirt, tree]+    'x': [stone, mountain]+    'c': [stone, cabin]+    's': [grass, null, sheep]+    '%': [grass, clover, null]+    'H': [stone, pier, null]+    '~': [dirt, water]+  upperleft: [-34, 11]+  map: |-+    ~~~~.......~~~~~~~~~~~~~.......................~~~~~~+    ~~.............~~~~.......%.%%%%%%..%%%%%.%.......~~~+    ~........................%%%%%%%%%%%%%..%%%%.......~~+    ~...................................................~+    .............................x.xx........tttttttt...~+    .........................................tttttttt...~+    ..........................xxxxx..........tttttttt...~+    .........................................tttttttt...~+    ...........s.............................tttttttt....+    ....x....................................tttttttt....+    ...xx................................................+    ..xx.....................................B......c....+    ...x.................................................+    ..............................s......................+    .....................................................+    ....................................................~+    ~..............s...................................~~+    ~~.......xxx...........x.........................~~~~+    ~~~............xx............x............~~~~H~~~~~~+    ~~~.....................................~~~~~~H~~~~~~+    ~~.....................................~~~~~~~H~~~~~~+    ~~..............................~~~~~~~~~~~~~~H~~~~~~+    ~~~~...................~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ data/scenarios/Challenges/_bucket-brigade/brigade.sw view
@@ -0,0 +1,94 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def forever = \c. c; forever c; end;++def pollUntilHas = \item.+    itemInInventory <- has item;+    if itemInInventory {} {+        pollUntilHas item;+    };+    end;++def pollUntilCount = \targetCount. \item.+    itemCount <- count item;+    if (itemCount < targetCount) {+        pollUntilCount targetCount item;+    } {};+    end;++def waitToGiveUntilHas = \item. \recipient.+    pollUntilHas item;+    give recipient item;+    end;++/** The "depth" is the number of spaces between+the root robot and the target item.+It is one minus the distance to the target item.+*/+def recurseUntilDepth = \depth.+    let item = "coal lump" in+    child <- build {++        // NOTE: Treads are auto-equipped via the implicit "require"+        move;+        unequip "treads";++        pollUntilCount depth "repro kit";++        // Use the recipe from "repro kit"+        make "3D printer";++        equip "solar panel";+        equip "grabber";+        equip "3D printer";+        equip "dictionary";+        equip "branch predictor";+        equip "comparator";+        equip "calculator";+        equip "lambda";+        equip "strange loop";+        equip "string";+        equip "clock";+        equip "counter";+        equip "mirror";+        equip "scanner";+        equip "logger";+        equip "net";++        if (depth > 1) {+            recurseUntilDepth $ depth - 1;+            forever $ waitToGiveUntilHas item parent;+        } {+            make "bucketwheel excavator";+            forever (+                drill forward;+                give parent item;+            );+        };+    };++    let kitGiftCount = depth in+    // say $ "Going to give child " ++ (format kitGiftCount) ++ " repro kits.";+    doN kitGiftCount $ give child "repro kit";++    end;++def makeBriquette =+    let briquette = "coal briquette" in+    pollUntilCount 100 "coal lump";+    make briquette;+    briquetteAlreadyPlaced <- ishere briquette;+    if briquetteAlreadyPlaced {} {+        place briquette;+    };+    end;++def go =+    // Use the recipe from "repro kit" to unpack the right+    // set of items to be taken from the inventory with the implicit "require":+    make "3D printer";+    recurseUntilDepth 8;+    forever makeBriquette;+    end;++go;
+ data/scenarios/Challenges/_bucket-brigade/hauler.sw view
@@ -0,0 +1,44 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++/** The function argument returns a boolean.+Keep running the function until either the+loop counter runs out or the function returns true.+*/+def doNTimesOr = \n. \f.+    if (n > 0) {+        shouldQuit <- f;+        if shouldQuit {} {+            doN (n - 1) f;+        }+    } {};+    end;++def forever = \c. c; forever c; end;++def traverseRoad =+    doN 11 (+        move;+        wait 7;+    );+    end;++def patrol =+    let briquette = "coal briquette" in+    traverseRoad;+    doNTimesOr 40 (+        briquetteIsHere <- ishere briquette;+        if briquetteIsHere {grab; return ()} {};+        return briquetteIsHere;+    );+    turn back;+    traverseRoad;++    hasBriquette <- has briquette;+    if hasBriquette {+        say "Delivered!";+        make "energy";+    } {};+    turn back;+    end;++forever patrol;
+ data/scenarios/Challenges/_bucket-brigade/powerplant.sw view
@@ -0,0 +1,20 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def flashLights =+    doN 8 (+        move;+        wait 8;+        turn back;+    );+    end;++def checkForEnergy = \lastCount.+    hauler <- robotnamed "hauler";+    energyCount <- as hauler {+        count "energy";+    };+    if (energyCount > lastCount) {flashLights} {};+    checkForEnergy energyCount;+    end;++checkForEnergy 0;
+ data/scenarios/Challenges/_friend/cat.sw view
@@ -0,0 +1,75 @@+def forever : cmd unit -> cmd unit = \c. c ; forever c end++def repeat : int -> cmd unit -> cmd unit =+  \n. \c. if (n == 0) {} {c ; repeat (n-1) c}+end++def elif = \t. \then. \else. {if t then else} end+def else = \t. t end++def abs = \n. if (n < 0) {-n} {n} end++def randdir : cmd dir =+  d <- random 4;+  return (+    if (d == 0) {north}+    $ elif (d == 1) {east}+    $ elif (d == 2) {south}+    $ else {west}+  )+end++def chooseWait : cmd int =+  t <- random (16*2);+  return (16 + t)+end++def wander =+  d <- randdir;+  turn d;+  dist <- random 2;+  try {repeat dist move} {};+  r <- random 5;+  if (r == 0) { say "meow" } {}+end++def disappointed = \cat. say "meow??"; cat end++def follow : cmd unit -> actor -> cmd unit = \cat. \r.+  rLoc <- as r {whereami};+  myLoc <- whereami;+  let dx = fst rLoc - fst myLoc in+  let dy = snd rLoc - snd myLoc in+  if (abs dx > abs dy)+  { if (dx < 0) {turn west} {turn east} }+  { if (dy < 0) {turn south} {turn north} };+  if (abs dx != 0 || abs dy != 0) {try { move } { disappointed cat }} {};+  wait 4;+  follow cat r+end++def love = \cat.+  say "purr";+  fishGiver <- meet;+  case fishGiver+    (\_. disappointed cat)+    (\r. follow cat r)+end++def cat = \start. \fishCount. \waitTime.+  if (waitTime == 0) { wander; start } { wait 1 };+  n <- count "fish";+  if (n > fishCount)+  { say "yum!";+    if (n >= 3) { love start } { cat start n (waitTime - 1) }+  }+  { cat start fishCount (waitTime - 1) }+end++def startCat =+  n <- count "fish";+  w <- chooseWait;+  cat startCat n w+end;++startCat
+ data/scenarios/Challenges/_friend/friend-solution.sw view
@@ -0,0 +1,18 @@+def m2 = move; move end+def m5 = m2; m2; move end+def m10 = m5; m5 end+def m20 = m10; m10 end++def give_fish = \n.+  if (n == 0) {}+  { mcat <- meet;+    case mcat (\_. give_fish n) (\cat. give cat "fish"; give_fish (n-1))+  }+end;++build {+  require 3 "fish";+  m2; turn left; m20;+  give_fish 3;+  turn back; m20; turn right; m2+}
+ data/scenarios/Challenges/_ice-cream/customer.sw view
@@ -0,0 +1,60 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def approachShop =+    doN 5 (+        move;+        wait 8;+    );+    end;++def placeOrder =+    scoopCountExtra <- random 4000;+    let scoopCount = 1000 + scoopCountExtra in+    say $ format scoopCount ++ " scoops of vanilla ice cream, no more, no less! And top it with a cherry, please.";+    return scoopCount;+    end;++def waitForItem = \item.+    hasItem <- has item;+    if hasItem {+        return ();+    } {+        wait 1;+        waitForItem item;+    }+    end;++/**+If we receive the cherry+when we also have the correct number of+scoops, then we produce this item+as a sempahore for the goal evaluation.++Otherwise, we express our disappointment.+*/+def createCompletedSemaphore = \targetCount.+    scoopcount <- count "scoop";+    if (scoopcount < targetCount) {+        say "That's not enough ice cream! Terrible service.";+    } {+        if (scoopcount > targetCount) {+            say "That's too much ice cream!";+        } {+            say "Just right! Here's your money.";+            give base "bitcoin";+        };+    };+    turn back;+    approachShop;+    selfdestruct;+    end;++def runScript =+    approachShop;+    waitForItem "cone";+    targetScoopCount <- placeOrder;+    waitForItem "cherry";+    createCompletedSemaphore targetScoopCount;+    end;++runScript;
+ data/scenarios/Challenges/_ice-cream/solution.sw view
@@ -0,0 +1,68 @@+def serveCustomer = \cone. \customer.+    give customer cone;+    end;++def getCounter =+    move;+    c <- grab;+    equip c;+    end;++def getBriefcase =+    turn left;+    move;+    move;+    b <- grab;+    equip b;+    turn back;+    move;+    move;+    end;++def getIngredients =+    move;+    move;+    cone <- grab;+    move;+    cherry <- grab;+    turn back;+    move;+    move;+    move;+    turn right;+    return (cone, cherry);+    end;++def meetCustomer =+    maybeCustomer <- meet;+    case maybeCustomer (\_. meetCustomer) return;+    end;++def serveScoop = \customer.+    let s = "scoop" in+    make s;+    give customer s;+    end;++def scoopUntil = \customer. \targetRemainingIngredientCount.+    currentCount <- count "milk";+    if (currentCount > targetRemainingIngredientCount) {+        serveScoop customer;+        scoopUntil customer targetRemainingIngredientCount;+    } {};+    end;++def runSolution = \targetRemainingIngredientCount.+    getBriefcase;+    coneAndCherry <- getIngredients;+    getCounter;+    customer <- meetCustomer;++    let cone = fst coneAndCherry in+    let cherry = snd coneAndCherry in+    serveCustomer cone customer;+    scoopUntil customer targetRemainingIngredientCount;+    give customer cherry;+    end;++runSolution 2712;
+ data/scenarios/Challenges/_wolf-goat-cabbage/cross.sw view
@@ -0,0 +1,40 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def crossRiver =+    doN 18 move;+    end;++move;+move;+goat <- grab;+turn east;+crossRiver;+place goat;+turn back;+crossRiver;+turn right;+move;+wolf <- grab;+turn right;+crossRiver;+place wolf;+turn right;+move;+goat2 <- grab;+turn right;+crossRiver;+place goat2;+turn left;+move;+cabbage <- grab;+turn left;+crossRiver;+place cabbage;+turn back;+crossRiver;+turn right;+move;+goat3 <- grab;+turn right;+crossRiver;+place goat3;
+ data/scenarios/Challenges/_wolf-goat-cabbage/multi-item-possession.sw view
@@ -0,0 +1,4 @@+has_wolf <- has "wolf";+has_goat <- has "goat";+has_cabbage <- has "cabbage";+return $ (has_wolf && has_goat) || (has_goat && has_cabbage) || (has_wolf && has_cabbage);
+ data/scenarios/Challenges/_wolf-goat-cabbage/together-on-east-bank.sw view
@@ -0,0 +1,28 @@+def get_x_coord = \r.+    as r {+        pos <- whereami;+        return $ fst pos;+    };+    end;++def all_on_bank = \baseX. \robotName.+    r <- robotnamed robotName;+    thisX <- get_x_coord r;++    as r {+        try {grab; return ()} {};+        move;+        try {grab; return ()} {};+        move;+        try {grab; return ()} {};++        has_wolf <- has "wolf";+        has_goat <- has "goat";+        has_cabbage <- has "cabbage";+        return $ baseX == thisX && has_wolf && has_goat && has_cabbage;+    };+    end;++baseX <- get_x_coord base;++all_on_bank baseX "east_detector";
+ data/scenarios/Challenges/_wolf-goat-cabbage/unattended-together.sw view
@@ -0,0 +1,35 @@+def get_x_coord = \r.+    as r {+        pos <- whereami;+        return $ fst pos;+    };+    end;++def is_unattended_together = \baseX. \robotName.+    r <- robotnamed robotName;+    thisX <- get_x_coord r;++    if (baseX == thisX) {+        return false;+    } {+        as r {+            try {grab; return ()} {};+            move;+            try {grab; return ()} {};+            move;+            try {grab; return ()} {};++            has_wolf <- has "wolf";+            has_goat <- has "goat";+            has_cabbage <- has "cabbage";+            return $ (has_wolf && has_goat) || (has_goat && has_cabbage);+        };        +    }+    end;++baseX <- get_x_coord base;++west_bad <- is_unattended_together baseX "west_detector";+east_bad <- is_unattended_together baseX "east_detector";++return $ west_bad || east_bad;
+ data/scenarios/Challenges/_word-search/create-puzzle.sw view
@@ -0,0 +1,247 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def intersperse = \n. \f2. \f1. if (n > 0) {+        f1;+        if (n > 1) {+            f2;+        } {};+        intersperse (n - 1) f2 f1;+    } {};+    end;++def whichOrdinal = \str.+    if (str == "capital C") {+        return 0;+    } {+        if (str == "capital O") {+            return 1;+        } {+            if (str == "capital W") {+                return 2;+            } {+                return (-1);+            }+        }+    }+    end;++/**+Returns -1 if not a recognized letter.+*/+def getAdjacentOrdinal = \d.+    maybeEntity <- scan d;+    str <- case maybeEntity (\_. return "") (\s. return s);+    whichOrdinal str;+    end;++/**+Call a function repeatedly with the numeric argument,+in descending order.+*/+def iterN = \n. \f.+    if (n > 0) {+        let newNum = n - 1 in+        f newNum;+        iterN newNum f;+    } {};+    end;++def chooseLetter = \i.+    if (i == 0) {+        return "capital C";+    } {+        if (i == 1) {+            return "capital O";+        } {+            return "capital W";+        }+    };+    end;++/**+We only need to probe further if+the letter above us is an O.+Then we need to check whether the letter+above that is a C or a W.+*/+def getExcludedVerticalLetter =+    northOrdinal <- getAdjacentOrdinal north;+    if (northOrdinal == 1) {+        currentLoc <- whereami;+        teleport self (fst currentLoc, snd currentLoc + 1);+        doubleNorthOrdinal <- getAdjacentOrdinal north;+        teleport self currentLoc;++        if (doubleNorthOrdinal == 2) {+            return 0;+        } {+            if (doubleNorthOrdinal == 0) {+                return 2;+            } {+                return (-1);+            }+        }+    } {+        return (-1);+    }+    end;++/**+To ensure there are limited numbers of solutions+(preferably exactly one),+make sure we're not completing a word+horizontally (foward or backward)+or vertically (upward or downward), except if we+are in the designated location.+*/+def reRoll = \excludedVertical. \expectedFwdOrdinal. \expectedBkwdOrdinal.++    // NOTE: excludeTwo and excludeZero+    // are mutually exclusive!+    let excludeTwo = expectedFwdOrdinal == 2 in+    let excludeZero = expectedBkwdOrdinal == 0 in++    // NOTE: Excluded letters can only be 0 or 2.+    // 1 is always a valid option.++    if excludeZero {+        if (excludedVertical == 2) {+            return 1;+        } {+            // Zero is the only excluded value,+            // so just offset a choice between 0 and 1 upward by 1, +            // to make it a choice between 1 and 2.+            val <- random 2;+            return $ val + 1;+        };+    } {+        if excludeTwo {+            if (excludedVertical == 0) {+                return 1;+            } {+                // Two is the only excluded value,+                // so make it a choice between 0 and 1.+                random 2;+            };+        } {+            if (excludedVertical == 0) {+                // Zero is the only excluded value,+                // so just offset a choice between 0 and 1 upward by 1, +                // to make it a choice between 1 and 2.+                val <- random 2;+                return $ val + 1;+            } {+                if (excludedVertical == 2) {+                    // Two is the only excluded value,+                    // so make it a choice between 0 and 1.+                    random 2;+                } {+                    // No values are excluded, so select random from+                    // the full range.+                    random 3;+                }+            };+        };+    };+    end;++def singleTile = \expectedFwdOrdinal. \expectedBkwdOrdinal.+    excludedVertical <- getExcludedVerticalLetter;+    letterIndex <- reRoll excludedVertical expectedFwdOrdinal expectedBkwdOrdinal;+    chosenLetter <- chooseLetter letterIndex;+    place chosenLetter;+    return letterIndex;+    end;++def crossBack = \n.+    currentLoc <- whereami;+    teleport self (0, snd currentLoc - 1);+    end;++/**+Recursive. Tracks the completion of the word in both the forward+and backward directions.+*/+def layTilesRow = \expectedFwdOrdinal. \expectedBkwdOrdinal. \n.+    placedIndex <- singleTile expectedFwdOrdinal expectedBkwdOrdinal;++    if (n > 1) {+        move;++        newFwdOrdinal <- if (placedIndex == expectedFwdOrdinal || placedIndex == 0) {+            return $ placedIndex + 1;+        } {+            return 0;+        };++        newBkwdOrdinal <- if (placedIndex == expectedBkwdOrdinal || placedIndex == 2) {+            return $ placedIndex - 1;+        } {+            return 2;+        };++        layTilesRow newFwdOrdinal newBkwdOrdinal $ n - 1;+    } {};+    end;++def giveLetterNumbered = \n.+    letter <- chooseLetter n;+    give base letter;+    end;++def removeBoulder =+    baseLoc <- as base {whereami};+    teleport self (fst baseLoc - 1, snd baseLoc);++    // Remove the boulder blocking the player's path+    grab;++    // Make sure the base "knows" about the letters+    // to get rid of the question marks ("?").+    iterN 3 giveLetterNumbered;++    selfdestruct;+    end;++def createImpossiblePuzzle = \width. \height.+    intersperse height (crossBack width) (layTilesRow 0 2 width);+    end;++/**+This word center is padded by+one cell from the vertical and horizontal+edges of the playfield.+This means that the word will not exist+horizontally at either the top or bottom edge,+nor will it exist vertically at the left or+right edge of the playfield.+*/+def overwriteWithWord = \width. \height.++    randX <- random $ width - 2;+    randY <- random $ height - 2;+    let insertionX = 1 + randX in+    let insertionY = 1 + randY in+    teleport self (insertionX, -insertionY);++    // Rotate to random orientation+    turnCount <- random 3;+    doN turnCount $ turn left;++    move;+    turn back;++    // Place all three letters of the word+    iterN 3 $ \x.+        chosenLetter <- chooseLetter x;+        swap chosenLetter;+        move;+    end;++def createPuzzle = \width. \height.+    createImpossiblePuzzle width height;+    overwriteWithWord width height;+    removeBoulder;+    end;++createPuzzle 25 15;
+ data/scenarios/Challenges/_word-search/solution.sw view
@@ -0,0 +1,127 @@+def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;++def intersperse = \n. \f2. \f1. if (n > 0) {+        f1;+        if (n > 1) {+            f2;+        } {};+        intersperse (n - 1) f2 f1;+    } {};+    end;++def waitUntilUnblocked =+    x <- blocked;+    if x {+        wait 1;+        waitUntilUnblocked;+    } {};+    end;++def whichOrdinal =+    isC <- ishere "capital C";+    if (isC) {+        return 0;+    } {+        isO <- ishere "capital O";+        if (isO) {+            return 1;+        } {+            isW <- ishere "capital W";+            if (isW) {+                return 2;+            } {+                return (-1);+            }+        }+    }+    end;++// Go to upper-left corner+def goToCorner =+    myLoc <- whereami;+    doN (fst myLoc) move;+    turn right;+    doN (-(snd myLoc)) move;+    turn right;+    end;++def highlightLetter =+    drill down;+    end;++def traverseRow = \expectedOrdinal. \colCount.++    theFoundOrdinal <- whichOrdinal;++    // Logic: the first letter of the target word is *always*+    // considered a "match".+    let shouldAdvance = theFoundOrdinal == expectedOrdinal || theFoundOrdinal == 0 in+    newExpectedOrdinal <- if shouldAdvance {+        return $ theFoundOrdinal + 1;+    } {+        // Reset the progress+        return 0;+    };++    if (newExpectedOrdinal == 3) {+        turn back;++        intersperse 3 move highlightLetter;+        return true;+    } {+        if (colCount > 1) {+            move;+            traverseRow newExpectedOrdinal (colCount - 1);+        } {+          return false;+        };+    };+    end;++def advanceRow =+    turn left;+    move;+    turn left;+    end;++/**+Travels forward and then backward+across a row, to check for solutions+in either direction.+*/+def traverseCols = \width. \height. +    didWin <- traverseRow 0 width;+    if didWin {+        return true;+    } {+        turn back;+        didWinBackward <- traverseRow 0 width;+        if didWinBackward {+            return true;+        } {+            if (height > 1) {+                advanceRow;+                traverseCols width $ height - 1;+            } {+                return false;+            };+        }+    } +    end;++def solve = \boardWidth. \boardHeight.+    waitUntilUnblocked;+    goToCorner;++    wonHorizontally <- traverseCols boardWidth boardHeight;+    if wonHorizontally {+        return true;+    } {+        // If we did not find a horizontal solution,+        // look for vertical solutions.+        turn right;+        traverseCols boardHeight boardWidth;+    }+    end;++solve 25 15;
+ data/scenarios/Challenges/_word-search/verify-solution.sw view
@@ -0,0 +1,183 @@+/**+Algorithm:+We only need to check the base's+current position: if we find three contiguous highlights,+then we know that the player has just completed their+third highlight.+*/++def whichOrdinal =+    isC <- ishere "lowercase c";+    if (isC) {+        return 0;+    } {+        isO <- ishere "lowercase o";+        if (isO) {+            return 1;+        } {+            isW <- ishere "lowercase w";+            if (isW) {+                return 2;+            } {+                return (-1);+            }+        }+    }+    end;+++def whichHighlightedOrdinal = \str.+    if (str == "lowercase c") {+        return 0;+    } {+        if (str == "lowercase o") {+            return 1;+        } {+            if (str == "lowercase w") {+                return 2;+            } {+                return (-1);+            }+        }+    }+    end;++def countConsecutive = \expectedOrdinal. \n.++    thisOrdinal <- whichOrdinal;+    nextOrdinal <- if (thisOrdinal == expectedOrdinal) {+        return $ expectedOrdinal + 1;+    } {+        return 0;+    };++    if (nextOrdinal == 3) {+        return true;+    } {+        if (n > 1) {+            move;+            countConsecutive nextOrdinal $ n - 1;+        } {+            return false;+        };+    };+    end;++def checkBackAndForth =++    foundBackward <- countConsecutive 0 3;+    if (foundBackward) {+        return true;+    } {+        turn back;+        countConsecutive 0 3;+    }+    end;++def checkDirections = \n.+    if (n > 0) {+        wasFound <- checkBackAndForth;+        if wasFound {+            return true;+        } {+            turn left;+            checkDirections $ n - 1;+        }+    } {+        return false;+    }+    end;++def isMarkedInDirection = \d.+    scanResult <- scan d;+    ordinalNum <- case scanResult+        (\_. return (-1))+        whichHighlightedOrdinal;+    return $ ordinalNum >= 0;+    end;++/**+It's possible we could be one cell away from+a marked cell after finishing, either due+to using a directional `drill` command instead of+`drill down`, or due to an apparent bug which+does not evaluate the goal condition between the+`drill` and a `move` command.+*/+def moveToMarkedCell = \n.+    if (n > 0) {+        isMarkedAhead <- isMarkedInDirection forward;+        if isMarkedAhead {+            move;+            return true;+        } {+            turn left;+            moveToMarkedCell $ n - 1;+        };+    } {+        return false;+    };+    end;++/**+Orient ourselves such that+a marked cell is behind us.+*/+def findMarkBehind = \n.+    if (n > 0) {+        isMarkedBehind <- isMarkedInDirection back;+        if isMarkedBehind {+            return true;+        } {+            turn left;+            findMarkBehind $ n - 1;+        };+    } {+        return false;+    };+    end;++/**+The cell we're on might be in the middle of a word,+rather than the end. Determine the orientation of+the line, then move along it until reaching the end.++Algorithm:+0. Assumption: we are currently on a marked cell.+1. Turn in all all four directions to `scan back`+   for a second marked cell. Stop turning if+   we encounter one.+   If none found after 4 turns, abort.+2. `scan forward` to see if there is a marked cell in+   the opposite direction.+   `move` (foward) once if there is. Since the word+   is only three cells long, this will be the other+   end of it.+*/+def moveToWordExtrema =+    foundCellBehind <- findMarkBehind 4;+    if foundCellBehind {+        isMarkedAhead <- isMarkedInDirection forward;+        if isMarkedAhead {+            move;+        } {};+    } {};+    end;++def checkSoln =+    isMarkedHere <- isMarkedInDirection down;+    atMarkedCell <- if isMarkedHere {+        return true;+    } {+        moveToMarkedCell 4;+    };++    if atMarkedCell {+        moveToWordExtrema;+        checkDirections 4;+    } {+        return false;+    }+    end;++as base {checkSoln};
+ data/scenarios/Challenges/bucket-brigade.yaml view
@@ -0,0 +1,216 @@+version: 1+name: Bucket Brigade+author: Karl Ostmo+description: |+  Convey resources with limited mobility+creative: false+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    heavy: true+    dir: [0, 1]+    devices:+      - 3D printer+      - ADT calculator+      - branch predictor+      - clock+      - comparator+      - counter+      - dictionary+      - grabber+      - hearing aid+      - lambda+      - logger+      - mirror+      - net+      - scanner+      - strange loop+      - string+      - workbench+      - briquette press+    inventory:+      - [10, repro kit]+      - [1, treads]+  - name: hauler+    system: true+    dir: [-1, 0]+    devices:+      - furnace+    display:+      invisible: false+      char: '='+      attr: robot+    program: |+      run "scenarios/Challenges/_bucket-brigade/hauler.sw"+  - name: powerplant+    system: true+    dir: [1, 0]+    display:+      invisible: false+      char: '▒'+      attr: gold+    program: |+      run "scenarios/Challenges/_bucket-brigade/powerplant.sw"+objectives:+  - id: deliver_coal_lump+    teaser: Get coal to base+    goal:+      - Deliver a "coal lump" to the base.+      - |+        To excavate coal from the "lignite mine" (M), a robot needs to+        `drill` while in posession of a "bucketwheel excavator".+      - |+        To assemble the excavator, you'll need to repurpose+        some "treads".+        Unfortunately, you have only one set of "treads".+        You'll have to make do...+    condition: |+      as base {has "coal lump"}+  - goal:+      - Your base got some coal!+      - Now fashion more of it into a "coal briquette", and place it+        so that the hauler (=) can pick it up.+      - The hauler will periodically stop at the base+        to check if a load is available.+    condition: |+      hauler <- robotnamed "hauler";+      as hauler {has "coal briquette"}+    prerequisite: deliver_coal_lump+solution: |+  run "scenarios/Challenges/_bucket-brigade/brigade.sw"+entities:+  - name: bucketwheel excavator+    display:+      char: 'x'+    description:+      - A powerful machine designed to remove overburden in mining operations.+    properties: [known, portable]+    capabilities: [drill]+  - name: lignite mine+    display:+      attr: copper+      char: 'M'+    description:+      - Requires heavy equipment to remove the overburden.+    properties: [known]+  - name: coal lump+    display:+      char: 'c'+    description:+      - Primitive, sooty fuel.+    properties: [known, portable]+  - name: coal briquette+    display:+      char: 'q'+    description:+      - A compressed, convenient form of coal for consumption and transport.+    properties: [known, portable]+  - name: briquette press+    display:+      char: 'P'+    description:+      - A device to compress coal lumps into briquettes.+    properties: [known, portable]+  - name: energy+    display:+      char: 'E'+    description:+      - The result of burning a coal briquettte.+    properties: [portable]+  - name: repro kit+    display:+      char: 'k'+    description:+      - Kit that can be unpacked into everything a robot needs to reproduce.+    properties: [known, portable]+recipes:+  - in:+      - [1, repro kit]+    out:+      - [1, 3D printer]+      - [1, ADT calculator]+      - [1, branch predictor]+      - [1, calculator]+      - [1, clock]+      - [1, comparator]+      - [1, counter]+      - [1, dictionary]+      - [1, grabber]+      - [1, lambda]+      - [1, lodestone]+      - [1, logger]+      - [1, metal drill]+      - [1, mirror]+      - [1, net]+      - [1, scanner]+      - [1, solar panel]+      - [1, string]+      - [1, strange loop]+      - [1, welder]+      - [1, workbench]+  - in:+      - [1, treads]+    out:+      - [1, bucketwheel excavator]+  - in:+      - [1, lignite mine]+    out:+      - [1, lignite mine]+      - [1, coal lump]+    required:+      - [1, bucketwheel excavator]+      - [1, metal drill]+  - in:+      - [100, coal lump]+    out:+      - [1, coal briquette]+    required:+      - [1, briquette press]+  - in:+      - [1, coal briquette]+    out:+      - [1, energy]+    required:+      - [1, furnace]+++known: [boulder, lignite mine]+seed: 0+world:+  default: [grass]+  palette:+    'B': [dirt, null, base]+    '.': [dirt]+    '=': [stone]+    'M': [stone, lignite mine]+    'H': [stone, null, hauler]+    'Z': [stone, null, powerplant]+    'w': [grass]+    's': [stone]+    'A': [stone, boulder]+    '┌': [stone, upper left corner]+    '┐': [stone, upper right corner]+    '└': [stone, lower left corner]+    '┘': [stone, lower right corner]+    '─': [stone, horizontal wall]+    '│': [stone, vertical wall]+  upperleft: [0, 0]+  map: |-+    ww...A.AA.A..A.AA....A..ww+    w..A..A.AA........A......w+    .AA..A......M....A....A...+    ....A..A..................+    .A...A.A..................+    ..AA......................+    ...A..A...............┌┐..+    ..A.................┌┐││..+    .....A.............┌────┐.+    ...................│sZss│.+    .A.................└────┘.+    .....A......B==========H=.+    ..........................+    .............┌┐.┌──────┐..+    w......A.....└┘.└──────┘..+    ww........................
+ data/scenarios/Challenges/friend.yaml view
@@ -0,0 +1,94 @@+version: 1+name: Make a Friend+author: Brent Yorgey+description: |+  Win the trust of a furry companion.+creative: false+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    heavy: true+    dir: [0, 1]+    devices:+      - 3D printer+      - ADT calculator+      - antenna+      - branch predictor+      - clock+      - comparator+      - counter+      - dictionary+      - grabber+      - hearing aid+      - lambda+      - logger+      - mirror+      - net+      - scanner+      - strange loop+      - string+      - toolkit+      - workbench+    inventory:+      - [10, ADT calculator]+      - [10, antenna]+      - [10, treads]+      - [10, branch predictor]+      - [10, fish]+      - [10, solar panel]+      - [10, grabber]+      - [10, lambda]+      - [10, strange loop]+      - [10, logger]+      - [10, clock]+      - [10, comparator]+      - [10, calculator]+  - name: cat+    system: true+    dir: [-1, 0]+    display:+      invisible: false+      char: 'c'+      attr: sand+    devices:+      - logger+    program: |+      run "scenarios/Challenges/_friend/cat.sw"+objectives:+  - goal:+      - There's a cat wandering around in the field.  Bring it back to+        your base.  If you give it something it likes, perhaps you can+        get it to follow you.+    condition: |+      c <- robotNamed "cat";+      catLoc <- as c {whereami};+      baseLoc <- as base {whereami};+      return (catLoc == baseLoc)+solution: |+  run "scenarios/Challenges/_friend/friend-solution.sw"+entities:+  - name: fish+    display:+      char: 'f'+    description:+      - A smelly fish. Rather unappetizing to a robot.+    properties: [known, portable]++known: [fish]+seed: 0+world:+  offset: true+  palette:+    'Ω': [grass, null, base]+    '.': [stone]+    ',': [grass]+    'c': [grass, null, cat]+    '*': [grass, flower]+    '@': [stone, boulder]+  upperleft: [-20, 2]+  map: |-+    c,..,,,,,,..,,,,...,.+    ,..,,,,,,,...........+    *,.,@,,,,,,,.....,..Ω
data/scenarios/Challenges/hanoi.yaml view
@@ -57,7 +57,7 @@       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~       // if       //  0. I stand on unlocked X-      //  1. place noth of me is NOT empty+      //  1. place north of me is NOT empty       // then       //  - lock X       run "scenarios/Challenges/hanoi-invariant.sw"@@ -157,7 +157,7 @@ world:   default: [grass, null]   palette:-    ' ': [grass]+    ',': [grass]     '_': [stone]     'v': [stone, null, base]     '┌': [stone, upper left corner]@@ -182,5 +182,5 @@     │2│.│.│     │3│.│.│     └─┴─┴─┘-     ^ ^ ^ -       X   +    ,^,^,^,+    ,,,X,,,
+ data/scenarios/Challenges/ice-cream.yaml view
@@ -0,0 +1,241 @@+version: 1+name: Ice Cream Shop+author: Karl Ostmo+description: |+  Serve a demanding customer+creative: false+seed: 0+objectives:+  - goal:+      - |+        Congratulations on the grand opening of your new ice cream shop.+        You have advertised: "All you can eat, for 1 bitcoin!"+      - |+        A customer is approaching the "Counter". They look hungry!+      - "..."+      - |+        Customer: "`give` me a cone, and then I'll tell you how many scoops I want."+      - "..."+      - |+        Oh dear, you've forgotten to stock your shop with a "calculator".+        Let's hope this order is simple.+    condition: |+      customer <- robotnamed "customer";+      as customer {has "cone"};+  - goal:+      - |+        `give` the customer exactly what they request.+      - |+        Perhaps there is something else around the shop that can assist with+        preparing the order...+    condition: |+      as base {+        has "bitcoin";+      };+robots:+  - name: base+    dir: [0, 1]+    devices:+      - branch predictor+      - treads+      - antenna+      - comparator+      - workbench+      - grabber+      - dictionary+      - lambda+      - logger+      - welder+      - hearing aid+      - scanner+      - strange loop+    inventory:+      - [5000, egg]+      - [5000, milk]+      - [5000, sugar]+      - [5000, ice]+      - [5000, vanilla]+  - name: customer+    system: true+    dir: [0, -1]+    display:+      invisible: false+      attr: 'iron'+    inventory:+      - [1, bitcoin]+    program: |+      run "scenarios/Challenges/_ice-cream/customer.sw"+solution: |+   run "scenarios/Challenges/_ice-cream/solution.sw"+entities:+  - name: scoop+    display:+      char: '@'+    description:+      - A single scoop of vanilla ice cream+    properties: [portable]+  - name: cherry+    display:+      char: '6'+      attr: 'red'+    properties: [portable]+    description:+      - A traditional ice cream garnish+  - name: Counter+    display:+      char: '▒'+      attr: 'sand'+    properties: [portable, known]+    capabilities: [count]+    description:+      - Where customers are served.+      - Also... it can count things.+  - name: cone+    display:+      char: 'V'+      attr: 'sand'+    properties: [portable]+    description:+      - A conical, edible container for ice cream+  - name: egg+    display:+      char: 'e'+      attr: 'gold'+    properties: [portable]+    description:+      - Organic from local, free-range, meticulously pampered chickens.+  - name: milk+    display:+      char: 'm'+      attr: 'silver'+    properties: [portable]+    description:+      - Pasteurized, homogenized, commoditized, metabolized.+  - name: ice+    display:+      char: 'i'+      attr: 'snow'+    properties: [portable]+    description:+      - Ice cream is better cold, don't you think?+  - name: sugar+    display:+      char: 's'+      attr: 'snow'+    properties: [portable]+    description:+      - Basis of most desserts+  - name: vanilla+    display:+      char: 'v'+      attr: 'wood'+    properties: [portable]+    description:+      - Traditional ice cream flavoring+  - name: briefcase+    display:+      char: 'B'+      attr: rubber+    capabilities: [sum, prod]+    properties: [portable]+    description:+      - Standard business accoutrement.+      - Particularly useful for `meet`-ings.+      - Enables the `case` expression.+  - name: awning1+    display:+      char: '▒'+      attr: flower+    description:+      - Decoration+    properties: [known]+  - name: awning2+    display:+      char: '▒'+      attr: snow+    description:+      - Decoration+    properties: [known]+  - name: cone left+    display:+      char: '\'+      attr: wood+    description:+      - Decoration+    properties: [known]+  - name: cone right+    display:+      char: '/'+      attr: wood+    description:+      - Decoration+    properties: [known]+  - name: cone top+    display:+      char: 'M'+      attr: wood+    description:+      - Decoration+    properties: [known]+  - name: cone inside+    display:+      char: 'Z'+      attr: wood+    description:+      - Decoration+    properties: [known]+  - name: decorative scoop+    display:+      char: 'O'+      attr: snow+    description:+      - Decoration+    properties: [known]+recipes:+  - in:+      - [1, egg]+      - [1, milk]+      - [1, sugar]+      - [1, ice]+      - [1, vanilla]+    out:+      - [1, scoop]+known: []+world:+  default: [grass]+  upperleft: [0, 0]+  offset: false+  palette:+    '@': [stone, boulder]+    '.': [grass]+    ':': [stone]+    'B': [stone, briefcase]+    '6': [stone, cherry]+    'V': [stone, cone]+    Ω: [stone, null, base]+    X: [stone, null, customer]+    'y': [stone, awning1]+    'z': [stone, awning2]+    '┌': [stone, upper left corner]+    '┐': [stone, upper right corner]+    '└': [stone, lower left corner]+    '┘': [stone, lower right corner]+    '─': [stone, horizontal wall]+    '│': [stone, vertical wall]+    '\': [stone, cone left]+    '/': [stone, cone right]+    'Z': [stone, cone inside]+    '^': [stone, cone top]+    'O': [stone, decorative scoop]+    'x': [stone, Counter]+  map: |+    ...X..........+    ...:..........+    ...:..........+    ...:..........+    ...:.......OO.+    yzy:yzyz..OOOO+    ┌─┐x┌──┐..^^^^+    │B:Ω:V6│..\ZZ/+    └──────┘...\/.+    
data/scenarios/Challenges/teleport.yaml view
@@ -58,6 +58,7 @@ world:   default: [ice, water]   palette:+    ',': [ice, water]     ' ': [ice, water]     '~': [ice, wavy water]     '*': [grass, flower]@@ -74,9 +75,9 @@   upperleft: [-1, 3]   map: |     ~~   ~         ~    ~-    ~┌─────┐~  ┌─────┐~  +    ~┌─────┐~  ┌─────┐~ ,     ┌┘.....└───┘T...*└┐ ~-    │_....._   _..λ.._│  +    │_....._   _..λ.._│ ,     └┐.....┌───┐*...T┌┘ ~-    ~└─────┘ ~~└─────┘~  +    ~└─────┘ ~~└─────┘~ ,     ~~~   ~        ~    ~
+ data/scenarios/Challenges/wolf-goat-cabbage.yaml view
@@ -0,0 +1,125 @@+version: 1+name: Lake crossing+author: Karl Ostmo+description: |+  Deposit all three items on the eastern shore of the lake.+creative: false+robots:+  - name: base+    display:+      char: 'U'+      attr: wood+    dir: [0, 1]+    devices:+      - ADT calculator+      - branch predictor+      - treads+      - lodestone+      - compass+      - comparator+      - counter+      - dictionary+      - grabber+      - lambda+      - logger+      - mirror+      - net+      - scanner+      - strange loop+      - boat+  - name: east_detector+    system: true+    dir: [0, 1]+    display:+      invisible: true+  - name: west_detector+    system: true+    dir: [0, 1]+    display:+      invisible: true+objectives:+  - goal:+      - |+        Ferry all three of the wolf, goat, and cabbage across the lake.+        However, only one of these can be carried at a time.+      - |+        Furthermore, if left unattended together, the wolf will eat the goat,+        or the goat will eat the cabbage.+    condition: |+      run "data/scenarios/Challenges/_wolf-goat-cabbage/together-on-east-bank.sw";+    prerequisite:+      previewable: true+      logic:+        not:+          or: +            - id: unattended_disaster+            - id: carrying_two_items+  - goal:+      - Carrying two items at once is not allowed!+    condition: |+      as base {+        run "data/scenarios/Challenges/_wolf-goat-cabbage/multi-item-possession.sw";+      };+    id: carrying_two_items+    optional: true+    hidden: true+  - id: unattended_disaster+    teaser: Something was eaten+    goal:+      - Leaving an animal alone with its food is not allowed!+    condition: |+      run "data/scenarios/Challenges/_wolf-goat-cabbage/unattended-together.sw";+    optional: true+    hidden: true    +solution: |+  run "scenarios/Challenges/_wolf-goat-cabbage/cross.sw"+entities:+  - name: wolf+    display:+      char: 'w'+      attr: silver+    description:+      - A wolf. Likes to eat goats.+    properties: [known, portable]+  - name: goat+    display:+      char: 'g'+      attr: sand+    description:+      - A goat. Likes to eat cabbage.+    properties: [known, portable]+  - name: cabbage+    display:+      char: 'c'+      attr: green+    description:+      - A cabbage.+    properties: [known, portable]+known: [water, boulder]+seed: 0+world:+  default: [blank]+  palette:+    'A': [stone, boulder]+    'B': [stone, boulder, base]+    '.': [grass]+    'e': [grass, null, east_detector]+    '~': [dirt, water]+    'w': [grass, wolf]+    'g': [grass, goat]+    'c': [grass, cabbage, west_detector]+  upperleft: [0, 0]+  map: |-+    AAAAAAAAAAAAAAAAAAAAA+    AAAAAAAA~~~~~AAAAAAAA+    AAAAA~~~~~~~~~~~AAAAA+    AAA~~~~~~~~~~~~~~~AAA+    AA~~~~~~~~~~~~~~~~~AA+    Aw~~~~~~~~~~~~~~~~~.A+    Ag~~~~~~~~~~~~~~~~~.A+    Ac~~~~~~~~~~~~~~~~~eA+    AB~~~~~~~~~~~~~~~~~AA+    AAA~~~~~~~~~~~~~~~AAA+    AAAAA~~~~~~~~~~~AAAAA+    AAAAAAAA~~~~~AAAAAAAA+    AAAAAAAAAAAAAAAAAAAAA
+ data/scenarios/Challenges/word-search.yaml view
@@ -0,0 +1,387 @@+version: 1+name: Word search+author: Karl Ostmo+description: |+  Find the word+seed: 2+creative: false+objectives:+  - id: prepare_playfield+    teaser: Wait patiently+    goal:+      - |+        First, wait for the playfield to be set up,+        and then your path forward will be cleared.+      - |+        You can speed this process up with CTRL+x.+    condition: |+      as base {knows "capital C"};+  - goal:+      - Use the `drill` command (e.g. "drill down" when on top of the+        intended letter) to mark the sequence of letters that+        spells COW within the designated playfield.+      - |+        The sequence may appear horizontally in either the+        leftward or rightward direction,+        or vertically in either the upward or downward direction.+        Diagonal appearances are not valid.+    prerequisite: prepare_playfield+    condition: |+      /**+      Algorithm:+      We only need to check the base's+      current position: if we find three contiguous highlights,+      then we know that the player has just completed their+      third highlight.+      */++      def whichOrdinal =+          isC <- ishere "lowercase c";+          if (isC) {+              return 0;+          } {+              isO <- ishere "lowercase o";+              if (isO) {+                  return 1;+              } {+                  isW <- ishere "lowercase w";+                  if (isW) {+                      return 2;+                  } {+                      return (-1);+                  }+              }+          }+          end;+++      def whichHighlightedOrdinal = \str.+          if (str == "lowercase c") {+              return 0;+          } {+              if (str == "lowercase o") {+                  return 1;+              } {+                  if (str == "lowercase w") {+                      return 2;+                  } {+                      return (-1);+                  }+              }+          }+          end;++      def countConsecutive = \expectedOrdinal. \n.++          thisOrdinal <- whichOrdinal;+          nextOrdinal <- if (thisOrdinal == expectedOrdinal) {+              return $ expectedOrdinal + 1;+          } {+              return 0;+          };++          if (nextOrdinal == 3) {+              return true;+          } {+              if (n > 1) {+                  move;+                  countConsecutive nextOrdinal $ n - 1;+              } {+                  return false;+              };+          };+          end;++      def checkBackAndForth =++          foundBackward <- countConsecutive 0 3;+          if (foundBackward) {+              return true;+          } {+              turn back;+              countConsecutive 0 3;+          }+          end;++      def checkDirections = \n.+          if (n > 0) {+              wasFound <- checkBackAndForth;+              if wasFound {+                  return true;+              } {+                  turn left;+                  checkDirections $ n - 1;+              }+          } {+              return false;+          }+          end;++      def isMarkedInDirection = \d.+          scanResult <- scan d;+          ordinalNum <- case scanResult+              (\_. return (-1))+              whichHighlightedOrdinal;+          return $ ordinalNum >= 0;+          end;++      /**+      It's possible we could be one cell away from+      a marked cell after finishing, either due+      to using a directional `drill` command instead of+      `drill down`, or due to an apparent bug which+      does not evaluate the goal condition between the+      `drill` and a `move` command.+      */+      def moveToMarkedCell = \n.+          if (n > 0) {+              isMarkedAhead <- isMarkedInDirection forward;+              if isMarkedAhead {+                  move;+                  return true;+              } {+                  turn left;+                  moveToMarkedCell $ n - 1;+              };+          } {+              return false;+          };+          end;++      /**+      Orient ourselves such that+      a marked cell is behind us.+      */+      def findMarkBehind = \n.+          if (n > 0) {+              isMarkedBehind <- isMarkedInDirection back;+              if isMarkedBehind {+                  return true;+              } {+                  turn left;+                  findMarkBehind $ n - 1;+              };+          } {+              return false;+          };+          end;++      /**+      The cell we're on might be in the middle of a word,+      rather than the end. Determine the orientation of+      the line, then move along it until reaching the end.++      Algorithm:+      0. Assumption: we are currently on a marked cell.+      1. Turn in all all four directions to `scan back`+        for a second marked cell. Stop turning if+        we encounter one.+        If none found after 4 turns, abort.+      2. `scan forward` to see if there is a marked cell in+        the opposite direction.+        `move` (foward) once if there is. Since the word+        is only three cells long, this will be the other+        end of it.+      */+      def moveToWordExtrema =+          foundCellBehind <- findMarkBehind 4;+          if foundCellBehind {+              isMarkedAhead <- isMarkedInDirection forward;+              if isMarkedAhead {+                  move;+              } {};+          } {};+          end;++      def checkSoln =+          isMarkedHere <- isMarkedInDirection down;+          atMarkedCell <- if isMarkedHere {+              return true;+          } {+              moveToMarkedCell 4;+          };++          if atMarkedCell {+              moveToWordExtrema;+              checkDirections 4;+          } {+              return false;+          }+          end;++      as base {checkSoln};++robots:+  - name: base+    display:+      attr: flower+    dir: [-1, 0]+    devices:+      - treads+      - ADT calculator+      - branch predictor+      - GPS receiver+      - clock+      - comparator+      - counter+      - dictionary+      - lambda+      - logger+      - mirror+      - net+      - scanner+      - strange loop+      - string+      - highlighter+    inventory:+      - [3, ink]+  - name: lettersetter+    system: true+    dir: [1, 0]+    display:+      invisible: true+    inventory:+      - [376, capital C]+      - [376, capital O]+      - [376, capital W]+    program: |+      run "scenarios/Challenges/_word-search/create-puzzle.sw"+solution: |+  // Hard-coded for seed=2:+  def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;+  def waitUntilUnblocked =+    x <- blocked;+    if x {+        wait 1;+        waitUntilUnblocked;+    } {};+    end;+  waitUntilUnblocked;+  doN 11 move;+  turn right;+  doN 2 move;+  drill down;+  move;+  drill down;+  move;+  drill down;+  // run "scenarios/Challenges/_word-search/solution.sw"+entities:+  - name: capital C+    display:+      char: 'C'+    description:+      - C is for California+  - name: capital O+    display:+      char: 'O'+    description:+      - O is for Oklahoma+  - name: capital W+    display:+      char: 'W'+    description:+      - W is for Washington+  - name: lowercase c+    display:+      char: 'c'+      attr: gold+    description:+      - c is for cantaloupe+  - name: lowercase o+    display:+      char: 'o'+      attr: gold+    description:+      - o is for orange+  - name: lowercase w+    display:+      char: 'w'+      attr: gold+    description:+      - w is for watermelon+  - name: highlighter+    display:+      char: 'P'+      attr: gold+    capabilities: [drill]+    description:+      - Instrument for marking found words+    properties: [known, portable]+  - name: ink+    display:+      char: 'K'+      attr: gold+    description:+      - Ink for marking found words+    properties: [known, portable]+recipes:+  - in:+      - [1, capital C]+      - [1, ink]+    out:+      - [1, lowercase c]+    required:+      - [1, highlighter]+  - in:+      - [1, capital O]+      - [1, ink]+    out:+      - [1, lowercase o]+    required:+      - [1, highlighter]+  - in:+      - [1, capital W]+      - [1, ink]+    out:+      - [1, lowercase w]+    required:+      - [1, highlighter]+  - in:+      - [1, lowercase c]+    out:+      - [1, capital C]+      - [1, ink]+    required:+      - [1, highlighter]+  - in:+      - [1, lowercase o]+    out:+      - [1, capital O]+      - [1, ink]+    required:+      - [1, highlighter]+  - in:+      - [1, lowercase w]+    out:+      - [1, capital W]+      - [1, ink]+    required:+      - [1, highlighter]+known: [boulder]+world:+  default: [dirt]+  upperleft: [0, 0]+  offset: false+  palette:+    '@': [stone, boulder]+    '.': [grass]+    'a': [grass, null, lettersetter]+    '#': [dirt]+    Ω: [stone, null, base]+  map: |+    a........................###+    .........................###+    .........................###+    .........................###+    .........................###+    .........................###+    .........................###+    .........................###+    .........................###+    .........................@@@+    .........................@Ω@+    .........................@@@+    .........................###+    .........................###+    .........................###
data/scenarios/Fun/00-ORDER.txt view
@@ -1,1 +1,2 @@ GoL.yaml+logo-burst.yaml
+ data/scenarios/Fun/_logo-burst/coordinator.sw view
@@ -0,0 +1,12 @@+def forever : cmd unit -> cmd unit = \c. c ; forever c end++def alternate =+  wait 50;+  say "Green light";+  make "bit (1)";+  wait 100;+  say "Red light";+  make "bit (0)";+  end;++forever alternate;
+ data/scenarios/Fun/_logo-burst/drone.sw view
@@ -0,0 +1,67 @@++def repeat : int -> cmd unit -> cmd unit =+  \n. \c. if (n == 0) {} {c ; repeat (n-1) c}+end+def abs = \n. if (n < 0) {-n} {n} end+def elif = \t. \then. \else. {if t then else} end+def else = \t. t end++def randdir : cmd dir =+  d <- random 4;+  return (+    if (d == 0) {north}+    $ elif (d == 1) {east}+    $ elif (d == 2) {south}+    $ else {west}+  )+end;++def wander =+  d <- randdir;+  turn d;+  dist <- random 4;+  try {repeat dist move} {};+end;++def disperse =+  r <- robotnamed "stoplight";+  greenLight <- as r {has "bit (1)"};+  if greenLight {+    wander;+    disperse;+  } {};+  end;++def converge = \loc. \currentLoc.++  let xDist = fst currentLoc - fst loc in+  let yDist = snd currentLoc - snd loc in++  if (xDist < 0) {+    turn east;+  } {+    if (xDist > 0) {+      turn west;+    } {};+  };+  repeat (abs xDist) move;++  if (yDist < 0) {+    turn north;+  } {+    if (yDist > 0) {+      turn south;+    } {};+  };+  repeat (abs yDist) move;+  end;++def go = \loc.+  disperse;+  currentLoc <- whereami;+  converge loc currentLoc;+  go loc;+  end;++loc <- whereami;+go loc;
+ data/scenarios/Fun/logo-burst.yaml view
@@ -0,0 +1,84 @@+version: 1+name: Logo burst+author: Karl Ostmo+description: |+  Animate the Swarm logo+creative: false+seed: 0+robots:+  # We include a base robot to stabilize the screen at a fixed position.+  - name: base+    display:+      invisible: true+    dir: [1, 0]+    devices:+      - logger+      - hearing aid+  - name: botRight+    system: true+    display:+      invisible: false+    dir: [1, 0]+    program: |+      run "scenarios/Fun/_logo-burst/drone.sw"+  - name: botUp+    system: true+    display:+      invisible: false+    dir: [0, 1]+    program: |+      run "scenarios/Fun/_logo-burst/drone.sw"+  - name: botLeft+    system: true+    display:+      invisible: false+    dir: [-1, 0]+    program: |+      run "scenarios/Fun/_logo-burst/drone.sw"+  - name: botDown+    system: true+    display:+      invisible: false+    dir: [0, -1]+    program: |+      run "scenarios/Fun/_logo-burst/drone.sw"+  - name: stoplight+    system: true+    display:+      invisible: false+    dir: [0, 1]+    devices:+      - lodestone+    inventory:+      - [1, bit (0)]+    program: |+      run "scenarios/Fun/_logo-burst/coordinator.sw"+  +known: [boulder, tree, water, wavy water]+world:+  default: [blank]+  upperleft: [0, 0]+  offset: false+  palette:+    'Ω': [blank, null, base]+    's': [blank, null, stoplight]+    '.': [blank]+    ' ': [blank]+    '@': [blank, boulder]+    'T': [blank, tree]+    '▒': [blank, water]+    '~': [blank, wavy water]+    '>': [blank, null, botRight]+    '^': [blank, null, botUp]+    '<': [blank, null, botLeft]+    'v': [blank, null, botDown]+  map: |+    s                   v                                     .+    .                                                         . +    . v<^vv<<@   ^^     vv    >^v^T    ^^^v<     <><      v   >+    T  ^<        >^  <  >>   v>   v<   >T   <v>  <<▒^  @><>   .+    .   <@v^^>>   @v  >  <T  <v<T<Ω^>   v><v<     << <<T^ v<  . +    . >     >>   v@ @@> <<   ~v   <    >~  ^~    >>  @^  >v   .+    .   >>^v^^^    < ~ T~v    v<   <~   >>T  <v   vv      ▒>  . +    .                             ▒                           . +    .                   ^      ^     v       >      >         .
data/scenarios/Speedruns/curry.yaml view
@@ -1,10 +1,12 @@ version: 1 name: Curry author: Brent Yorgey-description: Race to make a bowl of curry as fast as possible.+description: Race to make a bowl of curry as quickly as possible.   See the Swarm wiki for more information on Swarm speedrunning. objectives:-  - condition: as base {has "curry"}+  - goal:+      - Make a bowl of curry as quickly as possible!+    condition: as base {has "curry"} robots:   - name: base     loc: [0,0]
data/scenarios/Speedruns/forester.yaml view
@@ -4,7 +4,9 @@ description: Race to harvest 1024 trees as quickly as possible.   See the Swarm wiki for more information on Swarm speedrunning. objectives:-  - condition: as base {n <- count "tree"; return (n >= 1024)}+  - goal:+      - Harvest 1024 trees as quickly as possible!+    condition: as base {n <- count "tree"; return (n >= 1024)} robots:   - name: base     loc: [0,0]
data/scenarios/Speedruns/mithril.yaml view
@@ -4,7 +4,9 @@ description: Race to mine some mithril.   See the Swarm wiki for more information on Swarm speedrunning. objectives:-  - condition: as base {has "mithril"}+  - goal:+      - Mine some mithril as quickly as possible!+    condition: as base {has "mithril"} robots:   - name: base     loc: [0,0]
data/scenarios/Testing/00-ORDER.txt view
@@ -13,4 +13,13 @@ 378-objectives.yaml 684-swap.yaml 699-movement-fail+858-inventory+795-prerequisite 710-multi-robot.yaml+920-meet.yaml+955-heading.yaml+397-wrong-missing.yaml+961-custom-capabilities.yaml+956-GPS.yaml+958-isempty.yaml+1034-custom-attributes.yaml
+ data/scenarios/Testing/1034-custom-attributes.yaml view
@@ -0,0 +1,134 @@+version: 1+name: Rainbow color custom attributes+description: |+  Custom attributes with rainbow colors+creative: false+attrs:+  - name: rainbow1+    fg: "#ffadad"+  - name: rainbow2+    fg: "#ffd6a5"+  - name: rainbow3+    fg: "#ffff96"+  - name: rainbow4+    fg: "#caffbf"+  - name: rainbow5+    fg: "#9bf6ff"+  - name: rainbow6+    fg: "#a0c4ff"+  - name: rainbow7+    fg: "#bdb2ff"+  - name: redOnYellow+    fg: "#ff0000"+    bg: ffff00+  - name: cyanOnMagenta+    fg: 00ffff+    bg: "#ff00ff"+  - name: italicAndUnderline+    style:+      - Italic+      - Underline+  - name: boldAndStrikethrough+    style:+      - Bold+      - Strikethrough+entities:+  - name: color1+    display:+      char: '▒'+      attr: rainbow1+    description:+      - c1+    properties: [known]+  - name: color2+    display:+      char: '▒'+      attr: rainbow2+    description:+      - c2+    properties: [known]+  - name: color3+    display:+      char: '▒'+      attr: rainbow3+    description:+      - c3+    properties: [known]+  - name: color4+    display:+      char: '▒'+      attr: rainbow4+    description:+      - c4+    properties: [known]+  - name: color5+    display:+      char: '▒'+      attr: rainbow5+    description:+      - c5+    properties: [known]+  - name: color6+    display:+      char: '▒'+      attr: rainbow6+    description:+      - c6+    properties: [known]+  - name: color7+    display:+      char: '▒'+      attr: rainbow7+    description:+      - c7+    properties: [known]+  - name: redYellow+    display:+      char: 'R'+      attr: redOnYellow+    description:+      - Red on Yellow+    properties: [known]+  - name: cyanMagenta+    display:+      char: 'C'+      attr: cyanOnMagenta+    description:+      - Cyan on Magenta+    properties: [known]+  - name: italicUnderline+    display:+      char: 'X'+      attr: italicAndUnderline+    description:+      - Italic and Underline+    properties: [known]+  - name: boldStrikethrough+    display:+      char: 'X'+      attr: boldAndStrikethrough+    description:+      - Bold and Strikethrough+    properties: [known]+robots: []+world:+  default: [blank]+  palette:+    '.': [blank]+    '1': [blank, color1]+    '2': [blank, color2]+    '3': [blank, color3]+    '4': [blank, color4]+    '5': [blank, color5]+    '6': [blank, color6]+    '7': [blank, color7]+    'R': [blank, redYellow]+    'C': [blank, cyanMagenta]+    'I': [blank, italicUnderline]+    'B': [blank, boldStrikethrough]+  upperleft: [0, 0]+  map: |-+    .1234567..R.....II.+    .1234567..R........+    .1234567.....C..BB.+    .1234567.....C.....
data/scenarios/Testing/201-require/201-require-device-creative.yaml view
@@ -8,10 +8,10 @@       try {         r <- robotNumbered 1;         p <- as r {whereami};-        boatInstalled <- as r {installed "boat"};+        boatEquipped <- as r {equipped "boat"};         b1 <- as r {count "boat"};         b0 <- as base {count "boat"};-        return (p == (2,0) && b0 == 0 && boatInstalled && b1 == 0);+        return (p == (2,0) && b0 == 0 && boatEquipped && b1 == 0);       } { return false } creative: true solution: |
data/scenarios/Testing/201-require/201-require-device-creative1.yaml view
@@ -8,10 +8,10 @@       try {         r <- robotNumbered 1;         p <- as r {whereami};-        boatInstalled <- as r {installed "boat"};+        boatEquipped <- as r {equipped "boat"};         b1 <- as r {count "boat"};         b0 <- as base {count "boat"};-        return (p == (2,0) && b0 == 1 && boatInstalled && b1 == 0);+        return (p == (2,0) && b0 == 1 && boatEquipped && b1 == 0);       } { return false } creative: true solution: |
data/scenarios/Testing/201-require/201-require-device.yaml view
@@ -8,10 +8,10 @@       try {         r <- robotNumbered 1;         p <- as r {whereami};-        boatInstalled <- as r {installed "boat"};+        boatEquipped <- as r {equipped "boat"};         b1 <- as r {count "boat"};         b0 <- as base {count "boat"};-        return (p == (2,0) && b0 == 0 && boatInstalled && b1 == 0);+        return (p == (2,0) && b0 == 0 && boatEquipped && b1 == 0);       } { return false } solution: |   build {require "boat"; move; require "boat"; move}
data/scenarios/Testing/201-require/533-reprogram-simple.yaml view
@@ -1,7 +1,7 @@ version: 1-name: Install devices while reprogramming+name: Equip devices while reprogramming description: |-  While executing 'reprogram', we should install any required devices which+  While executing 'reprogram', we should equip any required devices which   the target robot doesn't have.   https://github.com/swarm-game/swarm/pulls/533 objectives:@@ -14,13 +14,13 @@          fred <- robotNamed "fred";         p <- as fred {whereami};-        boatInstalled <- as fred {installed "boat"};-        drillInstalled <- as fred {installed "metal drill"};-        solarInstalled <- as fred {installed "solar panel"};-        treadsInstalled <- as fred {installed "treads"};+        boatEquipped <- as fred {equipped "boat"};+        drillEquipped <- as fred {equipped "metal drill"};+        solarEquipped <- as fred {equipped "solar panel"};+        treadsEquipped <- as fred {equipped "treads"};          return (p == (2,0)-                && boatInstalled && drillInstalled && solarInstalled && treadsInstalled+                && boatEquipped && drillEquipped && solarEquipped && treadsEquipped                 && base_boats == 1 && base_solar == 1                 && base_treads == 1 && base_drills == 1                );
data/scenarios/Testing/201-require/533-reprogram.yaml view
@@ -1,7 +1,7 @@ version: 1-name: Install devices + entities while reprogramming+name: Equip devices + entities while reprogramming description: |-  While executing 'reprogram', we should install any required devices and+  While executing 'reprogram', we should equip any required devices and   give required entities which the target robot doesn't have.   https://github.com/swarm-game/swarm/pulls/533 objectives:@@ -15,11 +15,11 @@          fred <- robotNamed "fred";         p <- as fred {whereami};-        boatInstalled <- as fred {installed "boat"};-        drillInstalled <- as fred {installed "metal drill"};+        boatEquipped <- as fred {equipped "boat"};+        drillEquipped <- as fred {equipped "metal drill"};         fred_rocks <- as fred {count "rock"}; -        return (p == (2,0) && boatInstalled && drillInstalled+        return (p == (2,0) && boatEquipped && drillEquipped                 && base_boats == 1 && base_solar == 1                 && base_treads == 1 && base_drills == 1                 && base_rocks == 42 && fred_rocks == 8
data/scenarios/Testing/378-objectives.yaml view
@@ -3,7 +3,9 @@ description: |   First, get some trees; then, use them to build a harvester. objectives:-  - goal:+  - id: get_trees+    teaser: Get trees+    goal:       - Your first goal is to get three trees.     condition: |       try {@@ -14,6 +16,7 @@       - Nice job.  Now, build a harvester.     condition: |       try { as base {has "harvester"} } {return false}+    prerequisite: get_trees solution: |   build {turn right; move; move; grab; move; grab; move; grab; turn back; move; move; move; move};   wait 16;
data/scenarios/Testing/394-build-drill.yaml view
@@ -9,7 +9,7 @@       try {         as base {l <- has "detonator"; return (not l)}       } { return false }-# When testing, add `s <- build {...}; n <- as s {whoami}; log n` +# When testing, add `s <- build {...}; n <- as s {whoami}; log n` solution: |   def forever = \c. c ; forever c end;   def unblock = try {drill forward} {} end;@@ -18,7 +18,13 @@   r <- build {     wait 2;     log "Hi, I am builder";-    forever (build {log "Hi, I am pusher"; turn forward; forever push}; log "- robot built")+    forever (+      build {+        log "Hi, I am pusher";+        forever push+      };+      log "- robot built"+    )   };   wait 10;   place "detonator";
+ data/scenarios/Testing/397-wrong-missing.yaml view
@@ -0,0 +1,38 @@+version: 1+name: Test issue 397 (wrong device reported missing)+description: |+  A scenario designed to trigger issue 397, where in certain+  situations involving a capability with no candidate devices,+  an incorrect error message was generated reporting some other+  device to be missing.+  https://github.com/swarm-game/swarm/issues/397+objectives:+  - condition: |+      t <- time; return (t == 2)+    goal:+    - |+      This is a dummy condition that just ensures the base has had+      time to run the problematic `build` command.  The scenario+      *should* generate an error message; what we really care about is+      whether the generated error message is correct, which is checked+      in test/integration/Main.hs .+solution: |+  build {move; turn right; loc <- whereami}+robots:+  - name: base+    dir: [0,1]+    devices:+      - 3D printer+      - logger+    inventory:+      - [1, treads]+      - [1, solar panel]+world:+  default: [blank]+  palette:+    'Ω': [grass, null, base]+    '.': [grass]+  upperleft: [0,1]+  map: |+    .+    Ω
data/scenarios/Testing/562-lodestone.sw view
@@ -32,10 +32,8 @@ until (ishere "lodestone") {grab};  // get two bit (0)-// TODO: require should not be necessary build {   log "Hi!";-  require "branch predictor";   repeat (     log "I am going for a bit";     turn east; m2; x <- until (ishere "bit (0)") {harvest}; turn back; m2; place x;
data/scenarios/Testing/562-lodestone.yaml view
@@ -27,6 +27,7 @@       - branch predictor     inventory:       - [10, drill]+      - [10, lambda]       - [10, logger]       - [10, compass]       - [10, treads]
+ data/scenarios/Testing/795-prerequisite/00-ORDER.txt view
@@ -0,0 +1,4 @@+795-prerequisite-or.yaml+795-prerequisite-and.yaml+795-prerequisite-mutually-exclusive.yaml+795-prerequisite-cycle-with-not.yaml
+ data/scenarios/Testing/795-prerequisite/795-prerequisite-and.yaml view
@@ -0,0 +1,50 @@+version: 1+name: |+  Prerequisite objectives: AND+description: |+  Complete an objective with a prerequisite entailing both of two other objectives.+objectives:+  - goal:+      - Achieve both of two other objectives+    condition: |+      return true;+    prerequisite:+      previewable: true+      logic:+        and:+          - have_furnace+          - have_gear+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    optional: true+  - id: have_gear+    goal:+      - Make a "wooden gear".+    condition: |+      as base {has "wooden gear"};+    optional: true+solution: |+  make "furnace";+  make "wooden gear";+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+      - grabber+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, null, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/795-prerequisite/795-prerequisite-cycle-with-not.yaml view
@@ -0,0 +1,42 @@+version: 1+name: |+  Prerequisite objectives: non-dependency cycle due to the NOT+description: |+  This should NOT be rejected by the parser.+  The two objectives do reference each other,+  but the NOT is a distinct reference from the non-negated goal.+objectives:+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    prerequisite:+      not: have_gear+  - id: have_gear+    goal:+      - Make a "wooden gear".+    condition: |+      as base {has "wooden gear"};+    prerequisite: have_furnace+solution: |+  make "wooden gear";+  make "furnace";+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, null, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/795-prerequisite/795-prerequisite-mutually-exclusive.yaml view
@@ -0,0 +1,61 @@+version: 1+name: |+  Mutually exclusive prerequisites+description: |+  Complete an objective with a prerequisite of either of two other objectives.+objectives:+  - goal:+      - Either obtain a "furnace" WITHOUT a "flower", or obtain both a "wooden gear" and "flower".+    condition: |+      return true;+    prerequisite:+      previewable: true+      logic:+        or:+          - and:+            - id: have_furnace+            - not:+                id: have_flower+          - and:+            - id: have_gear+            - id: have_flower+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    +    optional: true+  - id: have_gear+    goal:+      - Make a "wooden gear".+    condition: |+      as base {has "wooden gear"};+    optional: true+  - id: have_flower+    goal:+      - Possess a "flower".+    condition: |+      as base {has "flower"};+    optional: true+solution: |+  make "furnace"+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+      - grabber+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, flower, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/795-prerequisite/795-prerequisite-or.yaml view
@@ -0,0 +1,49 @@+version: 1+name: |+  Prerequisite objectives: OR+description: |+  Complete an objective with a prerequisite of either of two other objectives.+objectives:+  - goal:+      - Achieve one of two other objectives+    condition: |+      return true;+    prerequisite:+      previewable: true+      logic:+        or:+          - id: have_furnace+          - id: have_gear+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    optional: true+  - id: have_gear+    goal:+      - Make a "wooden gear".+    condition: |+      as base {has "wooden gear"};+    optional: true+solution: |+  make "wooden gear"+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+      - grabber+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, flower, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/858-inventory/00-ORDER.txt view
@@ -0,0 +1,3 @@+858-possession-objective.yaml+858-nonpossession-objective.yaml+858-counting-objective.yaml
+ data/scenarios/Testing/858-inventory/858-counting-objective.yaml view
@@ -0,0 +1,36 @@+version: 1+name: Evaluate count of an item+description: |+  The goal criteria is exactly the same as+  858-possession-objective but evaluated+  with the "count" function instead.+creative: false+objectives:+  - goal:+      - |+        Get 1 tree.+    condition: |-+      as base {+        itemCount <- count "tree";+        return $ itemCount > 0;+      };+robots:+  - name: base+    dir: [0, 1]+    devices:+      - treads+      - scanner+      - grabber+solution: |+  move;+  grab;+known: [tree]+world:+  default: [blank]+  palette:+    'B': [grass, null, base]+    'w': [grass, tree]+  upperleft: [0, 0]+  map: |-+    w+    B
+ data/scenarios/Testing/858-inventory/858-nonpossession-objective.yaml view
@@ -0,0 +1,35 @@+version: 1+name: Evaluate nonpossession of an item+description: |+  Opposite test case of 858-possession-objective+creative: false+objectives:+  - goal:+      - |+        Get rid of 1 tree.+    condition: |-+      as base {+        x <- has "tree";+        return $ not x;+      };+robots:+  - name: base+    dir: [0, 1]+    devices:+      - treads+      - scanner+      - grabber+    inventory:+      - [1, tree]+solution: |+  place "tree";+known: [tree]+world:+  default: [blank]+  palette:+    'B': [grass, null, base]+    'w': [grass]+  upperleft: [0, 0]+  map: |-+    w+    B
+ data/scenarios/Testing/858-inventory/858-possession-objective.yaml view
@@ -0,0 +1,33 @@+version: 1+name: Evaluate possession of an item+description: |+  test case+creative: false+objectives:+  - goal:+      - |+        Get 1 tree.+    condition: |-+      as base {+        has "tree";+      };+robots:+  - name: base+    dir: [0, 1]+    devices:+      - treads+      - scanner+      - grabber+solution: |+  move;+  grab;+known: [tree]+world:+  default: [blank]+  palette:+    'B': [grass, null, base]+    'w': [grass, tree]+  upperleft: [0, 0]+  map: |-+    w+    B
+ data/scenarios/Testing/920-meet.yaml view
@@ -0,0 +1,52 @@+version: 1+name: Test meet and meetAll commands+description: |+  Make sure meet prefers a robot on the same cell, and test meetAll+  by giving everyone a boat.+objectives:+  - condition: |+      r0 <- robotNamed "other0";+      b0 <- as r0 { has "boat" };+      teleport self (0,0);+      b1 <- meetAll (\b. \r. b0 <- as r {has "boat"}; return (b && b0)) true;+      n2 <- as r0 { count "boat" };+      return (b0 && b1 && (n2 == 2))+solution: |+  mr0 <- meet;+  case mr0 (\_. return ()) (\r0. give r0 "boat");+  meetAll (\_. \r. give r "boat") ()+robots:+  - name: base+    loc: [0,0]+    dir: [1,0]+    devices:+      - logger+      - antenna+      - ADT calculator+      - grabber+    inventory:+      - [7, boat]+  - name: other0+    loc: [0,0]+    dir: [1,0]+  - name: other+    dir: [1,0]+world:+  default: [blank]+  palette:+    '.': [grass]+    'Ω': [grass, null]+    'o': [grass, null, other]+    '┌': [stone, upper left corner]+    '┐': [stone, upper right corner]+    '└': [stone, lower left corner]+    '┘': [stone, lower right corner]+    '─': [stone, horizontal wall]+    '│': [stone, vertical wall]+  upperleft: [-2, 2]+  map: |+    ┌───┐+    │.o.│+    │oΩo│+    │.o.│+    └───┘
+ data/scenarios/Testing/955-heading.yaml view
@@ -0,0 +1,28 @@+version: 1+name: Test heading command+description: |+  Test the `heading` command.+  https://github.com/swarm-game/swarm/issues/955+objectives:+  - condition: |+      loc <- as base {whereami};+      h <- as base {heading};+      return $ loc == (1,-1) && h == north+solution: |+  h <- heading; turn east; move; move; turn right; move; move; turn h+robots:+  - name: base+    dir: [0,1]+    devices:+      - treads+      - compass+world:+  default: [blank]+  palette:+    '^': [grass, null, base]+    '.': [grass]+  upperleft: [-1, 1]+  map: |+    ^..+    ...+    ...
+ data/scenarios/Testing/956-GPS.yaml view
@@ -0,0 +1,66 @@+version: 1+name: Test GPS receiver device+description: |+  Test the `whereami` command and GPS receiver device.+  https://github.com/swarm-game/swarm/issues/956+objectives:+  - condition: |+      loc <- as base {whereami};+      return $ loc == (0,0)+    goal:+      - |+        The goal is to move back to the origin after being+        unceremoniously teleported to a random location.  The only way+        to do this is to use a GPS receiver device to learn the+        current location and use the result to decide how to move.+solution: |+  def x = \n. \c. if (n == 0) {} {c ; x (n-1) c} end;+  def abs = \n. if (n<0) {-n} {n} end;+  wait 2;+  loc <- whereami;+  turn (if (fst loc < 0) {east} {west}); x (abs (fst loc)) move;+  turn (if (snd loc < 0) {north} {south}); x (abs (snd loc)) move;+robots:+  - name: base+    dir: [0,1]+    loc: [-5,5]+    devices:+      - clock+      - dictionary+      - GPS receiver+      - treads+      - compass+      - ADT calculator+      - strange loop+      - comparator+      - calculator+      - branch predictor+      - logger+  - name: teleporter+    dir: [0,1]+    loc: [0,0]+    system: true+    display:+      invisible: true+    program: |+      x <- random 11;+      y <- random 11;+      teleport base (x-5, y-5)+world:+  default: [blank]+  palette:+    '.': [grass]+  upperleft: [-5, 5]+  map: |+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+    ...........+
+ data/scenarios/Testing/958-isempty.yaml view
@@ -0,0 +1,57 @@+version: 1+name: Test isempty+description: |+  Test the `isempty` command.+objectives:+  - condition: |+      as base {has "thneed"}+    goal:+      - |+        The goal is to pick up the thing right after the empty+        spot in the row.+solution: |+  def find_thing =+    e <- isempty;+    move;+    if e {} {find_thing}+  end;+  find_thing;+  grab+robots:+  - name: base+    dir: [1,0]+    devices:+      - scanner+      - branch predictor+      - grabber+      - strange loop+      - lambda+      - dictionary+      - logger+      - treads+      - solar panel+entities:+  - name: thneed+    display:+      attr: gold+      char: 'Y'+    description:+    - A thing that everyone needs!+    properties: [portable]+world:+  default: [blank]+  palette:+    '.': [grass]+    '>': [grass, tree, base]+    'o': [grass, rock]+    '$': [grass, LaTeX]+    'i': [grass, cotton]+    '0': [grass, bit (0)]+    '1': [grass, bit (1)]+    'R': [grass, pixel (R)]+    'G': [grass, pixel (G)]+    'B': [grass, pixel (B)]+    'Y': [grass, thneed]+  upperleft: [-5, 5]+  map: |+    >o$i01R.YGB
+ data/scenarios/Testing/961-custom-capabilities.yaml view
@@ -0,0 +1,42 @@+version: 1+name: Test issue 961 (custom entities providing capabilities)+description: |+  Test that custom entities providing a capability are considered+  when equipping a new robot.+  https://github.com/swarm-game/swarm/issues/961+objectives:+  - condition: |+      mover <- robotNumbered 1;+      as mover { loc <- whereami; return $ loc == (0,1) }+    goal:+    - |+      Get a robot to (0,1)!+solution: |+  build {move}+entities:+  - name: wheels+    display:+      attr: device+      char: 'o'+    description:+    - A non-traditional means of locomotion.+    properties: [known, portable]+    capabilities: [move]+robots:+  - name: base+    dir: [0,1]+    devices:+      - 3D printer+      - logger+    inventory:+      - [1, wheels]+      - [1, solar panel]+world:+  default: [blank]+  palette:+    'Ω': [grass, null, base]+    '.': [grass]+  upperleft: [0,1]+  map: |+    .+    Ω
+ data/scenarios/Testing/_Validation/795-prerequisite-cycle.yaml view
@@ -0,0 +1,38 @@+version: 1+name: |+  Prerequisite objectives: dependency cycle+description: |+  This should be rejected by the parser.+objectives:+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    prerequisite: have_gear+  - id: have_gear+    goal:+      - Make a "wooden gear".+    condition: |+      as base {has "wooden gear"};+    prerequisite: have_furnace+solution: |+  make "wooden gear"+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, null, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/_Validation/795-prerequisite-nonexistent-reference.yaml view
@@ -0,0 +1,38 @@+version: 1+name: |+  Prerequisite objectives: Nonexistent reference+description: |+  This scenario should be unparseable due to typo "shave_furnace" vs "have_furnace"+objectives:+  - goal:+      - Achieve one of two other objectives+    condition: |+      return true;+    prerequisite: shave_furnace+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    optional: true+solution: |+  make "furnace"+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+      - grabber+    inventory:+      - [2, board]+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, flower, base]+  upperleft: [0, 0]+  map: |+    x
+ data/scenarios/Testing/_Validation/795-prerequisite-self-reference.yaml view
@@ -0,0 +1,31 @@+version: 1+name: |+  Prerequisite objectives: dependency cycle (self-reference)+description: |+  This should be rejected by the parser.+objectives:+  - id: have_furnace+    goal:+      - Make a "furnace".+    condition: |+      as base {has "furnace"};+    prerequisite: have_furnace+solution: |+  make "furnace"+robots:+  - name: base+    display:+      char: 'Ω'+      attr: robot+    dir: [0, 1]+    devices:+      - workbench+    inventory:+      - [5, rock]+world:+  default: [blank]+  palette:+    'x': [grass, null, base]+  upperleft: [0, 0]+  map: |+    x
data/scenarios/Tutorials/00-ORDER.txt view
@@ -5,9 +5,9 @@ place.yaml types.yaml type-errors.yaml-bind.yaml-install.yaml+equip.yaml build.yaml+bind2.yaml crash.yaml scan.yaml def.yaml
− data/scenarios/Tutorials/bind.yaml
@@ -1,81 +0,0 @@-version: 1-name: Bind notation-description: |-  Learn about command return types and how to bind the results.-objectives:-  - goal:-      - |-        Every command returns a value.  However, some simple commands, like-        `move`, do not have any meaningful-        value to return.  Swarm has a special type, `unit`, with only one value,-        called `()`. Since there is only one possible value of type-        `unit`, returning it does not convey any information.-        Thus, the type of `move` is `cmd unit`.-      - |-        Other commands do return a nontrivial value after executing.-        For example, `grab` has type-        `cmd text`, and returns the name of the-        grabbed entity as a text value. Try it:-      - |-        move; grab-      - |-        To use the result of a command later, you need bind notation, which-        consists of a variable name and a leftwards-pointing arrow-        before the command.  For example:-      - |-        move; t <- grab; place t-      - |-        In the above example, the result returned by `grab` is assigned-        to the variable name `t`, which can then be used later.-        This could be useful, for example, if you do not care what you-        grabbed and just want to move it to another cell, or if you-        are not sure of the name of the thing being grabbed.-      - Once you are done experimenting, do `place "Win"` to finish this challenge.-        (Note, you might need to `grab` the entity you are standing on-        or move to an empty cell first, since each cell can only-        contain at most one entity.)-    condition: |-      try {-        w <- as base {has "Win"};-        return (not w);-      } { return false }-solution: |-  place "Win"-entities:-  - name: Win-    display:-      attr: device-      char: 'W'-    description:-      - Do `place "Win"` once you are done with this challenge.-      - You might need to `grab` the entity you are standing on-        or move to an empty cell first.-    properties: [known, portable]-robots:-  - name: base-    dir: [1,0]-    devices:-      - treads-      - grabber-      - logger-      - compass-      - dictionary-    inventory:-      - [1, Win]-      - [0, tree]-world:-  default: [blank]-  palette:-    '>': [grass, null, base]-    'T': [grass, tree]-    '┌': [stone, upper left corner]-    '┐': [stone, upper right corner]-    '└': [stone, lower left corner]-    '┘': [stone, lower right corner]-    '─': [stone, horizontal wall]-    '│': [stone, vertical wall]-  upperleft: [-1, 1]-  map: |-    ┌───┐-    │>TT│-    └───┘
+ data/scenarios/Tutorials/bind2-solution.sw view
@@ -0,0 +1,11 @@+build {+    move; move;+    turn right;+    move; move; move; move; move; move;+    x <- grab;+    turn left;+    move;+    turn left;+    move; move;+    place x;+}
+ data/scenarios/Tutorials/bind2.yaml view
@@ -0,0 +1,111 @@+version: 1+name: Bind notation+creative: false+description: |+  Learn about command return types and how to bind the results.+objectives:+  - id: grab_artifact+    teaser: Retrieve artifact+    goal:+      - |+        A pedestal stands conspicuously empty in the center of the room.+        Perhaps its intended contents lie nearby?+      - |+        Build a robot to retrieve and restore the mystery artifact to its proper place!+      - |+        Note: If you find yourself stuck, you can select "Start over" from+        the "Quit" (CTRL+q) dialog.+    condition: |+      try {+        p <- robotnamed "floorspot";+        w <- as p {ishere "Hastur"};+        return (not w);+      } { return false }+  - goal:+      - |+        Your robot obtained the misplaced artifact! Next you need to put it back+        on the pedestal.  But neither you nor your robot knows the+        artifact's name.  How can it be placed?+      - |+        Every command returns a value.  However, some simple commands, like+        `move`, do not have any meaningful+        value to return.  Swarm has a special type, `unit`, with only one value,+        called `()`. Since there is only one possible value of type+        `unit`, returning it does not convey any information.+        Thus, the type of `move` is `cmd unit`.+      - |+        Other commands do return a nontrivial value after executing.+        For example, `grab` has type `cmd text`, and returns the name of the+        grabbed entity as a text value.+      - |+        To use the result of a command later, you need "bind notation", which+        consists of a variable name and a leftwards-pointing arrow+        before the command.  For example:+      - |+        move; t <- grab; place t+      - |+        In the above example, the result returned by `grab` is assigned+        to the variable name `t`, which can then be used later.+        This is useful, for example, if you do not care what you+        grabbed and just want to move it to another cell, or if you+        are not sure of the name of the thing being grabbed.+      - |+        Reminder: if you need to start over, your previous commands are+        accessible in the REPL history (up-arrow).+    condition: |+      try {+        p <- robotnamed "pedestal";+        w <- as p {ishere "Hastur"};+        return w;+      } { return false }+    prerequisite: grab_artifact+solution: |+  run "data/scenarios/Tutorials/bind2-solution.sw"+entities:+  - name: Hastur+    display:+      attr: gold+      char: 'H'+    description:+      - The Unspeakable One+    properties: [portable]+robots:+  - name: base+    dir: [0,1]+    display:+      char: Ω+      attr: robot+    devices:+      - 3D printer+    inventory:+      - [1, solar panel]+      - [1, treads]+      - [1, grabber]+  - name: floorspot+    system: true+  - name: pedestal+    system: true+world:+  default: [blank]+  palette:+    '.': [blank, null]+    'Ω': [blank, null, base]+    'x': [dirt, null]+    'H': [blank, Hastur, floorspot]+    's': [stone, null, pedestal]+    '┌': [stone, upper left corner]+    '┐': [stone, upper right corner]+    '└': [stone, lower left corner]+    '┘': [stone, lower right corner]+    '─': [stone, horizontal wall]+    '│': [stone, vertical wall]+  upperleft: [-1, -3]+  map: |+    ┌─────────┐+    │.........│+    │...xxx...│+    │...xsx...│+    │...xxxH..│+    │.........│+    │Ω┌───────┘+    └─┘........
data/scenarios/Tutorials/build.yaml view
@@ -14,11 +14,9 @@       - You can build a robot with `build {COMMANDS}`,         where in place of `COMMANDS` you write the sequence         of commands for the robot to execute (separated by semicolons).-      - Build a robot to harvest the flower and place it next-        to the water.       - |-        TIP: You can use the name of the flower directly ("pickerelweed"),-        or you can use bind notation: `f <- harvest; ... ; place f;`.+        Build a robot to harvest the "flower" and place it next+        to the water.       - |         TIP: Newly built robots start out facing the same         direction as their parent, which in the tutorials will always be north.@@ -26,26 +24,15 @@       try {         teleport self (0,-1);         turn east;-        a <- ishere "pickerelweed";+        a <- ishere "flower";         move;-        b <- ishere "pickerelweed";+        b <- ishere "flower";         move;-        c <- ishere "pickerelweed";+        c <- ishere "flower";         return (a || b || c);       } { return false }-entities:-  - name: pickerelweed-    display:-      attr: flower-      char: '*'-    description:-      - A small plant that grows near the water.-    properties: [known, portable, growable]-    # It would make sense for the plant to NOT grow away from water.-    # But if the player sent a robot that grabbed it and then did not-    # place it, another robot would need to salvage that robot. solution: |-  build {turn right; move; move; t <- harvest; turn right; move; place t}+  build {turn right; move; move; harvest; turn right; move; place "flower"} robots:   - name: base     dir: [0,1]@@ -69,7 +56,7 @@     'Ω': [grass, null, base]     '.': [grass]     '~': [ice, water]-    '*': [grass, pickerelweed]+    '*': [grass, flower]     '┌': [stone, upper left corner]     '┐': [stone, upper right corner]     '└': [stone, lower left corner]
data/scenarios/Tutorials/conditionals.yaml view
@@ -62,9 +62,6 @@   def pick = move; ifC (ishere VSR) {grab; return ()} {} end;   def pickrow = x4 pick; turn back; x4 move end;   build {-    require "treads"; require "branch predictor"; require "grabber";-    require "lambda"; require "scanner";  // #540-     turn south; x4 (move; tL; pickrow; tL); tB; x4 move; x4 (give base VSR)   } robots:
data/scenarios/Tutorials/craft.yaml view
@@ -15,6 +15,11 @@         available recipes.       - Your goal is to make a "branch predictor", so you will have to make         some "branch"es first.+      - |+        Note: when used after opening quotes in the REPL, the Tab key can cycle through+        possible completions of a name. E.g., type:+      - |+        > make "br[Tab][Tab]     condition: |       try {         as base {has "branch predictor"}
data/scenarios/Tutorials/crash-secret.sw view
@@ -15,41 +15,39 @@ end;  // At the beginning all robots can be given Win.-def allOK: robot -> bool = \rob.+def allOK: actor -> bool = \rob.   true end; +myLoc <- whereami;+ // Try to give a robot a Win, filtering out those that were already given a Win. // The robot will also receive instructions, so it **must have a logger!**-def tryGive: text -> (robot -> bool) -> int -> cmd (robot -> bool) = \msg.\f.\i.-  r <- try {-    robotNumbered i;-  } {-    log $ "could not find robot " ++ format i;-    return self-  };-  if (r != self && f r) {-    log $ "found the robot " ++ format i;-    l <- whereami;-    rl <- as r {whereami}; wait 1;  // WHY is this 'wait 1' required???-    if (l != rl) {-      log $ "the robot" ++ format i ++ "is not in my cell";-      return f;+def tryGive: text -> (actor -> bool) -> cmd (actor -> bool) = \msg.+  // (b -> actor -> cmd b) -> b -> cmd b+  meetAll $ \f.\rob.+    if (not $ f rob) {+      log $ "skipping the robot " ++ format rob ++ "because it already has a Win";+      return f     } {-      try {-        reprogram r { log msg; };-        log $ "successfully reprogrammed robot " ++ format i;-        give r "Win";-        log $ "successfully gave Win to robot " ++ format i;+      robLoc <- as rob {whereami};+      if (robLoc != myLoc) {+        log $ "the robot" ++ format rob ++ "is not in my cell";+        return f;       } {-        log $ "the robot " ++ format i ++ "is missing a logger!"-      };-      return (\rob. (rob != r && f rob));+        try {+          reprogram rob { log msg; };+          log $ "successfully reprogrammed robot " ++ format rob;+          give rob "Win";+          log $ "successfully gave Win to robot " ++ format rob;+          return (\r. (rob != r && f rob));+        } {+          log $ "the robot " ++ format rob ++ "is missing a logger!";+          return f;+        };+        +      }     }-  } {-    log $ "skipping the robot " ++ format i;-    return f-  } end;  // -------------------------------------------------------------------------@@ -57,7 +55,7 @@ // -------------------------------------------------------------------------  log "Hi, I am secret";-iterate allOK (foreachF 1 16 $ tryGive+iterate allOK (tryGive   $ "Send a robot to `salvage` me and come back to `give base \"Win\"`.\n"   ++ "When the rescue robot stands where I am and executes `salvage`,\n"   ++ "all my inventory and logs will go to it, including the \"Win\".\n"
data/scenarios/Tutorials/def.yaml view
@@ -46,7 +46,6 @@   def tB = turn back end;   def S = m16; tL; m2; tL; m16; tR; m2; tR; m16 end;   build {-    require "treads";  // #540     turn right; S; f <- harvest; tB; S; give base f   } robots:
+ data/scenarios/Tutorials/equip.yaml view
@@ -0,0 +1,61 @@+version: 1+name: Equip+description: |+  Learn how to equip devices and gain new capabilities.+objectives:+  - goal:+      - You know how to `grab` things lying around, so you are ready to get+        an upgrade!+        By equipping devices, you learn new capabilities which allow you to+        perform more complex commands.+      - Before you start building new robots in the later tutorials, you need+        to gain the "build" capability.+        Try typing `build {}` - you should get an error telling you that you+        need to equip a "3D printer".+      - |+        Fortunately, there is a 3D printer lying nearby.  Go `grab` it, then+        equip it with `equip "3D printer"`.+      - |+        You win by building your first robot:+      - |+        build {}+    condition: |+      try {+        _ <- robotNumbered 1;+        return true;+      } { return false }+solution: |+  turn south; move; grab; equip "3D printer"; build {};+robots:+  - name: base+    dir: [1,0]+    devices:+      - logger+      - treads+      - compass+      - grabber+      - welder+    inventory:+      - [10, solar panel]+      - [10, logger]+known: [3D printer, water]+world:+  default: [blank]+  palette:+    '>': [grass, null, base]+    '.': [grass]+    '~': [ice, water]+    '3': [grass, 3D printer]+    '┌': [stone, upper left corner]+    '┐': [stone, upper right corner]+    '└': [stone, lower left corner]+    '┘': [stone, lower right corner]+    '─': [stone, horizontal wall]+    '│': [stone, vertical wall]+  upperleft: [-1, 1]+  map: |+    ┌───┐+    │>..│+    │3..│+    │~~~│+    └───┘
data/scenarios/Tutorials/farming.sw view
@@ -11,7 +11,7 @@ def while : cmd bool -> {cmd a} -> cmd unit = \test. \body.   ifC test {force body ; while test body} {} end;-def giveall : robot -> text -> cmd unit = \r. \thing.+def giveall : actor -> text -> cmd unit = \r. \thing.   while (has thing) {give r thing} end; def x4 = \c. c; c; c; c end;@@ -20,7 +20,6 @@ def m12 = x12 move end; def next_row = tB; m12; tL; move; tL end; def plant_field : text -> cmd unit = \thing.-  log "planting";   x4 (     x12 (move; place thing; harvest);     next_row@@ -39,14 +38,9 @@   ) end; build {-  require "treads"; require "harvester"; require "logger";-  require "lambda"; require "branch predictor";             // #540   require 1 "lambda";   tB; move; tR; plant_field "lambda"; }; build {-  require "treads"; require "harvester"; require "logger"; require "scanner";-  require "grabber";-  require "lambda"; require "branch predictor"; require "strange loop";  // #540   harvest_lambdas }
data/scenarios/Tutorials/farming.yaml view
@@ -3,7 +3,9 @@ description: |   Learn how to build a farm to grow and harvest items. objectives:-  - goal:+  - id: get_many_lambdas+    teaser: Get 256 lambdas+    goal:       - Lambdas are an essential item for building robots, but they         are somewhat rare in the wild.  Therefore, it makes sense to farm         them in order to create a reliable supply.@@ -47,6 +49,7 @@       - Now go forth and build your swarm!     condition: |       try {as base {has "curry"}} {return false}+    prerequisite: get_many_lambdas solution: |   run "scenarios/Tutorials/farming.sw";   run "scenarios/Tutorials/make_curry.sw";@@ -61,6 +64,7 @@       - 3D printer       - dictionary       - grabber+      - welder       - life support system       - logger       - toolkit
− data/scenarios/Tutorials/install.yaml
@@ -1,60 +0,0 @@-version: 1-name: Install-description: |-  Learn how to install devices and gain new capabilities.-objectives:-  - goal:-      - You know how to `grab` things lying around, so you are ready to get-        an upgrade!-        By installing devices, you learn new capabilities which allow you to-        perform more complex commands.-      - Before you start building new robots in the later tutorials, you need-        to gain the "build" capability.-        Try typing `build {}` - you should get an error telling you that you-        need to install a "3D printer".-      - |-        Fortunately, there is a 3D printer lying nearby.  Go `grab` it, then-        install it on yourself with `install base "3D printer"`.-      - |-        You win by building your first robot:-      - |-        build {log "Hello World!"}-    condition: |-      try {-        _ <- robotNumbered 1;-        return true;-      } { return false }-solution: |-  turn south; move; grab; install base "3D printer"; build {log "Hello World!"};-robots:-  - name: base-    dir: [1,0]-    devices:-      - logger-      - treads-      - compass-      - grabber-    inventory:-      - [10, solar panel]-      - [10, logger]-known: [3D printer, water]-world:-  default: [blank]-  palette:-    '>': [grass, null, base]-    '.': [grass]-    '~': [ice, water]-    '3': [grass, 3D printer]-    '┌': [stone, upper left corner]-    '┐': [stone, upper right corner]-    '└': [stone, lower left corner]-    '┘': [stone, lower right corner]-    '─': [stone, horizontal wall]-    '│': [stone, vertical wall]-  upperleft: [-1, 1]-  map: |-    ┌───┐-    │>..│-    │3..│-    │~~~│-    └───┘
data/scenarios/Tutorials/lambda.yaml view
@@ -34,7 +34,6 @@   def tL = turn left end;   def s = m4; tL; m2; tL; m4; tR; m2; tR end;   build {-    require "treads"; require "lambda";  // #540     turn right;     x4 (x4 s; m4; m2; tR; x4 (x4 move); tL; m2);     grab
data/scenarios/Tutorials/move.yaml view
@@ -3,7 +3,9 @@ description: |   Learn how to move and chain commands. objectives:-  - goal:+  - id: move_to_first_flower+    teaser: Get the flower+    goal:       - Robots can use the `move` command to move forward one unit         in the direction they are currently facing.       - To complete this challenge, move your robot two spaces to the right,@@ -14,7 +16,9 @@     condition: |       r <- robotNamed "check1";       loc <- as r {has "Win"};-  - goal:+  - id: move_along_corridor+    teaser: Down the corridor+    goal:       - Good! Now you need to learn how to effectively repeat actions.       - |         Previously you could move twice by chaining the move command:@@ -29,7 +33,10 @@     condition: |       r <- robotNamed "check2";       loc <- as r {has "Win"};-  - goal:+    prerequisite: move_to_first_flower+  - id: move_northeast_corner+    teaser: To northeast corner+    goal:       - Well done! In addition to `move`, you can use the `turn` command         to turn your robot, for example, `turn right` or `turn east`.       - Switch to the inventory view in the upper left (by clicking on it or typing `Alt+E`)@@ -51,6 +58,7 @@     condition: |       r <- robotNamed "check3";       loc <- as r {has "Win"};+    prerequisite: move_along_corridor   - goal:       - Good job! You are now ready to move and turn on your own.       - To complete this challenge, move your robot to the northeast corner,@@ -61,6 +69,7 @@     condition: |       r <- robotNamed "check4";       loc <- as r {has "Win"};+    prerequisite: move_northeast_corner solution: |   // 0   move;move;
data/scenarios/Tutorials/require.yaml view
@@ -1,24 +1,25 @@ version: 1 name: Require devices description: |-  Learn how to require additional devices that would otherwise not be installed.+  Learn how to require additional devices that would otherwise not be equipped. objectives:   - goal:-      - The `build` command automatically installs devices that it knows+      - The `build` command automatically equips devices on the newly+        built robot that it knows         will be required.  For example, if you `build {move}`, some `treads`-        will automatically be installed on the new robot since it needs+        will automatically be equipped on the new robot since it needs         them to `move`.       - However, sometimes you need a device but `build` can't tell that         you need it. In this case, you can use the special `require`         command to require a particular device.  For example, if you         `build {require "3D printer"; move}`, a 3D printer will be-        installed on the new robot even though it does not execute any+        equipped on the new robot even though it does not execute any         commands that use a 3D printer.       - Your goal is to pick a flower on the other side of the river and         bring it back to your base.  You win when the base has a         `periwinkle` flower in its inventory.       - "Hint: robots will drown in the water unless they have a `boat` device-        installed!"+        equipped!"     condition: |       try {         as base {has "periwinkle"}@@ -34,8 +35,6 @@ solution: |   def m5 = move; move; move; move; move end;   build {-    require "treads";  // #540-     require "boat"; turn right;     m5; f <- grab; turn back; m5; give base f   }
data/scenarios/Tutorials/requireinv.yaml view
@@ -5,8 +5,8 @@ objectives:   - goal:       - In the previous tutorial challenge, you learned how to use-        `require` to require specific devices to be installed.-        Sometimes, instead of requiring installed devices, you require+        `require` to require specific devices to be equipped.+        Sometimes, instead of requiring equipped devices, you require         supplies in your inventory.  In this case, you can write         `require <int> <name>` to require a certain number of copies of         a certain entity to be placed in your inventory.@@ -38,7 +38,6 @@   def mp = move; place "rock" end;   def rr = turn right; move; turn right; x4 mp; turn back; x4 move end;   build {-    require "treads"; require "grabber"; require "lambda"; // #540     require 16 "rock";     x4 rr   };
data/scenarios/Tutorials/world101.yaml view
@@ -3,14 +3,16 @@ description: |   Learn some of the first steps you might take on the new planet. objectives:-  - goal:+  - id: get_trees+    teaser: Get 3 trees+    goal:       - You're ready to graduate to something a bit more complex! This         multi-step tutorial will walk you through a few of the first         steps you might take in exploring the new planet, using many         of the commands and techniques you have learned in the         previous tutorial challenges.       - You can see that your base starts out with some key devices-        installed and some basic supplies for building robots.  To+        equipped and some basic supplies for building robots.  To         build more advanced devices and produce more robots,         you'll need to explore, gather resources, and set up some         automated production pipelines.@@ -26,7 +28,9 @@         n <- as base {count "tree"};         return (n >= 3)       } { return false }-  - goal:+  - id: get_harvester+    teaser: Make a harvester+    goal:       - Nice work!  Now, use the trees to make a harvester device.         This will require several intermediate products; try making         various things, and take a look at your available recipes (F3)@@ -34,6 +38,7 @@         you may end up needing some additional trees.     condition: |       try { as base {has "harvester"} } {return false}+    prerequisite: get_trees   - goal:       - Now that you have a harvester, you can use `harvest` instead of `grab`         whenever you pick up a growing item (check for the word "growing" at the@@ -50,6 +55,7 @@         coordinates."     condition: |       try { as base {has "lambda"} } {return false}+    prerequisite: get_harvester robots:   - name: base     display:@@ -61,6 +67,7 @@       - 3D printer       - dictionary       - grabber+      - welder       - life support system       - logger       - toolkit
data/scenarios/classic.yaml view
@@ -13,6 +13,7 @@       - 3D printer       - dictionary       - grabber+      - welder       - life support system       - logger       - toolkit
+ data/test/language-snippets/warnings/unused-vars/multiple-lambda-first-unused.sw view
@@ -0,0 +1,3 @@+def put = \x. \y.+    place y;+    end;
+ data/test/language-snippets/warnings/unused-vars/multiple-lambda-second-unused.sw view
@@ -0,0 +1,3 @@+def put = \x. \y.+    place x;+    end;
+ data/test/language-snippets/warnings/unused-vars/multiple-let-all-unused.sw view
@@ -0,0 +1,3 @@+let x = "foo" in+let y = "bar" in+place "quux";
+ data/test/language-snippets/warnings/unused-vars/multiple-let-first-unused.sw view
@@ -0,0 +1,3 @@+let x = "foo" in+let y = "bar" in+place y;
+ data/test/language-snippets/warnings/unused-vars/multiple-let-second-unused.sw view
@@ -0,0 +1,3 @@+let x = "foo" in+let y = "bar" in+place x;
+ data/test/language-snippets/warnings/unused-vars/recursive-let.sw view
@@ -0,0 +1,1 @@+let one = 1 in let fac = \n. if (n == 0) {one} {n * fac (n-1)} in 17
+ data/test/language-snippets/warnings/unused-vars/shadowed-variable-lambda-unused.sw view
@@ -0,0 +1,1 @@+def y = \x. \x. x + 1 end
+ data/test/language-snippets/warnings/unused-vars/shadowed-variable-let-intermediate-use.sw view
@@ -0,0 +1,4 @@+let x = "flower" in+place x;+x <- grab;+give parent x;
+ data/test/language-snippets/warnings/unused-vars/shadowed-variable-let-unused.sw view
@@ -0,0 +1,3 @@+let x = "flower" in+x <- grab;+give parent x;
+ data/test/language-snippets/warnings/unused-vars/single-bind-unused.sw view
@@ -0,0 +1,2 @@+x <- grab;+place "foo";
+ data/test/language-snippets/warnings/unused-vars/single-bind-used.sw view
@@ -0,0 +1,2 @@+x <- grab;+place x;
editors/emacs/swarm-mode.el view
@@ -45,6 +45,8 @@                "format"                "chars"                "split"+               "charat"+               "tochar"              ))              (x-commands '(                "noop"@@ -56,10 +58,11 @@                "harvest"                "place"                "give"-               "install"+               "equip"+               "unequip"                "make"                "has"-               "installed"+               "equipped"                "count"                "drill"                "build"@@ -73,10 +76,14 @@                "create"                "time"                "whereami"+               "heading"                "blocked"                "scan"                "upload"                "ishere"+               "isempty"+               "meet"+               "meetall"                "whoami"                "setname"                "random"@@ -90,17 +97,17 @@                "robotnamed"                "robotnumbered"                "knows"-               "left"-               "right"-               "back"-               "forward"                "north"                "south"                "east"                "west"+               "left"+               "right"+               "back"+               "forward"                "down"              ))-             (x-types '("int" "text" "dir" "bool" "cmd" "void" "unit"))+             (x-types '("int" "text" "dir" "bool" "cmd" "void" "unit" "actor"))               (x-keywords-regexp (regexp-opt x-keywords 'words))              (x-builtins-regexp (regexp-opt x-builtins 'words))
editors/vscode/syntaxes/swarm.tmLanguage.json view
@@ -29,7 +29,7 @@ 				}, 				{ 				"name": "keyword.control.dictionary.let",-				"begin": "\\s*let\\s+(\\w+)\\s*(:((\\s*(cmd|dir|text|int|void|unit|\\(|\\)|\\{|\\}|(\\*|\\+|->)|[a-z]\\w*|forall ([a-z]\\w*\\s*)+.)\\s*)+))?=",+				"begin": "\\s*let\\s+(\\w+)\\s*(:((\\s*(cmd|dir|text|int|void|unit|actor|\\(|\\)|\\{|\\}|(\\*|\\+|->)|[a-z]\\w*|forall ([a-z]\\w*\\s*)+.)\\s*)+))?=", 				"end": "\\s*in", 				"beginCaptures": { 					"1": {"name": "entity.name.function"},@@ -58,7 +58,7 @@ 				}, 				{ 				"name": "keyword.other",-				"match": "\\b(?i)(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|chars|split|noop|wait|selfdestruct|move|turn|grab|harvest|place|give|install|make|has|installed|count|drill|build|salvage|reprogram|say|listen|log|view|appear|create|time|whereami|blocked|scan|upload|ishere|whoami|setname|random|run|return|try|swap|atomic|teleport|as|robotnamed|robotnumbered|knows)\\b"+				"match": "\\b(?i)(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|chars|split|charat|tochar|noop|wait|selfdestruct|move|turn|grab|harvest|place|give|equip|unequip|make|has|equipped|count|drill|build|salvage|reprogram|say|listen|log|view|appear|create|time|whereami|heading|blocked|scan|upload|ishere|isempty|meet|meetall|whoami|setname|random|run|return|try|swap|atomic|teleport|as|robotnamed|robotnumbered|knows)\\b" 				} 			] 			},@@ -89,7 +89,7 @@ 			"patterns": [ 				{ 				"name": "variable.language.dir",-				"match": "\\b(?i)(left|right|back|forward|north|south|east|west|down)\\b"+				"match": "\\b(?i)(north|south|east|west|left|right|back|forward|down)\\b" 				}, 				{ 				"name": "variable.parameter",
example/BFS-clear.sw view
@@ -63,4 +63,4 @@   give base "tree";   selfdestruct; end;-def start : cmd robot = build {turn west; repeat 7 move; clear} end+def start : cmd actor = build {turn west; repeat 7 move; clear} end
example/list.sw view
@@ -148,7 +148,7 @@ // nil : listI def nil = 0 end -// Add non-negative number to beggining of list (cons adds the sign)+// Add non-negative number to beginning of list (cons adds the sign) // consP : nat -> listI -> int def consP = \x.\xs.   if (x == 0)@@ -159,7 +159,7 @@     } end -// Add integer to the beggining of the list+// Add integer to the beginning of the list // consP : int -> listI -> listI def cons = \x.\xs.   if (x >= 0)
+ src/Data/BoolExpr.hs view
@@ -0,0 +1,337 @@+{-+Copyright (c) 2008, 2009, Nicolas Pouillard+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of the copyright holders nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE NoDeriveAnyClass #-}++--------------------------------------------------------------------++--------------------------------------------------------------------++-- |+-- Module    : Data.BoolExpr+-- Copyright : (c) Nicolas Pouillard 2008,2009+-- License   : BSD3+--+-- Maintainer: Nicolas Pouillard <nicolas.pouillard@gmail.com>+-- Stability : provisional+-- Portability:+--+-- Boolean expressions and various representations.+module Data.BoolExpr (+  -- * A boolean class+  Boolean (..),++  -- * Boolean trees+  BoolExpr (..),+  reduceBoolExpr,+  evalBoolExpr,++  -- * Boolean evaluation semantic+  Eval (..),+  runEvalId,++  -- * Signed constants+  Signed (..),+  negateSigned,+  evalSigned,+  constants,+  negateConstant,++  -- * Conjunctive Normal Form+  CNF (..),+  Conj (..),+  boolTreeToCNF,++  -- * Disjunctive Normal Form+  Disj (..),+  DNF (..),+  boolTreeToDNF,++  -- * Other transformations+  dualize,+  fromBoolExpr,+  pushNotInwards,+)+where++import Control.Monad (ap)+import Data.Aeson+import Data.Char (toLower)+import Data.Traversable+import GHC.Generics (Generic)++-- | Signed values are either positive or negative.+data Signed a = Positive a | Negative a+  deriving (Eq, Ord, Generic, Show, Read)++instance Functor Signed where+  fmap f (Positive x) = Positive (f x)+  fmap f (Negative x) = Negative (f x)++instance Traversable Signed where+  traverse f (Positive x) = Positive <$> f x+  traverse f (Negative x) = Negative <$> f x++instance Foldable Signed where+  foldMap = foldMapDefault++instance Applicative Signed where+  pure = Positive+  (<*>) = ap++instance Monad Signed where+  Positive x >>= f = f x+  Negative x >>= f = negateSigned $ f x++infix 9 /\+infix 9 \/++-- | A boolean type class.+class Boolean f where+  (/\) :: f a -> f a -> f a+  (\/) :: f a -> f a -> f a+  bNot :: f a -> f a+  bTrue :: f a+  bFalse :: f a+  bConst :: Signed a -> f a++-- | Syntax of boolean expressions parameterized over a+-- set of leaves, named constants.+data BoolExpr a+  = BAnd (BoolExpr a) (BoolExpr a)+  | BOr (BoolExpr a) (BoolExpr a)+  | BNot (BoolExpr a)+  | BTrue+  | BFalse+  | BConst (Signed a)+  deriving (Eq, Ord, Generic, Show {-! derive : Arbitrary !-})++encodingOptions :: Options+encodingOptions =+  defaultOptions+    { sumEncoding = ObjectWithSingleField+    , constructorTagModifier = map toLower+    }++instance (ToJSON a) => ToJSON (Signed a) where+  toJSON = genericToJSON encodingOptions++instance (ToJSON a) => ToJSON (BoolExpr a) where+  toJSON = genericToJSON encodingOptions++instance (ToJSON a) => ToJSON (DNF a) where+  toJSON = genericToJSON encodingOptions++instance (ToJSON a) => ToJSON (CNF a) where+  toJSON = genericToJSON encodingOptions++instance (ToJSON a) => ToJSON (Conj a) where+  toJSON = genericToJSON encodingOptions++instance (ToJSON a) => ToJSON (Disj a) where+  toJSON = genericToJSON encodingOptions++instance Functor BoolExpr where+  fmap f (BAnd a b) = BAnd (fmap f a) (fmap f b)+  fmap f (BOr a b) = BOr (fmap f a) (fmap f b)+  fmap f (BNot t) = BNot (fmap f t)+  fmap _ BTrue = BTrue+  fmap _ BFalse = BFalse+  fmap f (BConst x) = BConst (fmap f x)++instance Traversable BoolExpr where+  traverse f (BAnd a b) = BAnd <$> traverse f a <*> traverse f b+  traverse f (BOr a b) = BOr <$> traverse f a <*> traverse f b+  traverse f (BNot t) = BNot <$> traverse f t+  traverse _ BTrue = pure BTrue+  traverse _ BFalse = pure BFalse+  traverse f (BConst x) = BConst <$> traverse f x++instance Foldable BoolExpr where+  foldMap = foldMapDefault++instance Boolean BoolExpr where+  (/\) = BAnd+  (\/) = BOr+  bNot = BNot+  bTrue = BTrue+  bFalse = BFalse+  bConst = BConst++newtype Eval b a = Eval {runEval :: (a -> b) -> b}++runEvalId :: Eval a a -> a+runEvalId e = runEval e id++instance b ~ Bool => Boolean (Eval b) where+  (/\) = liftE2 (&&)+  (\/) = liftE2 (||)+  bNot = liftE not+  bTrue = Eval $ const True+  bFalse = Eval $ const False+  bConst = Eval . flip evalSigned++liftE :: (b -> b) -> Eval b a -> Eval b a+liftE f (Eval x) = Eval (f . x)++liftE2 :: (b -> b -> b) -> Eval b a -> Eval b a -> Eval b a+liftE2 f (Eval x) (Eval y) = Eval (\e -> f (x e) (y e))++-- | Turns a boolean tree into any boolean type.+fromBoolExpr :: Boolean f => BoolExpr a -> f a+fromBoolExpr (BAnd l r) = fromBoolExpr l /\ fromBoolExpr r+fromBoolExpr (BOr l r) = fromBoolExpr l \/ fromBoolExpr r+fromBoolExpr (BNot t) = bNot $ fromBoolExpr t+fromBoolExpr BTrue = bTrue+fromBoolExpr BFalse = bFalse+fromBoolExpr (BConst c) = bConst c++--- | Disjunction of atoms ('a')+newtype Disj a = Disj {unDisj :: [a]}+  deriving (Show, Generic, Functor, Semigroup, Monoid)++--- | Conjunction of atoms ('a')+newtype Conj a = Conj {unConj :: [a]}+  deriving (Show, Generic, Functor, Semigroup, Monoid)++--- | Conjunctive Normal Form+newtype CNF a = CNF {unCNF :: Conj (Disj (Signed a))}+  deriving (Show, Generic, Semigroup, Monoid)++--- | Disjunctive Normal Form+newtype DNF a = DNF {unDNF :: Disj (Conj (Signed a))}+  deriving (Show, Generic, Semigroup, Monoid)++instance Functor CNF where+  fmap f (CNF x) = CNF (fmap (fmap (fmap f)) x)++instance Boolean CNF where+  l /\ r = l `mappend` r+  l \/ r =+    CNF $+      Conj+        [ x `mappend` y | x <- unConj $ unCNF l, y <- unConj $ unCNF r+        ]+  bNot = error "bNot on CNF"+  bTrue = CNF $ Conj []+  bFalse = CNF $ Conj [Disj []]+  bConst x = CNF $ Conj [Disj [x]]++instance Functor DNF where+  fmap f (DNF x) = DNF (fmap (fmap (fmap f)) x)++instance Boolean DNF where+  l /\ r =+    DNF $+      Disj+        [ x `mappend` y | x <- unDisj $ unDNF l, y <- unDisj $ unDNF r+        ]+  l \/ r = l `mappend` r+  bNot = error "bNot on CNF"+  bTrue = DNF $ Disj [Conj []]+  bFalse = DNF $ Disj []+  bConst x = DNF $ Disj [Conj [x]]++-- | Reduce a boolean tree annotated by booleans to a single boolean.+reduceBoolExpr :: BoolExpr Bool -> Bool+reduceBoolExpr = evalBoolExpr id++-- Given a evaluation function of constants, returns an evaluation+-- function over boolean trees.+--+-- Note that since 'BoolExpr' is a functor, one can simply use+-- 'reduceBoolExpr':+--+-- @+-- evalBoolExpr f = reduceBoolExpr . fmap (f$)+-- @+evalBoolExpr :: (a -> Bool) -> (BoolExpr a -> Bool)+evalBoolExpr env expr = runEval (fromBoolExpr expr) env++-- | Returns constants used in a given boolean tree, these+-- constants are returned signed depending one how many+-- negations stands over a given constant.+constants :: BoolExpr a -> [Signed a]+constants = go True+ where+  go sign (BAnd a b) = go sign a ++ go sign b+  go sign (BOr a b) = go sign a ++ go sign b+  go sign (BNot t) = go (not sign) t+  go _ BTrue = []+  go _ BFalse = []+  go sign (BConst x) = [if sign then x else negateSigned x]++dualize :: Boolean f => BoolExpr a -> f a+dualize (BAnd l r) = dualize l \/ dualize r+dualize (BOr l r) = dualize l /\ dualize r+dualize BTrue = bFalse+dualize BFalse = bTrue+dualize (BConst c) = negateConstant c+dualize (BNot e) = fromBoolExpr e++-- When dualize is used by pushNotInwards not BNot remain,+-- hence it makes sense to assert that dualize does not+-- have to work on BNot. However `dualize` can be freely+-- used as a fancy `bNot`.+-- dualize (BNot _)   = error "dualize: impossible"++-- | Push the negations inwards as much as possible.+-- The resulting boolean tree no longer use negations.+pushNotInwards :: Boolean f => BoolExpr a -> f a+pushNotInwards (BAnd l r) = pushNotInwards l /\ pushNotInwards r+pushNotInwards (BOr l r) = pushNotInwards l \/ pushNotInwards r+pushNotInwards (BNot t) = dualize $ pushNotInwards t+pushNotInwards BTrue = bTrue+pushNotInwards BFalse = bFalse+pushNotInwards (BConst c) = bConst c++-- | Convert a boolean tree to a conjunctive normal form.+boolTreeToCNF :: BoolExpr a -> CNF a+boolTreeToCNF = pushNotInwards++-- | Convert a boolean tree to a disjunctive normal form.+boolTreeToDNF :: BoolExpr a -> DNF a+boolTreeToDNF = pushNotInwards++evalSigned :: (a -> Bool) -> Signed a -> Bool+evalSigned f (Positive x) = f x+evalSigned f (Negative x) = not $ f x++negateSigned :: Signed a -> Signed a+negateSigned (Positive x) = Negative x+negateSigned (Negative x) = Positive x++negateConstant :: Boolean f => Signed a -> f a+negateConstant = bConst . negateSigned
+ src/Data/BoolExpr/Simplify.hs view
@@ -0,0 +1,57 @@+-- | Simplification logic for boolean expressions that is not+-- provided in the 'boolexpr' package.+module Data.BoolExpr.Simplify (+  cannotBeTrue,+  replace,+) where++import Data.BoolExpr+import Data.List qualified as L+import Data.Map (Map)+import Data.Map qualified as M+import Data.Set qualified as S++-- | Used only by "hasContradiction".+-- Note that the Booleans returned in this tuple are not actually used+-- as conditions, and therefore their semantic convention (e.g. associating+-- True = Positive and False = Negative) is irrelevant.+-- Rather, they are collected into sets+-- to determine whether both True and False exist for a key.+extractConstFromSigned :: Signed a -> (a, Bool)+extractConstFromSigned v = case v of+  Negative x -> (x, False)+  Positive x -> (x, True)++hasContradiction :: Ord a => Conj (Signed a) -> Bool+hasContradiction (Conj items) =+  not $+    M.null $+      M.filter ((> 1) . S.size) $+        M.fromListWith (<>) $+          fmap (fmap S.singleton . extractConstFromSigned) items++simplifyDNF :: Ord a => DNF a -> DNF a+simplifyDNF (DNF (Disj disjunctions)) =+  DNF $ Disj $ L.filter (not . hasContradiction) disjunctions++isAlwaysFalse :: Ord a => DNF a -> Bool+isAlwaysFalse (DNF (Disj disjunctions)) = L.null disjunctions++cannotBeTrue :: Ord a => BoolExpr a -> Bool+cannotBeTrue = isAlwaysFalse . simplifyDNF . boolTreeToDNF++replace :: Ord a => Map a Bool -> BoolExpr a -> BoolExpr a+replace f (BAnd a b) = BAnd (replace f a) (replace f b)+replace f (BOr a b) = BOr (replace f a) (replace f b)+replace f (BNot t) = BNot (replace f t)+replace _ BTrue = BTrue+replace _ BFalse = BFalse+replace m c@(BConst x) = case M.lookup varname m of+  Nothing -> c+  Just val ->+    if txform val+      then BTrue+      else BFalse+ where+  (varname, isPositive) = extractConstFromSigned x+  txform = if isPositive then id else not
src/Swarm/App.hs view
@@ -13,16 +13,17 @@ import Brick import Brick.BChan import Control.Concurrent (forkIO, threadDelay)-import Control.Lens ((%~), (&), (?~), (^.))+import Control.Lens (view, (%~), (&), (?~)) import Control.Monad.Except import Data.IORef (newIORef, writeIORef) import Data.Text qualified as T import Data.Text.IO qualified as T import Graphics.Vty qualified as V-import Swarm.Game.Robot (LogSource (ErrorTrace, Said))-import Swarm.TUI.Attr+import Swarm.Game.Robot (ErrorLevel (..), LogSource (ErrorTrace, Said)) import Swarm.TUI.Controller import Swarm.TUI.Model+import Swarm.TUI.Model.StateUpdate+import Swarm.TUI.Model.UI (uiAttrMap) import Swarm.TUI.View import Swarm.Version (getNewerReleaseVersion) import Swarm.Web@@ -38,7 +39,7 @@     , appChooseCursor = chooseCursor     , appHandleEvent = eventHandler     , appStartEvent = enablePasteMode-    , appAttrMap = const swarmAttrMap+    , appAttrMap = view $ uiState . uiAttrMap     }  -- | The main @IO@ computation which initializes the state, sets up@@ -74,27 +75,30 @@         writeBChan chan (UpstreamVersion upRel)        -- Start the web service with a reference to the game state-      gsRef <- newIORef (s ^. gameState)-      eport <- Swarm.Web.startWebThread (userWebPort opts) gsRef+      appStateRef <- newIORef s+      eport <- Swarm.Web.startWebThread (userWebPort opts) appStateRef        let logP p = logEvent Said ("Web API", -2) ("started on :" <> T.pack (show p))-      let logE e = logEvent ErrorTrace ("Web API", -2) (T.pack e)+      let logE e = logEvent (ErrorTrace Error) ("Web API", -2) (T.pack e)       let s' =-            s & runtimeState-              %~ case eport of-                Right p -> (webPort ?~ p) . (eventLog %~ logP p)-                Left e -> eventLog %~ logE e+            s+              & runtimeState+                %~ case eport of+                  Right p -> (webPort ?~ p) . (eventLog %~ logP p)+                  Left e -> eventLog %~ logE e        -- Update the reference for every event       let eventHandler e = do             curSt <- get-            liftIO $ writeIORef gsRef (curSt ^. gameState)+            liftIO $ writeIORef appStateRef curSt             handleEvent e -      -- Run the app.-      let buildVty = V.mkVty V.defaultConfig+      -- Setup virtual terminal+      let buildVty = V.mkVty $ V.defaultConfig {V.colorMode = colorMode opts}       initialVty <- buildVty       V.setMode (V.outputIface initialVty) V.Mouse True++      -- Run the app.       void $ customMain initialVty buildVty (Just chan) (app eventHandler) s'  -- | A demo program to run the web service directly, without the terminal application.@@ -111,14 +115,15 @@           , scriptToRun = Nothing           , autoPlay = False           , cheatMode = False+          , colorMode = Nothing           , userWebPort = Nothing           , repoGitInfo = Nothing           }   case res of     Left errMsg -> T.putStrLn errMsg     Right s -> do-      gsRef <- newIORef (s ^. gameState)-      webMain Nothing demoPort gsRef+      appStateRef <- newIORef s+      webMain Nothing demoPort appStateRef  where   demoScenario = Just "./data/scenarios/Testing/475-wait-one.yaml" 
src/Swarm/DocGen.hs view
@@ -45,7 +45,7 @@ import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities) import Swarm.Game.Entity qualified as E import Swarm.Game.Recipe (Recipe, loadRecipes, recipeInputs, recipeOutputs, recipeRequirements, recipeTime, recipeWeight)-import Swarm.Game.Robot (installedDevices, instantiateRobot, robotInventory)+import Swarm.Game.Robot (equippedDevices, instantiateRobot, robotInventory) import Swarm.Game.Scenario (Scenario, loadScenario, scenarioRobots) import Swarm.Game.WorldGen (testWorld2Entites) import Swarm.Language.Capability (Capability)@@ -54,7 +54,7 @@ import Swarm.Language.Syntax (Const (..)) import Swarm.Language.Syntax qualified as Syntax import Swarm.Language.Typecheck (inferConst)-import Swarm.Util (getDataFileNameSafe, isRightOr)+import Swarm.Util (getDataFileNameSafe, isRightOr, listEnums, quote) import Text.Dot (Dot, NodeId, (.->.)) import Text.Dot qualified as Dot import Witch (from)@@ -105,7 +105,7 @@               putStrLn $ "-- " <> show et               putStrLn $ replicate 40 '-'               generateEditorKeywords et-        mapM_ editorGen [minBound .. maxBound]+        mapM_ editorGen listEnums   CheatSheet address s -> case s of     Nothing -> error "Not implemented for all Wikis"     Just st -> case st of@@ -161,8 +161,6 @@ editorList = \case   Emacs -> T.unlines . map (("  " <>) . quote)   VSCode -> T.intercalate "|"- where-  quote = T.cons '"' . flip T.snoc '"'  constSyntax :: Const -> Text constSyntax = Syntax.syntax . Syntax.constInfo@@ -306,7 +304,7 @@   header = [listToRow mw capabilityHeader, separatingLine mw]  capabilityPage :: PageAddress -> EntityMap -> Text-capabilityPage a em = capabilityTable a em [minBound .. maxBound]+capabilityPage a em = capabilityTable a em listEnums  -- --------- -- Entities@@ -321,7 +319,7 @@     escapeTable     [ codeQuote . T.singleton $ e ^. entityDisplay . to displayChar     , addLink ("#" <> linkID) $ view entityName e-    , T.intercalate ", " $ Capability.capabilityName <$> view E.entityCapabilities e+    , T.intercalate ", " $ Capability.capabilityName <$> Set.toList (view E.entityCapabilities e)     , T.intercalate ", " . map tshow . filter (/= E.Portable) $ toList props     , if E.Portable `elem` props         then ":heavy_check_mark:"@@ -351,7 +349,7 @@       <> [T.intercalate "\n\n" $ view E.entityDescription e]  where   props = view E.entityProperties e-  caps = view E.entityCapabilities e+  caps = Set.toList $ view E.entityCapabilities e  entitiesPage :: PageAddress -> [Entity] -> Text entitiesPage _a es =@@ -415,9 +413,9 @@   world <- diamond "World"   base <- diamond "Base"   -- ---------------------------------------------------------------------------  -- add nodes with for all the known entites+  -- add nodes with for all the known entities   let enames' = toList . Map.keysSet . entitiesByName $ emap-      enames = filter (`Set.notMember` ignoredEntites) enames'+      enames = filter (`Set.notMember` ignoredEntities) enames'   ebmap <- Map.fromList . zip enames <$> mapM (box . unpack) enames   -- --------------------------------------------------------------------------   -- getters for the NodeId based on entity name or the whole entity@@ -425,7 +423,7 @@       getE = safeGetEntity ebmap       nid = getE . view entityName   -- ---------------------------------------------------------------------------  -- Get the starting inventories, entites present in the world and compute+  -- Get the starting inventories, entities present in the world and compute   -- how hard each entity is to get - see 'recipeLevels'.   let devs = startingDevices classic       inv = startingInventory classic@@ -439,13 +437,13 @@     mapM_ ((base ---<>) . nid) devs     mapM_ ((base .->.) . nid . fst) $ Map.toList inv   -- ---------------------------------------------------------------------------  -- World entites+  -- World entities   (_wc, ()) <- Dot.cluster $ do     Dot.attribute ("style", "filled")     Dot.attribute ("color", "forestgreen")     mapM_ ((uncurry (Dot..->.) . (world,)) . getE) (toList testWorld2Entites)   -- ---------------------------------------------------------------------------  let -- put a hidden node above and below entites and connect them by hidden edges+  let -- put a hidden node above and below entities and connect them by hidden edges       wrapBelowAbove :: Set Entity -> Dot (NodeId, NodeId)       wrapBelowAbove ns = do         b <- hiddenNode@@ -454,7 +452,7 @@         mapM_ (b .~>.) ns'         mapM_ (.~>. t) ns'         return (b, t)-      -- put set of entites in nice+      -- put set of entities in nice       subLevel :: Int -> Set Entity -> Dot (NodeId, NodeId)       subLevel i ns = fmap snd . Dot.cluster $ do         Dot.attribute ("style", "filled")@@ -471,7 +469,7 @@             ]         return bt   -- ---------------------------------------------------------------------------  -- order entites into clusters based on how "far" they are from+  -- order entities into clusters based on how "far" they are from   -- what is available at the start - see 'recipeLevels'.   bottom <- wrapBelowAbove worldEntites   ls <- zipWithM subLevel [1 ..] (tail levels)@@ -495,14 +493,14 @@ -- RECIPE LEVELS -- ---------------------------------------------------------------------------- --- | Order entites in sets depending on how soon it is possible to obtain them.+-- | Order entities in sets depending on how soon it is possible to obtain them. -- -- So:---  * Level 0 - starting entites (for example those obtainable in the world)+--  * Level 0 - starting entities (for example those obtainable in the world) --  * Level N+1 - everything possible to make (or drill) from Level N ----- This is almost a BFS, but the requirement is that the set of entites--- required for recipe is subset of the entites known in Level N.+-- This is almost a BFS, but the requirement is that the set of entities+-- required for recipe is subset of the entities known in Level N. -- -- If we ever depend on some graph library, this could be rewritten -- as some BFS-like algorithm with added recipe nodes, but you would@@ -524,21 +522,21 @@             then ls             else go (n : ls) (Set.union n known) --- | Get classic scenario to figure out starting entites.+-- | Get classic scenario to figure out starting entities. classicScenario :: ExceptT Text IO Scenario classicScenario = do   entities <- loadEntities >>= guardRight "load entities"   fst <$> loadScenario "data/scenarios/classic.yaml" entities  startingDevices :: Scenario -> Set Entity-startingDevices = Set.fromList . map snd . E.elems . view installedDevices . instantiateRobot 0 . head . view scenarioRobots+startingDevices = Set.fromList . map snd . E.elems . view equippedDevices . instantiateRobot 0 . head . view scenarioRobots  startingInventory :: Scenario -> Map Entity Int startingInventory = Map.fromList . map swap . E.elems . view robotInventory . instantiateRobot 0 . head . view scenarioRobots --- | Ignore utility entites that are just used for tutorials and challenges.-ignoredEntites :: Set Text-ignoredEntites =+-- | Ignore utility entities that are just used for tutorials and challenges.+ignoredEntities :: Set Text+ignoredEntities =   Set.fromList     [ "upper left corner"     , "upper right corner"
src/Swarm/Game/CESK.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}  -- |@@ -64,7 +64,7 @@    -- * Store   Store,-  Loc,+  Addr,   emptyStore,   Cell (..),   allocate,@@ -82,31 +82,26 @@    -- ** Extracting information   finalValue,--  -- ** Pretty-printing-  prettyFrame,-  prettyCont,-  prettyCESK, ) where +import Control.Lens ((^.)) import Control.Lens.Combinators (pattern Empty) import Data.Aeson (FromJSON, ToJSON)-import Data.Aeson qualified import Data.IntMap.Strict (IntMap) import Data.IntMap.Strict qualified as IM-import Data.List (intercalate) import GHC.Generics (Generic)-import Swarm.Game.Entity (Entity, Inventory)+import Prettyprinter (Doc, Pretty (..), hsep, (<+>))+import Swarm.Game.Entity (Count, Entity) import Swarm.Game.Exception-import Swarm.Game.Value as V-import Swarm.Game.World (World)+import Swarm.Game.World (WorldUpdate (..)) import Swarm.Language.Context+import Swarm.Language.Module import Swarm.Language.Pipeline import Swarm.Language.Pretty import Swarm.Language.Requirement (ReqCtx) import Swarm.Language.Syntax import Swarm.Language.Types-import Witch (from)+import Swarm.Language.Value as V  ------------------------------------------------------------ -- Frames and continuations@@ -163,12 +158,21 @@     --   in the given environment (extended by binding the variable,     --   if there is one, to the output of the first command).     FBind (Maybe Var) Term Env+  | -- | Discard any environment generated as the result of executing+    --   a command.+    FDiscardEnv   | -- | Apply specific updates to the world and current robot.-    FImmediate WorldUpdate RobotUpdate+    --+    -- The 'Const' is used to track the original command for error messages.+    FImmediate Const [WorldUpdate Entity] [RobotUpdate]   | -- | Update the memory cell at a certain location with the computed value.-    FUpdate Loc+    FUpdate Addr   | -- | Signal that we are done with an atomic computation.     FFinishAtomic+  | -- | We are in the middle of running a computation for all the+    --   nearby robots.  We have the function to run, and the list of+    --   robot IDs to run it on.+    FMeetAll Value [Int]   deriving (Eq, Show, Generic, FromJSON, ToJSON)  -- | A continuation is just a stack of frames.@@ -178,10 +182,10 @@ -- Store ------------------------------------------------------------ -type Loc = Int+type Addr = Int  -- | 'Store' represents a store, indexing integer locations to 'Cell's.-data Store = Store {next :: Loc, mu :: IntMap Cell} deriving (Show, Eq, Generic, FromJSON, ToJSON)+data Store = Store {next :: Addr, mu :: IntMap Cell} deriving (Show, Eq, Generic, FromJSON, ToJSON)  -- | A memory cell can be in one of three states. data Cell@@ -213,15 +217,15 @@ -- | Allocate a new memory cell containing an unevaluated expression --   with the current environment.  Return the index of the allocated --   cell.-allocate :: Env -> Term -> Store -> (Loc, Store)+allocate :: Env -> Term -> Store -> (Addr, Store) allocate e t (Store n m) = (n, Store (n + 1) (IM.insert n (E t e) m))  -- | Look up the cell at a given index.-lookupCell :: Loc -> Store -> Maybe Cell+lookupCell :: Addr -> Store -> Maybe Cell lookupCell n = IM.lookup n . mu  -- | Set the cell at a given index.-setCell :: Loc -> Cell -> Store -> Store+setCell :: Addr -> Cell -> Store -> Store setCell n c (Store nxt m) = Store nxt (IM.insert n c m)  ------------------------------------------------------------@@ -281,11 +285,25 @@  -- | Like 'initMachine', but also take an explicit starting continuation. initMachine' :: ProcessedTerm -> Env -> Store -> Cont -> CESK-initMachine' (ProcessedTerm t (Module (Forall _ (TyCmd _)) ctx) _ reqCtx) e s k =-  case ctx of-    Empty -> In t e s (FExec : k)-    _ -> In t e s (FExec : FLoadEnv ctx reqCtx : k)-initMachine' (ProcessedTerm t _ _ _) e s k = In t e s k+initMachine' (ProcessedTerm (Module t' ctx) _ reqCtx) e s k =+  case t' ^. sType of+    -- If the starting term has a command type...+    Forall _ (TyCmd _) ->+      case ctx of+        -- ...but doesn't contain any definitions, just create a machine+        -- that will evaluate it and then execute it.+        Empty -> In t e s (FExec : k)+        -- Or, if it does contain definitions, then load the resulting+        -- context after executing it.+        _ -> In t e s (FExec : FLoadEnv ctx reqCtx : k)+    -- Otherwise, for a term with a non-command type, just+    -- create a machine to evaluate it.+    _ -> In t e s k+ where+  -- Erase all type and SrcLoc annotations from the term before+  -- putting it in the machine state, since those are irrelevant at+  -- runtime.+  t = eraseS t'  -- | Cancel the currently running computation. cancel :: CESK -> CESK@@ -307,82 +325,73 @@   resetBlackhole c = c  --------------------------------------------------------------- Very crude pretty-printing of CESK states.  Should really make a--- nicer version of this code...+-- Pretty printing CESK machine states ------------------------------------------------------------ --- | Very poor pretty-printing of CESK machine states, really just for---   debugging. At some point we should make a nicer version.-prettyCESK :: CESK -> String-prettyCESK (In c _ _ k) =-  unlines-    [ "▶ " ++ prettyString c-    , "  " ++ prettyCont k-    ]-prettyCESK (Out v _ k) =-  unlines-    [ "◀ " ++ from (prettyValue v)-    , "  " ++ prettyCont k-    ]-prettyCESK (Up e _ k) =-  unlines-    [ "! " ++ from (formatExn mempty e)-    , "  " ++ prettyCont k-    ]-prettyCESK (Waiting t cek) =-  "🕑" <> show t <> " " <> show cek---- | Poor pretty-printing of continuations.-prettyCont :: Cont -> String-prettyCont = ("[" ++) . (++ "]") . intercalate " | " . map prettyFrame---- | Poor pretty-printing of frames.-prettyFrame :: Frame -> String-prettyFrame (FSnd t _) = "(_, " ++ prettyString t ++ ")"-prettyFrame (FFst v) = "(" ++ from (prettyValue v) ++ ", _)"-prettyFrame (FArg t _) = "_ " ++ prettyString t-prettyFrame (FApp v) = prettyString (valueToTerm v) ++ " _"-prettyFrame (FLet x t _) = "let " ++ from x ++ " = _ in " ++ prettyString t-prettyFrame (FTry t) = "try _ (" ++ prettyString (valueToTerm t) ++ ")"-prettyFrame FUnionEnv {} = "_ ∪ <Env>"-prettyFrame FLoadEnv {} = "loadEnv"-prettyFrame (FDef x) = "def " ++ from x ++ " = _"-prettyFrame FExec = "exec _"-prettyFrame (FBind Nothing t _) = "_ ; " ++ prettyString t-prettyFrame (FBind (Just x) t _) = from x ++ " <- _ ; " ++ prettyString t-prettyFrame FImmediate {} = "(_ : cmd a)"-prettyFrame (FUpdate loc) = "store@" ++ show loc ++ "(_)"-prettyFrame FFinishAtomic = "finishAtomic"+instance PrettyPrec CESK where+  prettyPrec _ (In c _ _ k) = prettyCont k (11, "▶" <> ppr c <> "◀")+  prettyPrec _ (Out v _ k) = prettyCont k (11, "◀" <> ppr (valueToTerm v) <> "▶")+  prettyPrec _ (Up e _ k) = prettyCont k (11, "!" <> (pretty (formatExn mempty e) <> "!"))+  prettyPrec _ (Waiting t cesk) = "🕑" <> pretty t <> "(" <> ppr cesk <> ")" ------------------------------------------------------------------ Wrappers for functions in FImmediate------ NOTE: we can not use GameState and Robot directly, as it--- would create a cyclic dependency. The alternative is--- making CESK, Cont and Frame polymorphic which just muddies--- the picture too much for one little game feature.+-- | Take a continuation, and the pretty-printed expression which is+--   the focus of the continuation (i.e. the expression whose value+--   will be given to the continuation) along with its top-level+--   precedence, and pretty-print the whole thing. ----- BEWARE: the types do not follow normal laws for Show and Eq-----------------------------------------------------------------newtype WorldUpdate = WorldUpdate-  { worldUpdate :: World Int Entity -> Either Exn (World Int Entity)-  }--newtype RobotUpdate = RobotUpdate-  { robotUpdateInventory :: Inventory -> Inventory-  }--instance Show WorldUpdate where show _ = "WorldUpdate {???}"+--   As much as possible, we try to print to look like an *expression*+--   with a currently focused part, that is, we print the continuation+--   from the inside out instead of as a list of frames.  This makes+--   it much more intuitive to read.+prettyCont :: Cont -> (Int, Doc ann) -> Doc ann+prettyCont [] (_, inner) = inner+prettyCont (f : k) inner = prettyCont k (prettyFrame f inner) -instance Show RobotUpdate where show _ = "RobotUpdate {???}"+-- | Pretty-print a single continuation frame, given its already+--   pretty-printed focus.  In particular, given a frame and its+--   "inside" (i.e. the expression or other frames being focused on,+--   whose value will eventually be passed to this frame), with the+--   precedence of the inside's top-level construct, return a+--   pretty-printed version of the entire frame along with its+--   top-level precedence.+prettyFrame :: Frame -> (Int, Doc ann) -> (Int, Doc ann)+prettyFrame (FSnd t _) (_, inner) = (11, "(" <> inner <> "," <+> ppr t <> ")")+prettyFrame (FFst v) (_, inner) = (11, "(" <> ppr (valueToTerm v) <> "," <+> inner <> ")")+prettyFrame (FArg t _) (p, inner) = (10, pparens (p < 10) inner <+> prettyPrec 11 t)+prettyFrame (FApp v) (p, inner) = (10, prettyPrec 10 (valueToTerm v) <+> pparens (p < 11) inner)+prettyFrame (FLet x t _) (_, inner) = (11, hsep ["let", pretty x, "=", inner, "in", ppr t])+prettyFrame (FTry v) (p, inner) = (10, "try" <+> pparens (p < 11) inner <+> prettyPrec 11 (valueToTerm v))+prettyFrame (FUnionEnv _) inner = prettyPrefix "∪·" inner+prettyFrame (FLoadEnv _ _) inner = prettyPrefix "L·" inner+prettyFrame (FDef x) (_, inner) = (11, "def" <+> pretty x <+> "=" <+> inner <+> "end")+prettyFrame FExec inner = prettyPrefix "E·" inner+prettyFrame (FBind Nothing t _) (p, inner) = (0, pparens (p < 1) inner <+> ";" <+> ppr t)+prettyFrame (FBind (Just x) t _) (p, inner) = (0, hsep [pretty x, "<-", pparens (p < 1) inner, ";", ppr t])+prettyFrame FDiscardEnv inner = prettyPrefix "D·" inner+prettyFrame (FImmediate c _worldUpds _robotUpds) inner = prettyPrefix ("I[" <> ppr c <> "]·") inner+prettyFrame (FUpdate addr) inner = prettyPrefix ("S@" <> pretty addr) inner+prettyFrame FFinishAtomic inner = prettyPrefix "A·" inner+prettyFrame (FMeetAll _ _) inner = prettyPrefix "M·" inner -instance Eq WorldUpdate where _ == _ = True+-- | Pretty-print a special "prefix application" frame, i.e. a frame+--   formatted like @X· inner@.  Unlike typical applications, these+--   associate to the *right*, so that we can print something like @X·+--   Y· Z· inner@ with no parens.+prettyPrefix :: Doc ann -> (Int, Doc ann) -> (Int, Doc ann)+prettyPrefix pre (p, inner) = (11, pre <+> pparens (p < 11) inner) -instance Eq RobotUpdate where _ == _ = True+--------------------------------------------------------------+-- Runtime robot update+-------------------------------------------------------------- --- TODO: remove these instances once Update fields are concret-instance FromJSON WorldUpdate where parseJSON _ = pure $ WorldUpdate $ \w -> Right w-instance ToJSON WorldUpdate where toJSON _ = Data.Aeson.Null-instance FromJSON RobotUpdate where parseJSON _ = pure $ RobotUpdate id-instance ToJSON RobotUpdate where toJSON _ = Data.Aeson.Null+-- | Update the robot in an inspectable way.+--+-- This type is used for changes by e.g. the drill command at later+-- tick. Using ADT allows us to serialize and inspect the updates.+--+-- Note that this can not be in 'Swarm.Game.Robot' as it would create+-- a cyclic dependency.+data RobotUpdate+  = AddEntity Count Entity+  | LearnEntity Entity+  deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON)
src/Swarm/Game/Display.hs view
@@ -44,9 +44,9 @@ import Data.Maybe (fromMaybe, isJust) import Data.Yaml import GHC.Generics (Generic)-import Swarm.Language.Syntax (Direction (..))+import Swarm.Language.Syntax (AbsoluteDir (..), Direction (..)) import Swarm.TUI.Attr (entityAttr, robotAttr, worldPrefix)-import Swarm.Util (maxOn, (?))+import Swarm.Util (maxOn) import Swarm.Util.Yaml (FromJSONE (..), With (runE), getE, liftE, withObjectE)  -- | Display priority.  Entities with higher priority will be drawn on@@ -60,7 +60,7 @@ -- | A record explaining how to display an entity in the TUI. data Display = Display   { _defaultChar :: Char-  , _orientationMap :: Map Direction Char+  , _orientationMap :: Map AbsoluteDir Char   , _curOrientation :: Maybe Direction   , _displayAttr :: AttrName   , _displayPriority :: Priority@@ -83,7 +83,7 @@ --   optionally associates different display characters with --   different orientations.  If an orientation is not in the map, --   the 'defaultChar' will be used.-orientationMap :: Lens' Display (Map Direction Char)+orientationMap :: Lens' Display (Map AbsoluteDir Char)  -- | The display caches the current orientation of the entity, so we --   know which character to use from the orientation map.@@ -128,9 +128,9 @@  -- | Look up the character that should be used for a display. displayChar :: Display -> Char-displayChar disp = case disp ^. curOrientation of-  Nothing -> disp ^. defaultChar-  Just dir -> M.lookup dir (disp ^. orientationMap) ? (disp ^. defaultChar)+displayChar disp = fromMaybe (disp ^. defaultChar) $ do+  DAbsolute d <- disp ^. curOrientation+  M.lookup d (disp ^. orientationMap)  -- | Render a display as a UI widget. renderDisplay :: Display -> Widget n@@ -167,7 +167,7 @@ --   priority 10. -- --   Note that the 'defaultChar' is used for direction 'DDown'---   and is overriden for the special base robot.+--   and is overridden for the special base robot. defaultRobotDisplay :: Display defaultRobotDisplay =   Display
src/Swarm/Game/Entity.hs view
@@ -69,6 +69,8 @@   isSubsetOf,   isEmpty,   inventoryCapabilities,+  extantElemsWithCapability,+  entitiesByCapability,    -- ** Modification   insert,@@ -81,32 +83,31 @@ ) where  import Control.Arrow ((&&&))-import Control.Lens (Getter, Lens', lens, to, view, (^.), _2)+import Control.Lens (Getter, Lens', lens, to, view, (^.)) import Control.Monad.IO.Class import Data.Bifunctor (bimap, first) import Data.Char (toLower) import Data.Function (on) import Data.Hashable-import Data.Int (Int64) import Data.IntMap (IntMap) import Data.IntMap qualified as IM import Data.IntSet (IntSet) import Data.IntSet qualified as IS import Data.List (foldl')+import Data.List.NonEmpty qualified as NE import Data.Map (Map) import Data.Map qualified as M import Data.Maybe (fromMaybe, isJust, listToMaybe) import Data.Set (Set)-import Data.Set qualified as Set (fromList)-import Data.Set.Lens (setOf)+import Data.Set qualified as Set (fromList, member, toList, unions) import Data.Text (Text) import Data.Text qualified as T import Data.Yaml import GHC.Generics (Generic)-import Linear (V2) import Swarm.Game.Display import Swarm.Language.Capability-import Swarm.Util (dataNotFound, getDataFileNameSafe, plural, reflow, (?))+import Swarm.Util (binTuples, dataNotFound, getDataFileNameSafe, plural, reflow, (?))+import Swarm.Util.Location import Swarm.Util.Yaml import Text.Read (readMaybe) import Witch@@ -198,33 +199,33 @@ --   entities stored in the world that are the same will literally --   just be stored as pointers to the same shared record. data Entity = Entity-  { -- | A hash value computed from the other fields-    _entityHash :: Int-  , -- | The way this entity should be displayed on the world map.-    _entityDisplay :: Display-  , -- | The name of the entity, used /e.g./ in an inventory display.-    _entityName :: Text-  , -- | The plural of the entity name, in case it is irregular.  If-    --   this field is @Nothing@, default pluralization heuristics-    --   will be used (see 'plural').-    _entityPlural :: Maybe Text-  , -- | A longer-form description. Each 'Text' value is one-    --   paragraph.-    _entityDescription :: [Text]-  , -- | The entity's orientation (if it has one).  For example, when-    --   a robot moves, it moves in the direction of its orientation.-    _entityOrientation :: Maybe (V2 Int64)-  , -- | If this entity grows, how long does it take?-    _entityGrowth :: Maybe GrowthTime-  , -- | The name of a different entity obtained when this entity is-    -- grabbed.-    _entityYields :: Maybe Text-  , -- | Properties of the entity.-    _entityProperties :: Set EntityProperty-  , -- | Capabilities provided by this entity.-    _entityCapabilities :: [Capability]-  , -- | Inventory of other entities held by this entity.-    _entityInventory :: Inventory+  { _entityHash :: Int+  -- ^ A hash value computed from the other fields+  , _entityDisplay :: Display+  -- ^ The way this entity should be displayed on the world map.+  , _entityName :: Text+  -- ^ The name of the entity, used /e.g./ in an inventory display.+  , _entityPlural :: Maybe Text+  -- ^ The plural of the entity name, in case it is irregular.  If+  --   this field is @Nothing@, default pluralization heuristics+  --   will be used (see 'plural').+  , _entityDescription :: [Text]+  -- ^ A longer-form description. Each 'Text' value is one+  --   paragraph.+  , _entityOrientation :: Maybe Heading+  -- ^ The entity's orientation (if it has one).  For example, when+  --   a robot moves, it moves in the direction of its orientation.+  , _entityGrowth :: Maybe GrowthTime+  -- ^ If this entity grows, how long does it take?+  , _entityYields :: Maybe Text+  -- ^ The name of a different entity obtained when this entity is+  -- grabbed.+  , _entityProperties :: Set EntityProperty+  -- ^ Properties of the entity.+  , _entityCapabilities :: Set Capability+  -- ^ Capabilities provided by this entity.+  , _entityInventory :: Inventory+  -- ^ Inventory of other entities held by this entity.   }   -- Note that an entity does not have a location, because the   -- location of an entity is implicit in the way it is stored (by@@ -236,7 +237,8 @@ --   value and simply combines the other fields. instance Hashable Entity where   hashWithSalt s (Entity _ disp nm pl descr orient grow yld props caps inv) =-    s `hashWithSalt` disp+    s+      `hashWithSalt` disp       `hashWithSalt` nm       `hashWithSalt` pl       `hashWithSalt` descr@@ -275,7 +277,7 @@   [Capability] ->   Entity mkEntity disp nm descr props caps =-  rehashEntity $ Entity 0 disp nm Nothing descr Nothing Nothing Nothing (Set.fromList props) caps empty+  rehashEntity $ Entity 0 disp nm Nothing descr Nothing Nothing Nothing (Set.fromList props) (Set.fromList caps) empty  ------------------------------------------------------------ -- Entity map@@ -291,7 +293,7 @@   deriving (Eq, Show, Generic, FromJSON, ToJSON)  instance Semigroup EntityMap where-  EntityMap n1 c1 <> EntityMap n2 c2 = EntityMap (n1 <> n2) (c1 <> c2)+  EntityMap n1 c1 <> EntityMap n2 c2 = EntityMap (n1 <> n2) (M.unionWith (<>) c1 c2)  instance Monoid EntityMap where   mempty = EntityMap M.empty M.empty@@ -313,7 +315,7 @@ buildEntityMap es =   EntityMap     { entitiesByName = M.fromList . map (view entityName &&& id) $ es-    , entitiesByCap = M.fromListWith (<>) . concatMap (\e -> map (,[e]) (e ^. entityCapabilities)) $ es+    , entitiesByCap = M.fromListWith (<>) . concatMap (\e -> map (,[e]) (Set.toList $ e ^. entityCapabilities)) $ es     }  ------------------------------------------------------------@@ -332,7 +334,7 @@               <*> v .:? "growth"               <*> v .:? "yields"               <*> v .:? "properties" .!= mempty-              <*> v .:? "capabilities" .!= []+              <*> v .:? "capabilities" .!= mempty               <*> pure empty           ) @@ -419,7 +421,7 @@ entityDescription = hashedLens _entityDescription (\e x -> e {_entityDescription = x})  -- | The direction this entity is facing (if it has one).-entityOrientation :: Lens' Entity (Maybe (V2 Int64))+entityOrientation :: Lens' Entity (Maybe Heading) entityOrientation = hashedLens _entityOrientation (\e x -> e {_entityOrientation = x})  -- | How long this entity takes to grow, if it regrows.@@ -439,8 +441,8 @@ hasProperty :: Entity -> EntityProperty -> Bool hasProperty e p = p `elem` (e ^. entityProperties) --- | The capabilities this entity provides when installed.-entityCapabilities :: Lens' Entity [Capability]+-- | The capabilities this entity provides when equipped.+entityCapabilities :: Lens' Entity (Set Capability) entityCapabilities = hashedLens _entityCapabilities (\e x -> e {_entityCapabilities = x})  -- | The inventory of other entities carried by this entity.@@ -567,7 +569,25 @@  -- | Compute the set of capabilities provided by the devices in an inventory. inventoryCapabilities :: Inventory -> Set Capability-inventoryCapabilities = setOf (to elems . traverse . _2 . entityCapabilities . traverse)+inventoryCapabilities = Set.unions . map (^. entityCapabilities) . nonzeroEntities++-- | List elements that have at least one copy in the inventory.+nonzeroEntities :: Inventory -> [Entity]+nonzeroEntities = map snd . filter ((> 0) . fst) . elems++-- | List elements that possess a given Capability and+-- exist with nonzero count in the inventory.+extantElemsWithCapability :: Capability -> Inventory -> [Entity]+extantElemsWithCapability cap =+  filter (Set.member cap . (^. entityCapabilities)) . nonzeroEntities++-- | Groups entities by the capabilities they offer.+entitiesByCapability :: Inventory -> Map Capability (NE.NonEmpty Entity)+entitiesByCapability inv =+  binTuples entityCapabilityPairs+ where+  getCaps = Set.toList . (^. entityCapabilities)+  entityCapabilityPairs = concatMap ((\e -> map (,e) $ getCaps e) . snd) $ elems inv  -- | Delete a single copy of a certain entity from an inventory. delete :: Entity -> Inventory -> Inventory
src/Swarm/Game/Exception.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} @@ -32,6 +31,7 @@ import Swarm.Language.Pretty (prettyText) import Swarm.Language.Requirement (Requirements (..)) import Swarm.Language.Syntax (Const, Term)+import Swarm.TUI.Model.Achievement.Definitions import Swarm.Util import Witch (from) @@ -52,8 +52,8 @@  -- | Suggested way to fix incapable error. data IncapableFix-  = -- | Install the missing device on yourself/target-    FixByInstall+  = -- | Equip the missing device on yourself/target+    FixByEquip   | -- | Add the missing device to your inventory     FixByObtain   deriving (Eq, Show, Generic, FromJSON, ToJSON)@@ -74,7 +74,7 @@   | -- | A command failed in some "normal" way (/e.g./ a 'Move'     --   command could not move, or a 'Grab' command found nothing to     --   grab, /etc./).-    CmdFailed Const Text+    CmdFailed Const Text (Maybe GameplayAchievement)   | -- | The user program explicitly called 'Undefined' or 'Fail'.     User Text   deriving (Eq, Show, Generic, FromJSON, ToJSON)@@ -89,7 +89,7 @@       , "<https://github.com/swarm-game/swarm/issues/new>."       ]   InfiniteLoop -> "Infinite loop detected!"-  (CmdFailed c t) -> T.concat [prettyText c, ": ", t]+  (CmdFailed c t _) -> T.concat [prettyText c, ": ", t]   (User t) -> "Player exception: " <> t   (Incapable f caps tm) -> formatIncapable em f caps tm @@ -99,7 +99,7 @@  formatIncapableFix :: IncapableFix -> Text formatIncapableFix = \case-  FixByInstall -> "install"+  FixByEquip -> "equip"   FixByObtain -> "obtain"  -- | Pretty print the incapable exception with an actionable suggestion@@ -108,7 +108,7 @@ -- >>> w = mkEntity (defaultEntityDisplay 'l') "magic wand" [] [] [CAppear] -- >>> r = mkEntity (defaultEntityDisplay 'o') "the one ring" [] [] [CAppear] -- >>> m = buildEntityMap [w,r]--- >>> incapableError cs t = putStr . unpack $ formatIncapable m FixByInstall cs t+-- >>> incapableError cs t = putStr . unpack $ formatIncapable m FixByEquip cs t -- -- >>> incapableError (R.singletonCap CGod) (TConst As) -- Thou shalt not utter such blasphemy:@@ -118,7 +118,7 @@ -- >>> incapableError (R.singletonCap CAppear) (TConst Appear) -- You do not have the devices required for: --   'appear'---   Please install:+--   Please equip: --   - the one ring or magic wand -- -- >>> incapableError (R.singletonCap CRandom) (TConst Random)@@ -135,32 +135,32 @@ formatIncapable :: EntityMap -> IncapableFix -> Requirements -> Term -> Text formatIncapable em f (Requirements caps _ inv) tm   | CGod `S.member` caps =-    unlinesExText-      [ "Thou shalt not utter such blasphemy:"-      , squote $ prettyText tm-      , "If God in troth thou wantest to play, try thou a Creative game."-      ]+      unlinesExText+        [ "Thou shalt not utter such blasphemy:"+        , squote $ prettyText tm+        , "If God in troth thou wantest to play, try thou a Creative game."+        ]   | not (null capsNone) =-    unlinesExText-      [ "Missing the " <> capMsg <> " for:"-      , squote $ prettyText tm-      , "but no device yet provides it. See"-      , "https://github.com/swarm-game/swarm/issues/26"-      ]+      unlinesExText+        [ "Missing the " <> capMsg <> " for:"+        , squote $ prettyText tm+        , "but no device yet provides it. See"+        , "https://github.com/swarm-game/swarm/issues/26"+        ]   | not (S.null caps) =-    unlinesExText-      ( "You do not have the devices required for:" :-        squote (prettyText tm) :-        "Please " <> formatIncapableFix f <> ":" :-        (("- " <>) . formatDevices <$> filter (not . null) deviceSets)-      )+      unlinesExText+        ( "You do not have the devices required for:"+            : squote (prettyText tm)+            : "Please " <> formatIncapableFix f <> ":"+            : (("- " <>) . formatDevices <$> filter (not . null) deviceSets)+        )   | otherwise =-    unlinesExText-      ( "You are missing required inventory for:" :-        squote (prettyText tm) :-        "Please obtain:" :-        (("- " <>) . formatEntity <$> M.assocs inv)-      )+      unlinesExText+        ( "You are missing required inventory for:"+            : squote (prettyText tm)+            : "Please obtain:"+            : (("- " <>) . formatEntity <$> M.assocs inv)+        )  where   capList = S.toList caps   deviceSets = map (`deviceForCap` em) capList
+ src/Swarm/Game/Log.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module      :  Swarm.Game.Log+-- Copyright   :  Ondřej Šebek+-- Maintainer  :  ondras98@icloud.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- A data type to represent in-game logs by robots.+--+-- Because of the use of system robots, we sometimes+-- want to use special kinds of logs that will be+-- shown to the player.+--+-- TODO: #1039 Currently we abuse this system for system+-- logs, which is fun, but we should eventually make+-- a dedicated `SystemLogEntry` type for 'RuntimeState'+-- message queue.+module Swarm.Game.Log (+  LogSource (..),+  ErrorLevel (..),++  -- * Robot log entries+  LogEntry (..),+  leText,+  leSource,+  leRobotName,+  leTime,+  leLocation,+  leRobotID,+) where++import Control.Lens hiding (contains)+import Data.Aeson (FromJSON, ToJSON)+import Data.Text (Text)+import GHC.Generics (Generic)+import Swarm.Util.Location (Location)++-- | Severity of the error - critical errors are bugs+--   and should be reported as Issues.+data ErrorLevel = Debug | Warning | Error | Critical+  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)++-- | Source of the robot log.+data LogSource+  = -- | Log produced by 'Swarm.Language.Syntax.Say'+    Said+  | -- | Log produced by 'Swarm.Language.Syntax.Log'+    Logged+  | -- | Log produced by an exception or system.+    ErrorTrace ErrorLevel+  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)++-- | An entry in a robot's log.+data LogEntry = LogEntry+  { _leTime :: Integer+  -- ^ The time at which the entry was created.+  --   Note that this is the first field we sort on.+  , _leSource :: LogSource+  -- ^ Whether this log records a said message.+  , _leRobotName :: Text+  -- ^ The name of the robot that generated the entry.+  , _leRobotID :: Int+  -- ^ The ID of the robot that generated the entry.+  , _leLocation :: Location+  -- ^ Location of the robot at log entry creation.+  , _leText :: Text+  -- ^ The text of the log entry.+  }+  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)++makeLenses ''LogEntry
src/Swarm/Game/Recipe.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} @@ -191,7 +190,7 @@  -- | Figure out which ingredients (if any) are lacking from an --   inventory to be able to carry out the recipe.---   Requirements are not consumed and so can use installed.+--   Requirements are not consumed and so can use equipped. missingIngredientsFor :: (Inventory, Inventory) -> Recipe Entity -> [MissingIngredient] missingIngredientsFor (inv, ins) (Recipe inps _ reqs _ _) =   mkMissing MissingInput (findLacking inv inps)@@ -214,7 +213,7 @@ --   or an inventory without inputs and function adding outputs if --   it was successful. make ::-  -- robots inventory and installed devices+  -- robots inventory and equipped devices   (Inventory, Inventory) ->   -- considered recipe   Recipe Entity ->@@ -222,11 +221,10 @@   -- a function to add results and the recipe repeated   Either     [MissingIngredient]-    (Inventory, Inventory -> Inventory, Recipe Entity)+    (Inventory, IngredientList Entity, Recipe Entity) make invs r = finish <$> make' invs r  where-  finish (invTaken, out) = (invTaken, addOuts out, r)-  addOuts out inv' = foldl' (flip $ uncurry insertCount) inv' out+  finish (invTaken, out) = (invTaken, out, r)  -- | Try to make a recipe, but do not insert it yet. make' :: (Inventory, Inventory) -> Recipe Entity -> Either [MissingIngredient] (Inventory, IngredientList Entity)
src/Swarm/Game/Robot.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-}@@ -19,14 +18,7 @@   -- * Robots data    -- * Robot log entries-  LogSource (..),-  LogEntry (..),-  leText,-  leSaid,-  leRobotName,-  leTime,-  leLocation,-  leRobotID,+  module Swarm.Game.Log,    -- * Robots   RobotPhase (..),@@ -35,6 +27,9 @@   Robot,   TRobot, +  -- ** Runtime robot update+  RobotUpdate (..),+   -- * Robot context   RobotContext,   defTypes,@@ -54,12 +49,13 @@   trobotLocation,   robotOrientation,   robotInventory,-  installedDevices,+  equippedDevices,   robotLog,   robotLogUpdated,   inventoryHash,   robotCapabilities,   robotContext,+  trobotContext,   robotID,   robotParentID,   robotHeavy,@@ -86,7 +82,6 @@ import Control.Lens hiding (contains) import Data.Aeson (FromJSON, ToJSON) import Data.Hashable (hashWithSalt)-import Data.Int (Int64) import Data.Maybe (fromMaybe, isNothing) import Data.Sequence (Seq) import Data.Sequence qualified as Seq@@ -98,32 +93,33 @@ import Swarm.Game.CESK import Swarm.Game.Display (Display, curOrientation, defaultRobotDisplay, invisible) import Swarm.Game.Entity hiding (empty)-import Swarm.Game.Value as V+import Swarm.Game.Log import Swarm.Language.Capability (Capability) import Swarm.Language.Context qualified as Ctx import Swarm.Language.Requirement (ReqCtx) import Swarm.Language.Syntax (toDirection) import Swarm.Language.Typed (Typed (..)) import Swarm.Language.Types (TCtx)-import Swarm.Util ()+import Swarm.Language.Value as V+import Swarm.Util.Location import Swarm.Util.Yaml import System.Clock (TimeSpec)  -- | A record that stores the information---   for all defintions stored in a 'Robot'+--   for all definitions stored in a 'Robot' data RobotContext = RobotContext-  { -- | Map definition names to their types.-    _defTypes :: TCtx-  , -- | Map defintion names to the capabilities-    --   required to evaluate/execute them.-    _defReqs :: ReqCtx-  , -- | Map defintion names to their values. Note that since-    --   definitions are delayed, the values will just consist of-    --   'VRef's pointing into the store.-    _defVals :: Env-  , -- | A store containing memory cells allocated to hold-    --   definitions.-    _defStore :: Store+  { _defTypes :: TCtx+  -- ^ Map definition names to their types.+  , _defReqs :: ReqCtx+  -- ^ Map definition names to the capabilities+  --   required to evaluate/execute them.+  , _defVals :: Env+  -- ^ Map definition names to their values. Note that since+  --   definitions are delayed, the values will just consist of+  --   'VRef's pointing into the store.+  , _defStore :: Store+  -- ^ A store containing memory cells allocated to hold+  --   definitions.   }   deriving (Eq, Show, Generic, FromJSON, ToJSON) @@ -146,37 +142,16 @@         req <- Ctx.lookup name (ctx ^. defReqs)         return $ Typed val typ req     setter ctx Nothing =-      ctx & defTypes %~ Ctx.delete name+      ctx+        & defTypes %~ Ctx.delete name         & defVals %~ Ctx.delete name         & defReqs %~ Ctx.delete name     setter ctx (Just (Typed val typ req)) =-      ctx & defTypes %~ Ctx.addBinding name typ+      ctx+        & defTypes %~ Ctx.addBinding name typ         & defVals %~ Ctx.addBinding name val         & defReqs %~ Ctx.addBinding name req -data LogSource = Said | Logged | ErrorTrace-  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)---- | An entry in a robot's log.-data LogEntry = LogEntry-  { -- | The time at which the entry was created.-    --   Note that this is the first field we sort on.-    _leTime :: Integer-  , -- | Whether this log records a said message.-    _leSaid :: LogSource-  , -- | The name of the robot that generated the entry.-    _leRobotName :: Text-  , -- | The ID of the robot that generated the entry.-    _leRobotID :: Int-  , -- | Location of the robot at log entry creation.-    _leLocation :: V2 Int64-  , -- | The text of the log entry.-    _leText :: Text-  }-  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)--makeLenses ''LogEntry- -- | A unique identifier for a robot. type RID = Int @@ -192,8 +167,8 @@ -- | With a robot template, we may or may not have a location.  With a --   concrete robot we must have a location. type family RobotLocation (phase :: RobotPhase) :: * where-  RobotLocation 'TemplateRobot = Maybe (V2 Int64)-  RobotLocation 'ConcreteRobot = V2 Int64+  RobotLocation 'TemplateRobot = Maybe Location+  RobotLocation 'ConcreteRobot = Location  -- | Robot templates have no ID; concrete robots definitely do. type family RobotID (phase :: RobotPhase) :: * where@@ -205,10 +180,10 @@ --   the robot has been assigned a unique ID. data RobotR (phase :: RobotPhase) = RobotR   { _robotEntity :: Entity-  , _installedDevices :: Inventory-  , -- | A cached view of the capabilities this robot has.-    --   Automatically generated from '_installedDevices'.-    _robotCapabilities :: Set Capability+  , _equippedDevices :: Inventory+  , _robotCapabilities :: Set Capability+  -- ^ A cached view of the capabilities this robot has.+  --   Automatically generated from '_equippedDevices'.   , _robotLog :: Seq LogEntry   , _robotLogUpdated :: Bool   , _robotLocation :: RobotLocation phase@@ -233,7 +208,7 @@ -- See https://byorgey.wordpress.com/2021/09/17/automatically-updated-cached-views-with-lens/ -- for the approach used here with lenses. -let exclude = ['_robotCapabilities, '_installedDevices, '_robotLog]+let exclude = ['_robotCapabilities, '_equippedDevices, '_robotLog]  in makeLensesWith       ( lensRules           & generateSignatures .~ False@@ -299,32 +274,36 @@ --   a getter, since when changing a robot's location we must remember --   to update the 'robotsByLocation' map as well.  You can use the --   'updateRobotLocation' function for this purpose.-robotLocation :: Getter Robot (V2 Int64)+robotLocation :: Getter Robot Location  -- | Set a robot's location.  This is unsafe and should never be --   called directly except by the 'updateRobotLocation' function. --   The reason is that we need to make sure the 'robotsByLocation' --   map stays in sync.-unsafeSetRobotLocation :: V2 Int64 -> Robot -> Robot+unsafeSetRobotLocation :: Location -> Robot -> Robot unsafeSetRobotLocation loc r = r {_robotLocation = loc}  -- | A template robot's location.  Unlike 'robotLocation', this is a --   lens, since when dealing with robot templates there is as yet no --   'robotsByLocation' map to keep up-to-date.-trobotLocation :: Lens' TRobot (Maybe (V2 Int64))+trobotLocation :: Lens' TRobot (Maybe Location) trobotLocation = lens _robotLocation (\r l -> r {_robotLocation = l})  -- | Which way the robot is currently facing.-robotOrientation :: Lens' Robot (Maybe (V2 Int64))+robotOrientation :: Lens' Robot (Maybe Heading) robotOrientation = robotEntity . entityOrientation  -- | The robot's inventory. robotInventory :: Lens' Robot Inventory robotInventory = robotEntity . entityInventory --- | The robot's context+-- | The robot's context. robotContext :: Lens' Robot RobotContext +-- | The robot's context.+trobotContext :: Lens' TRobot RobotContext+trobotContext = lens _robotContext (\r c -> r {_robotContext = c})+ -- | The (unique) ID number of the robot.  This is only a Getter since --   the robot ID is immutable. robotID :: Getter Robot RID@@ -338,7 +317,7 @@ instantiateRobot i r =   r     { _robotID = i-    , _robotLocation = fromMaybe (V2 0 0) (_robotLocation r)+    , _robotLocation = fromMaybe zero (_robotLocation r)     }  -- | The ID number of the robot's parent, that is, the robot that@@ -349,19 +328,19 @@ -- | Is this robot extra heavy (thus requiring tank treads to move)? robotHeavy :: Lens' Robot Bool --- | A separate inventory for "installed devices", which provide the+-- | A separate inventory for equipped devices, which provide the --   robot with certain capabilities. -----   Note that every time the inventory of installed devices is+--   Note that every time the inventory of equipped devices is --   modified, this lens recomputes a cached set of the capabilities---   the installed devices provide, to speed up subsequent lookups to+--   the equipped devices provide, to speed up subsequent lookups to --   see whether the robot has a certain capability (see 'robotCapabilities')-installedDevices :: Lens' Robot Inventory-installedDevices = lens _installedDevices setInstalled+equippedDevices :: Lens' Robot Inventory+equippedDevices = lens _equippedDevices setEquipped  where-  setInstalled r inst =+  setEquipped r inst =     r-      { _installedDevices = inst+      { _equippedDevices = inst       , _robotCapabilities = inventoryCapabilities inst       } @@ -390,20 +369,20 @@ --   viewed? robotLogUpdated :: Lens' Robot Bool --- | A hash of a robot's entity record and installed devices, to+-- | A hash of a robot's entity record and equipped devices, to --   facilitate quickly deciding whether we need to redraw the robot --   info panel. inventoryHash :: Getter Robot Int-inventoryHash = to (\r -> 17 `hashWithSalt` (r ^. (robotEntity . entityHash)) `hashWithSalt` (r ^. installedDevices))+inventoryHash = to (\r -> 17 `hashWithSalt` (r ^. (robotEntity . entityHash)) `hashWithSalt` (r ^. equippedDevices))  -- | Does a robot know of an entity's existence? robotKnows :: Robot -> Entity -> Bool-robotKnows r e = contains0plus e (r ^. robotInventory) || contains0plus e (r ^. installedDevices)+robotKnows r e = contains0plus e (r ^. robotInventory) || contains0plus e (r ^. equippedDevices)  -- | Get the set of capabilities this robot possesses.  This is only a --   getter, not a lens, because it is automatically generated from---   the 'installedDevices'.  The only way to change a robot's---   capabilities is to modify its 'installedDevices'.+--   the 'equippedDevices'.  The only way to change a robot's+--   capabilities is to modify its 'equippedDevices'. robotCapabilities :: Getter Robot (Set Capability) robotCapabilities = to _robotCapabilities @@ -472,12 +451,12 @@   -- | Initial location.   RobotLocation phase ->   -- | Initial heading/direction.-  V2 Int64 ->+  Heading ->   -- | Robot display.   Display ->   -- | Initial CESK machine.   CESK ->-  -- | Installed devices.+  -- | Equipped devices.   [Entity] ->   -- | Initial inventory.   [(Count, Entity)] ->@@ -494,7 +473,7 @@         mkEntity disp name descr [] []           & entityOrientation ?~ dir           & entityInventory .~ fromElems inv-    , _installedDevices = inst+    , _equippedDevices = inst     , _robotCapabilities = inventoryCapabilities inst     , _robotLog = Seq.empty     , _robotLogUpdated = False
src/Swarm/Game/Scenario.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}@@ -17,14 +16,11 @@ -- conditions, which can be used both for building interactive -- tutorials and for standalone puzzles and scenarios. module Swarm.Game.Scenario (-  -- * Objectives-  Objective,-  objectiveGoal,-  objectiveCondition,-   -- * WorldDescription-  Cell (..),-  WorldDescription (..),+  PCell (..),+  Cell,+  PWorldDescription (..),+  WorldDescription,   IndexedTRobot,    -- * Scenario@@ -37,6 +33,7 @@   scenarioDescription,   scenarioCreative,   scenarioSeed,+  scenarioAttrs,   scenarioEntities,   scenarioRecipes,   scenarioKnown,@@ -56,183 +53,29 @@ import Control.Carrier.Lift (Lift, sendIO) import Control.Carrier.Throw.Either (Throw, throwError) import Control.Lens hiding (from, (<.>))-import Control.Monad (filterM, when)-import Control.Monad.Extra (mapMaybeM)-import Data.Aeson.Key qualified as Key-import Data.Aeson.KeyMap (KeyMap)-import Data.Aeson.KeyMap qualified as KeyMap-import Data.Map (Map)-import Data.Map qualified as M+import Control.Monad (filterM)+import Data.Aeson import Data.Maybe (catMaybes, isNothing, listToMaybe) import Data.Text (Text) import Data.Text qualified as T-import Data.Vector qualified as V import Data.Yaml as Y-import GHC.Generics (Generic)-import GHC.Int (Int64)-import Linear.V2 import Swarm.Game.Entity import Swarm.Game.Recipe-import Swarm.Game.Robot (TRobot, trobotName)-import Swarm.Game.Terrain+import Swarm.Game.Robot (TRobot)+import Swarm.Game.Scenario.Cell+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Validation+import Swarm.Game.Scenario.RobotLookup+import Swarm.Game.Scenario.Style+import Swarm.Game.Scenario.WorldDescription import Swarm.Language.Pipeline (ProcessedTerm)-import Swarm.Util (getDataFileNameSafe, reflow)+import Swarm.Util (getDataFileNameSafe) import Swarm.Util.Yaml import System.Directory (doesFileExist) import System.FilePath ((<.>), (</>)) import Witch (from, into)  --------------------------------------------------------------- Scenario objectives----------------------------------------------------------------- | An objective is a condition to be achieved by a player in a---   scenario.-data Objective = Objective-  { _objectiveGoal :: [Text]-  , _objectiveCondition :: ProcessedTerm-  }-  deriving (Eq, Show, Generic, ToJSON)--makeLensesWith (lensRules & generateSignatures .~ False) ''Objective---- | An explanation of the goal of the objective, shown to the player---   during play.  It is represented as a list of paragraphs.-objectiveGoal :: Lens' Objective [Text]---- | A winning condition for the objective, expressed as a---   program of type @cmd bool@.  By default, this program will be---   run to completion every tick (the usual limits on the number---   of CESK steps per tick do not apply).-objectiveCondition :: Lens' Objective ProcessedTerm--instance FromJSON Objective where-  parseJSON = withObject "objective" $ \v ->-    Objective-      <$> (fmap . map) reflow (v .:? "goal" .!= [])-      <*> (v .: "condition")----------------------------------------------------------------- Robot map----------------------------------------------------------------- | A robot template paired with its definition's index within--- the Scenario file-type IndexedTRobot = (Int, TRobot)---- | A map from names to robots, used to look up robots in scenario---   descriptions.-type RobotMap = Map Text IndexedTRobot---- | Create a 'RobotMap' from a list of robot templates.-buildRobotMap :: [TRobot] -> RobotMap-buildRobotMap rs = M.fromList $ zipWith (\x y -> (view trobotName y, (x, y))) [0 ..] rs----------------------------------------------------------------- Lookup utilities----------------------------------------------------------------- | Look up a thing by name, throwing a parse error if it is not---   found.-getThing :: String -> (Text -> m -> Maybe a) -> Text -> ParserE m a-getThing thing lkup name = do-  m <- getE-  case lkup name m of-    Nothing -> fail $ "Unknown " <> thing <> " name: " ++ show name-    Just a -> return a---- | Look up an entity by name in an 'EntityMap', throwing a parse---   error if it is not found.-getEntity :: Text -> ParserE EntityMap Entity-getEntity = getThing "entity" lookupEntityName---- | Look up a robot by name in a 'RobotMap', throwing a parse error---   if it is not found.-getRobot :: Text -> ParserE RobotMap IndexedTRobot-getRobot = getThing "robot" M.lookup----------------------------------------------------------------- World cells----------------------------------------------------------------- | A single cell in a world map, which contains a terrain value,---   and optionally an entity and robot.-data Cell = Cell-  { cellTerrain :: TerrainType-  , cellEntity :: Maybe Entity-  , cellRobots :: [IndexedTRobot]-  }-  deriving (Eq, Show)---- | Parse a tuple such as @[grass, rock, base]@ into a 'Cell'.  The---   entity and robot, if present, are immediately looked up and---   converted into 'Entity' and 'TRobot' values.  If they are not---   found, a parse error results.-instance FromJSONE (EntityMap, RobotMap) Cell where-  parseJSONE = withArrayE "tuple" $ \v -> do-    let tup = V.toList v-    when (null tup) $ fail "palette entry must nonzero length (terrain, optional entity and then robots if any)"--    terr <- liftE $ parseJSON (head tup)--    ent <- case tup ^? ix 1 of-      Nothing -> return Nothing-      Just e -> do-        meName <- liftE $ parseJSON @(Maybe Text) e-        traverse (localE fst . getEntity) meName--    let name2rob r = do-          mrName <- liftE $ parseJSON @(Maybe Text) r-          traverse (localE snd . getRobot) mrName--    robs <- mapMaybeM name2rob (drop 2 tup)--    return $ Cell terr ent robs----------------------------------------------------------------- World description----------------------------------------------------------------- | A world palette maps characters to 'Cell' values.-newtype WorldPalette = WorldPalette-  {unPalette :: KeyMap Cell}-  deriving (Eq, Show)--instance FromJSONE (EntityMap, RobotMap) WorldPalette where-  parseJSONE = withObjectE "palette" $ fmap WorldPalette . mapM parseJSONE---- | A description of a world parsed from a YAML file.-data WorldDescription = WorldDescription-  { defaultTerrain :: Maybe Cell-  , offsetOrigin :: Bool-  , palette :: WorldPalette-  , ul :: V2 Int64-  , area :: [[Cell]]-  }-  deriving (Eq, Show)--instance FromJSONE (EntityMap, RobotMap) WorldDescription where-  parseJSONE = withObjectE "world description" $ \v -> do-    pal <- v ..:? "palette" ..!= WorldPalette mempty-    WorldDescription-      <$> v ..:? "default"-      <*> liftE (v .:? "offset" .!= False)-      <*> pure pal-      <*> liftE (v .:? "upperleft" .!= V2 0 0)-      <*> liftE ((v .:? "map" .!= "") >>= paintMap pal)---- | "Paint" a world map using a 'WorldPalette', turning it from a raw---   string into a nested list of 'Cell' values by looking up each---   character in the palette, failing if any character in the raw map---   is not contained in the palette.-paintMap :: MonadFail m => WorldPalette -> Text -> m [[Cell]]-paintMap pal = traverse (traverse toCell . into @String) . T.lines- where-  toCell c = case KeyMap.lookup (Key.fromString [c]) (unPalette pal) of-    Nothing -> fail $ "Char not in world palette: " ++ show c-    Just cell -> return cell-------------------------------------------------------------- -- Scenario ------------------------------------------------------------ @@ -245,6 +88,7 @@   , _scenarioDescription :: Text   , _scenarioCreative :: Bool   , _scenarioSeed :: Maybe Int+  , _scenarioAttrs :: [CustomAttr]   , _scenarioEntities :: EntityMap   , _scenarioRecipes :: [Recipe Entity]   , _scenarioKnown :: [Text]@@ -263,6 +107,7 @@     -- parse custom entities     em <- liftE (buildEntityMap <$> (v .:? "entities" .!= []))     -- extend ambient EntityMap with custom entities+     withE em $ do       -- parse 'known' entity names and make sure they exist       known <- liftE (v .:? "known" .!= [])@@ -284,12 +129,13 @@         <*> liftE (v .:? "description" .!= "")         <*> liftE (v .:? "creative" .!= False)         <*> liftE (v .:? "seed")+        <*> liftE (v .:? "attrs" .!= [])         <*> pure em         <*> v ..:? "recipes" ..!= []         <*> pure known         <*> localE (,rsMap) (v ..: "world")         <*> pure rs-        <*> liftE (v .:? "objectives" .!= [])+        <*> (liftE (v .:? "objectives" .!= []) >>= validateObjectives)         <*> liftE (v .:? "solution")         <*> liftE (v .:? "stepsPerTick") @@ -318,6 +164,9 @@ -- | The seed used for the random number generator.  If @Nothing@, use --   a random seed / prompt the user for the seed. scenarioSeed :: Lens' Scenario (Maybe Int)++-- | Custom attributes defined in the scenario.+scenarioAttrs :: Lens' Scenario [CustomAttr]  -- | Any custom entities used for this scenario. scenarioEntities :: Lens' Scenario EntityMap
+ src/Swarm/Game/Scenario/Cell.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}++module Swarm.Game.Scenario.Cell (+  PCell (..),+  Cell,+) where++import Control.Lens hiding (from, (<.>))+import Control.Monad (when)+import Control.Monad.Extra (mapMaybeM)+import Data.Text (Text)+import Data.Vector qualified as V+import Data.Yaml as Y+import Swarm.Game.Entity+import Swarm.Game.Scenario.RobotLookup+import Swarm.Game.Terrain+import Swarm.Util.Yaml++------------------------------------------------------------+-- World cells+------------------------------------------------------------++-- | A single cell in a world map, which contains a terrain value,+--   and optionally an entity and robot.+--   It is parameterized on the Entity type to facilitate less+--   stateful versions of the Entity type in rendering scenario data.+data PCell e = Cell+  { cellTerrain :: TerrainType+  , cellEntity :: Maybe e+  , cellRobots :: [IndexedTRobot]+  }+  deriving (Eq, Show)++-- | A single cell in a world map, which contains a terrain value,+--   and optionally an entity and robot.+type Cell = PCell Entity++-- | Parse a tuple such as @[grass, rock, base]@ into a 'Cell'.  The+--   entity and robot, if present, are immediately looked up and+--   converted into 'Entity' and 'TRobot' values.  If they are not+--   found, a parse error results.+instance FromJSONE (EntityMap, RobotMap) Cell where+  parseJSONE = withArrayE "tuple" $ \v -> do+    let tup = V.toList v+    when (null tup) $ fail "palette entry must nonzero length (terrain, optional entity and then robots if any)"++    terr <- liftE $ parseJSON (head tup)++    ent <- case tup ^? ix 1 of+      Nothing -> return Nothing+      Just e -> do+        meName <- liftE $ parseJSON @(Maybe Text) e+        traverse (localE fst . getEntity) meName++    let name2rob r = do+          mrName <- liftE $ parseJSON @(Maybe Text) r+          traverse (localE snd . getRobot) mrName++    robs <- mapMaybeM name2rob (drop 2 tup)++    return $ Cell terr ent robs
+ src/Swarm/Game/Scenario/Objective.hs view
@@ -0,0 +1,195 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Swarm.Game.Scenario.Objective where++import Control.Applicative ((<|>))+import Control.Lens hiding (from, (<.>))+import Data.Aeson+import Data.Set qualified as Set+import Data.Text (Text)+import GHC.Generics (Generic)+import Swarm.Game.Scenario.Objective.Logic as L+import Swarm.Language.Pipeline (ProcessedTerm)+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.Util (reflow)++------------------------------------------------------------+-- Scenario objectives+------------------------------------------------------------++data PrerequisiteConfig = PrerequisiteConfig+  { previewable :: Bool+  -- ^ Typically, only the currently "active" objectives are+  -- displayed to the user in the Goals dialog. An objective+  -- is "active" if all of its prerequisites are met.+  --+  -- However, some objectives may be "high-level", in that they may+  -- explain the broader intention behind potentially multiple+  -- prerequisites.+  --+  -- Set this to option True to display this goal in the "upcoming" section even+  -- if the objective has currently unmet prerequisites.+  , logic :: Prerequisite ObjectiveLabel+  -- ^ Boolean expression of dependencies upon other objectives. Variables in this expression+  -- are the "id"s of other objectives, and become "true" if the corresponding objective is completed.+  -- The "condition" of the objective at hand shall not be evaluated until its+  -- prerequisite expression evaluates as True.+  --+  -- Note that the achievement of these objective dependencies is+  -- persistent; once achieved, they still count even if their "condition"+  -- might not still hold. The condition is never re-evaluated once True.+  }+  deriving (Eq, Show, Generic, ToJSON)++instance FromJSON PrerequisiteConfig where+  -- Parsing JSON/YAML 'PrerequisiteConfig' has a shorthand option+  -- in which the boolean expression can be written directly,+  -- bypassing the "logic" key.+  -- Furthermore, an "Id" in a boolean expressions can be written+  -- as a bare string without needing the "id" key.+  parseJSON val = preLogic val <|> preObject val+   where+    preObject = withObject "prerequisite" $ \v ->+      PrerequisiteConfig+        <$> (v .:? "previewable" .!= False)+        <*> v .: "logic"+    preLogic = fmap (PrerequisiteConfig False) . parseJSON++-- | An objective is a condition to be achieved by a player in a+--   scenario.+data Objective = Objective+  { _objectiveGoal :: [Text]+  , _objectiveTeaser :: Maybe Text+  , _objectiveCondition :: ProcessedTerm+  , _objectiveId :: Maybe ObjectiveLabel+  , _objectiveOptional :: Bool+  , _objectivePrerequisite :: Maybe PrerequisiteConfig+  , _objectiveHidden :: Bool+  , _objectiveAchievement :: Maybe AchievementInfo+  }+  deriving (Eq, Show, Generic, ToJSON)++makeLensesWith (lensRules & generateSignatures .~ False) ''Objective++-- | An explanation of the goal of the objective, shown to the player+--   during play.  It is represented as a list of paragraphs.+objectiveGoal :: Lens' Objective [Text]++-- | A very short (3-5 words) description of the goal for+-- displaying on the left side of the Objectives modal.+objectiveTeaser :: Lens' Objective (Maybe Text)++-- | A winning condition for the objective, expressed as a+--   program of type @cmd bool@.  By default, this program will be+--   run to completion every tick (the usual limits on the number+--   of CESK steps per tick do not apply).+objectiveCondition :: Lens' Objective ProcessedTerm++-- | Optional name by which this objective may be referenced+-- as a prerequisite for other objectives.+objectiveId :: Lens' Objective (Maybe Text)++-- | Indicates whether the objective is not required in order+-- to "win" the scenario. Useful for (potentially hidden) achievements.+-- If the field is not supplied, it defaults to False (i.e. the+-- objective is mandatory to "win").+objectiveOptional :: Lens' Objective Bool++-- | Dependencies upon other objectives+objectivePrerequisite :: Lens' Objective (Maybe PrerequisiteConfig)++-- | Whether the goal is displayed in the UI before completion.+-- The goal will always be revealed after it is completed.+--+-- This attribute often goes along with an Achievement.+objectiveHidden :: Lens' Objective Bool++-- | An optional Achievement that is to be registered globally+-- when this objective is completed.+objectiveAchievement :: Lens' Objective (Maybe AchievementInfo)++instance FromJSON Objective where+  parseJSON = withObject "objective" $ \v ->+    Objective+      <$> (fmap . map) reflow (v .:? "goal" .!= [])+      <*> (v .:? "teaser")+      <*> (v .: "condition")+      <*> (v .:? "id")+      <*> (v .:? "optional" .!= False)+      <*> (v .:? "prerequisite")+      <*> (v .:? "hidden" .!= False)+      <*> (v .:? "achievement")++data CompletionBuckets = CompletionBuckets+  { incomplete :: [Objective]+  , completed :: [Objective]+  , unwinnable :: [Objective]+  }+  deriving (Show, Generic, FromJSON, ToJSON)++data ObjectiveCompletion = ObjectiveCompletion+  { completionBuckets :: CompletionBuckets+  -- ^ This is the authoritative "completion status"+  -- for all objectives.+  -- Note that there is a separate Set to store the+  -- completion status of prerequisite objectives, which+  -- must be carefully kept in sync with this.+  -- Those prerequisite objectives are required to have+  -- labels, but other objectives are not.+  -- Therefore only prerequisites exist in the completion+  -- map keyed by label.+  , completedIDs :: Set.Set ObjectiveLabel+  }+  deriving (Show, Generic, FromJSON, ToJSON)++-- | Concatenates all incomplete and completed objectives.+listAllObjectives :: CompletionBuckets -> [Objective]+listAllObjectives (CompletionBuckets x y z) = x <> y <> z++addCompleted :: Objective -> ObjectiveCompletion -> ObjectiveCompletion+addCompleted obj (ObjectiveCompletion buckets cmplIds) =+  ObjectiveCompletion newBuckets newCmplById+ where+  newBuckets =+    buckets+      { completed = obj : completed buckets+      }+  newCmplById = case _objectiveId obj of+    Nothing -> cmplIds+    Just lbl -> Set.insert lbl cmplIds++addUnwinnable :: Objective -> ObjectiveCompletion -> ObjectiveCompletion+addUnwinnable obj (ObjectiveCompletion buckets cmplIds) =+  ObjectiveCompletion newBuckets cmplIds+ where+  newBuckets =+    buckets+      { unwinnable = obj : unwinnable buckets+      }++setIncomplete ::+  ([Objective] -> [Objective]) ->+  ObjectiveCompletion ->+  ObjectiveCompletion+setIncomplete f (ObjectiveCompletion buckets cmplIds) =+  ObjectiveCompletion newBuckets cmplIds+ where+  newBuckets =+    buckets+      { incomplete = f $ incomplete buckets+      }++addIncomplete :: Objective -> ObjectiveCompletion -> ObjectiveCompletion+addIncomplete obj = setIncomplete (obj :)++-- | Returns the "ObjectiveCompletion" with the "incomplete" goals+-- extracted to a separate tuple member.+-- This is intended as input to a "fold".+extractIncomplete :: ObjectiveCompletion -> (ObjectiveCompletion, [Objective])+extractIncomplete oc =+  (withoutIncomplete, incompleteGoals)+ where+  incompleteGoals = incomplete $ completionBuckets oc+  withoutIncomplete = setIncomplete (const []) oc
+ src/Swarm/Game/Scenario/Objective/Graph.hs view
@@ -0,0 +1,144 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- | Utilities for performing graph analysis on Objective prerequisites+module Swarm.Game.Scenario.Objective.Graph where++import Control.Arrow ((&&&))+import Data.Aeson+import Data.BoolExpr (Signed (Positive))+import Data.BoolExpr qualified as BE+import Data.Graph (Graph, SCC (AcyclicSCC), graphFromEdges, stronglyConnComp)+import Data.Map (Map)+import Data.Map.Strict qualified as M+import Data.Maybe (mapMaybe)+import Data.Set qualified as Set+import Data.Text qualified as T+import Data.Tuple (swap)+import GHC.Generics (Generic)+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Logic as L+import Swarm.Game.Scenario.Objective.WinCheck++-- | This is only needed for constructing a Graph,+-- which requires all nodes to have a key.+data ObjectiveId+  = Label (Signed ObjectiveLabel)+  | -- | for unlabeled objectives+    Ordinal Int+  deriving (Eq, Ord, Show, Generic, ToJSON)++data GraphInfo = GraphInfo+  { actualGraph :: Graph+  , isAcyclic :: Bool+  , sccInfo :: [SCC Objective]+  , nodeIDs :: [ObjectiveId]+  }+  deriving (Show, Generic, ToJSON)++instance ToJSON (SCC Objective) where+  toJSON = String . T.pack . show++instance ToJSON Graph where+  toJSON = String . T.pack . show++getConstFromSigned :: BE.Signed a -> a+getConstFromSigned = \case+  BE.Positive x -> x+  BE.Negative x -> x++-- | Collect all of the constants that have a negation.+-- This is necessary for enumerating all of the distinct+-- nodes when constructing a Graph, as we treat a constant+-- and its negation as distinct nodes.+getNegatedIds :: [Objective] -> Map ObjectiveLabel Objective+getNegatedIds objs =+  M.fromList $ mapMaybe f allConstants+ where+  objectivesById = getObjectivesById objs++  allPrereqExpressions = mapMaybe _objectivePrerequisite objs+  allConstants =+    mapMaybe onlyNegative+      . Set.toList+      . Set.unions+      . map (getDistinctConstants . logic)+      $ allPrereqExpressions++  f = sequenceA . \x -> (x, M.lookup x objectivesById)++  onlyNegative = \case+    BE.Negative x -> Just x+    _ -> Nothing++getObjectivesById :: [Objective] -> Map ObjectiveLabel Objective+getObjectivesById objs =+  M.fromList $+    map swap $+      mapMaybe (sequenceA . (id &&& _objectiveId)) objs++-- | Uses the textual labels for those objectives that+-- have them, and assigns arbitrary integer IDs for+-- the remaining.+--+-- Only necessary for constructing a "Graph".+assignIds :: [Objective] -> Map ObjectiveId Objective+assignIds objs =+  unlabeledObjsMap <> labeledObjsMap+ where+  objectivesById = getObjectivesById objs++  labeledObjsMap = M.mapKeys (Label . Positive) objectivesById++  unlabeledObjs = filter (null . _objectiveId) objs+  unlabeledObjsMap = M.fromList $ zipWith (\x y -> (Ordinal x, y)) [0 ..] unlabeledObjs++type Edges = [(Objective, ObjectiveId, [ObjectiveId])]++-- | NOTE: Based strictly on the goal labels, the graph could+-- potentially contain a cycle, if there exist+-- mutually-exclusive goals. That is, if goal A depends on the NOT+-- of "goal B".  Goal B could then also depend on "NOT Goal A" (re-enforcing the+-- mutual-exclusivity), or it could mandate a completion order, e.g.:+-- Goal A and Goal B are simultaneously available to pursue.  However, if the+-- player completes Goal B first, then it closes off the option to complete+-- Goal A.  However, if Goal A is completed first, then the user is also allowed+-- to complete Goal B.+--+-- To avoid a "cycle" in this circumstance, "A" needs to exist as a distinct node+-- from "NOT A" in the graph.+makeGraph :: Edges -> Graph+makeGraph edges =+  myGraph+ where+  (myGraph, _, _) = graphFromEdges edges++makeGraphEdges :: [Objective] -> Edges+makeGraphEdges objectives =+  rootTuples <> negatedTuples+ where+  rootTuples = map f $ M.toList $ assignIds objectives+  negatedTuples = map gg $ M.toList $ getNegatedIds objectives+  gg (k, v) = (v, Label $ BE.Negative k, [])++  f (k, v) = (v, k, maybe [] (map Label . g) $ _objectivePrerequisite v)+  g = Set.toList . getDistinctConstants . logic++isAcyclicGraph :: [SCC Objective] -> Bool+isAcyclicGraph =+  all isAcyclicVertex+ where+  isAcyclicVertex = \case+    AcyclicSCC _ -> True+    _ -> False++makeGraphInfo :: ObjectiveCompletion -> GraphInfo+makeGraphInfo oc =+  GraphInfo+    (makeGraph edges)+    (isAcyclicGraph connectedComponents)+    connectedComponents+    (M.keys $ assignIds objs)+ where+  edges = makeGraphEdges objs+  connectedComponents = stronglyConnComp edges+  objs = listAllObjectives $ completionBuckets oc
+ src/Swarm/Game/Scenario/Objective/Logic.hs view
@@ -0,0 +1,46 @@+-- | A model for defining boolean expressions for Objective prerequisites.+--+-- This model is intended to be user-facing in the .yaml files, and is+-- distinct from that in 'Data.BoolExpr'.+module Swarm.Game.Scenario.Objective.Logic where++import Control.Applicative ((<|>))+import Data.Aeson+import Data.BoolExpr+import Data.Char (toLower)+import Data.List.NonEmpty (NonEmpty)+import Data.Text (Text)+import GHC.Generics (Generic)++type ObjectiveLabel = Text++-- | In contrast with the "BoolExpr" type,+-- "And" and "Or" can have /one or more/ children+-- instead of /exactly two/.+data Prerequisite a+  = And (NonEmpty (Prerequisite a))+  | Or (NonEmpty (Prerequisite a))+  | Not (Prerequisite a)+  | Id a+  deriving (Eq, Show, Generic, Functor, Foldable)++prerequisiteOptions :: Options+prerequisiteOptions =+  defaultOptions+    { sumEncoding = ObjectWithSingleField+    , constructorTagModifier = map toLower+    }++instance ToJSON (Prerequisite ObjectiveLabel) where+  toJSON = genericToJSON prerequisiteOptions++instance FromJSON (Prerequisite ObjectiveLabel) where+  parseJSON x = preString x <|> genericParseJSON prerequisiteOptions x+   where+    preString = withText "prerequisite" $ pure . Id++toBoolExpr :: Prerequisite a -> BoolExpr a+toBoolExpr (And xs) = foldr1 BAnd (fmap toBoolExpr xs)+toBoolExpr (Or xs) = foldr1 BOr (fmap toBoolExpr xs)+toBoolExpr (Not x) = BNot $ toBoolExpr x+toBoolExpr (Id x) = BConst $ pure x
+ src/Swarm/Game/Scenario/Objective/Presentation/Model.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoGeneralizedNewtypeDeriving #-}++-- A UI-centric model for Objective presentation.+module Swarm.Game.Scenario.Objective.Presentation.Model where++import Brick.Focus+import Brick.Widgets.List qualified as BL+import Control.Lens (makeLenses)+import Data.Aeson+import Data.List.NonEmpty (NonEmpty, nonEmpty)+import Data.List.NonEmpty qualified as NE+import Data.Map (Map)+import Data.Map qualified as M+import Data.Maybe (mapMaybe)+import GHC.Generics (Generic)+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.WinCheck+import Swarm.TUI.Model.Name+import Swarm.Util (listEnums)++-- | These are intended to be used as keys in a map+-- of lists of goals.+data GoalStatus+  = -- | Goals in this category have other goals as prerequisites.+    -- However, they are only displayed if the "previewable" attribute+    -- is `true`.+    Upcoming+  | -- | Goals in this category may be pursued in parallel.+    -- However, they are only displayed if the "hidden" attribute+    -- is `false`.+    Active+  | -- | A goal's programmatic condition, as well as all its prerequisites, were completed.+    -- This is a "latch" mechanism; at some point the conditions required to meet the goal may+    -- no longer hold. Nonetheless, the goal remains "completed".+    Completed+  | -- | A goal that can no longer be achieved.+    -- If this goal is not an "optional" goal, then the player+    -- also "Loses" the scenario.+    --+    -- Note that currently the only way to "Fail" a goal is by way+    -- of a negative prerequisite that was completed.+    Failed+  deriving (Show, Eq, Ord, Bounded, Enum, Generic, ToJSON, ToJSONKey)++-- | TODO: #1044 Could also add an "ObjectiveFailed" constructor...+newtype Announcement+  = ObjectiveCompleted Objective+  deriving (Show, Generic, ToJSON)++type CategorizedGoals = Map GoalStatus (NonEmpty Objective)++data GoalEntry+  = Header GoalStatus+  | Goal GoalStatus Objective++isHeader :: GoalEntry -> Bool+isHeader = \case+  Header _ -> True+  _ -> False++data GoalTracking = GoalTracking+  { announcements :: [Announcement]+  -- ^ TODO: #1044 the actual contents of these are not used yet,+  -- other than as a flag to pop up the Goal dialog.+  , goals :: CategorizedGoals+  }+  deriving (Generic, ToJSON)++data GoalDisplay = GoalDisplay+  { _goalsContent :: GoalTracking+  , _listWidget :: BL.List Name GoalEntry+  -- ^ required for maintaining the selection/navigation+  -- state among list items+  , _focus :: FocusRing Name+  }++makeLenses ''GoalDisplay++emptyGoalDisplay :: GoalDisplay+emptyGoalDisplay =+  GoalDisplay+    (GoalTracking mempty mempty)+    (BL.list (GoalWidgets ObjectivesList) mempty 1)+    (focusRing $ map GoalWidgets listEnums)++hasAnythingToShow :: GoalTracking -> Bool+hasAnythingToShow (GoalTracking ann g) = not (null ann && null g)++hasMultipleGoals :: GoalTracking -> Bool+hasMultipleGoals gt =+  goalCount > 1+ where+  goalCount = sum . M.elems . M.map NE.length . goals $ gt++constructGoalMap :: Bool -> ObjectiveCompletion -> CategorizedGoals+constructGoalMap isCheating objectiveCompletion@(ObjectiveCompletion buckets _) =+  M.fromList $+    mapMaybe (traverse nonEmpty) categoryList+ where+  categoryList =+    [ (Upcoming, displayableInactives)+    , (Active, suppressHidden activeGoals)+    , (Completed, completed buckets)+    , (Failed, unwinnable buckets)+    ]++  displayableInactives =+    suppressHidden $+      filter (maybe False previewable . _objectivePrerequisite) inactiveGoals++  suppressHidden =+    if isCheating+      then id+      else filter $ not . _objectiveHidden++  (activeGoals, inactiveGoals) = partitionActiveObjectives objectiveCompletion
+ src/Swarm/Game/Scenario/Objective/Presentation/Render.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}++-- Display logic for Objectives.+module Swarm.Game.Scenario.Objective.Presentation.Render where++import Brick hiding (Direction, Location)+import Brick.Focus+import Brick.Widgets.Center+import Brick.Widgets.List qualified as BL+import Control.Applicative ((<|>))+import Control.Lens hiding (Const, from)+import Data.List.NonEmpty qualified as NE+import Data.Map.Strict qualified as M+import Data.Maybe (listToMaybe)+import Data.Vector qualified as V+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Presentation.Model+import Swarm.TUI.Attr+import Swarm.TUI.Model.Name+import Swarm.TUI.View.Util++makeListWidget :: GoalTracking -> BL.List Name GoalEntry+makeListWidget (GoalTracking _announcements categorizedObjs) =+  BL.listMoveTo 1 $ BL.list (GoalWidgets ObjectivesList) (V.fromList objList) 1+ where+  objList = concatMap f $ M.toList categorizedObjs+  f (h, xs) = Header h : map (Goal h) (NE.toList xs)++renderGoalsDisplay :: GoalDisplay -> Widget Name+renderGoalsDisplay gd =+  if hasMultiple+    then+      hBox+        [ leftSide+        , hLimitPercent 70 $ padLeft (Pad 2) goalElaboration+        ]+    else goalElaboration+ where+  hasMultiple = hasMultipleGoals $ gd ^. goalsContent+  lw = _listWidget gd+  fr = _focus gd+  leftSide =+    hLimitPercent 30 $+      padAll 1 $+        vBox+          [ hCenter $ str "Goals"+          , padAll 1 $+              vLimit 10 $+                withFocusRing fr (BL.renderList drawGoalListItem) lw+          ]++  -- Adds very subtle coloring to indicate focus switch+  highlightIfFocused = case (hasMultiple, focusGetCurrent fr) of+    (True, Just (GoalWidgets GoalSummary)) -> withAttr lightCyanAttr+    _ -> id++  -- Note: An extra "padRight" is inserted to account for the vertical scrollbar,+  -- whether or not it appears.+  goalElaboration =+    clickable (GoalWidgets GoalSummary) $+      maybeScroll ModalViewport $+        maybe emptyWidget (padAll 1 . padRight (Pad 1) . highlightIfFocused . singleGoalDetails . snd) $+          BL.listSelectedElement lw++getCompletionIcon :: Objective -> GoalStatus -> Widget Name+getCompletionIcon obj = \case+  Upcoming -> withAttr yellowAttr $ txt " ○  "+  Active -> withAttr cyanAttr $ txt " ○  "+  Failed -> withAttr redAttr $ txt " ●  "+  Completed -> withAttr colorAttr $ txt " ●  "+   where+    colorAttr =+      if obj ^. objectiveHidden+        then magentaAttr+        else greenAttr++drawGoalListItem ::+  Bool ->+  GoalEntry ->+  Widget Name+drawGoalListItem _isSelected e = case e of+  Header gs -> withAttr boldAttr $ str $ show gs+  Goal gs obj -> getCompletionIcon obj gs <+> titleWidget+   where+    textSource = obj ^. objectiveTeaser <|> obj ^. objectiveId <|> listToMaybe (obj ^. objectiveGoal)+    titleWidget = maybe (txt "?") withEllipsis textSource++singleGoalDetails :: GoalEntry -> Widget Name+singleGoalDetails = \case+  Header _gs -> displayParagraphs [" "]+  Goal _gs obj -> displayParagraphs $ obj ^. objectiveGoal
+ src/Swarm/Game/Scenario/Objective/Validation.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Validity checking for Objective prerequisites+module Swarm.Game.Scenario.Objective.Validation where++import Control.Monad (unless)+import Data.Foldable (for_, toList)+import Data.Graph (stronglyConnComp)+import Data.Maybe (mapMaybe)+import Data.Set qualified as Set+import Data.Text qualified as T+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Graph+import Swarm.Util (quote)+import Witch (into)++-- | Performs monadic validation before returning+-- the "pure" construction of a wrapper record.+-- This validation entails:+-- 1) Ensuring that all goal references utilized in prerequisites+--    actually exist+-- 2) Ensuring that the graph of dependencies is acyclic.+validateObjectives ::+  MonadFail m =>+  [Objective] ->+  m [Objective]+validateObjectives objectives = do+  for_ objectives $ \x -> case _objectivePrerequisite x of+    Just p ->+      unless (null remaining) $+        fail . into @String $+          T.unwords+            [ "Reference to undefined objective(s)"+            , T.intercalate ", " (map quote $ Set.toList remaining) <> "."+            , "Defined are:"+            , T.intercalate ", " (map quote $ Set.toList allIds)+            ]+     where+      refs = Set.fromList $ toList $ logic p+      remaining = Set.difference refs allIds+    Nothing -> return ()++  unless (isAcyclicGraph connectedComponents) $+    fail . into @String $+      T.unwords ["There are dependency cycles in the prerequisites."]++  return objectives+ where+  connectedComponents = stronglyConnComp $ makeGraphEdges objectives+  allIds = Set.fromList $ mapMaybe _objectiveId objectives
+ src/Swarm/Game/Scenario/Objective/WinCheck.hs view
@@ -0,0 +1,83 @@+-- | Utilities to check whether conditions are met for a game win/loss.+module Swarm.Game.Scenario.Objective.WinCheck where++import Data.Aeson+import Data.BoolExpr qualified as BE+import Data.BoolExpr.Simplify qualified as Simplify+import Data.List (partition)+import Data.Map qualified as M+import Data.Set (Set)+import Data.Set qualified as Set+import GHC.Generics (Generic)+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Logic as L++-- | We have "won" if all of the "unwinnable" or remaining "incomplete" objectives are "optional".+didWin :: ObjectiveCompletion -> Bool+didWin oc = all _objectiveOptional $ incomplete buckets <> unwinnable buckets+ where+  buckets = completionBuckets oc++-- | We have "lost" if any of the "unwinnable" objectives not "optional".+didLose :: ObjectiveCompletion -> Bool+didLose oc = not $ all _objectiveOptional $ unwinnable buckets+ where+  buckets = completionBuckets oc++isPrereqsSatisfied :: ObjectiveCompletion -> Objective -> Bool+isPrereqsSatisfied completions =+  maybe True f . _objectivePrerequisite+ where+  f = BE.evalBoolExpr getTruth . L.toBoolExpr . logic++  getTruth :: ObjectiveLabel -> Bool+  getTruth label = Set.member label $ completedIDs completions++isUnwinnablePrereq :: Set ObjectiveLabel -> Prerequisite ObjectiveLabel -> Bool+isUnwinnablePrereq completedObjectives =+  Simplify.cannotBeTrue . Simplify.replace boolMap . L.toBoolExpr+ where+  boolMap =+    M.fromList $+      map (,True) $+        Set.toList completedObjectives++isUnwinnable :: ObjectiveCompletion -> Objective -> Bool+isUnwinnable completions obj =+  maybe False (isUnwinnablePrereq (completedIDs completions) . logic) $ _objectivePrerequisite obj++-- | The first element of the returned tuple consists of "active" objectives,+-- the second element "inactive".+partitionActiveObjectives :: ObjectiveCompletion -> ([Objective], [Objective])+partitionActiveObjectives oc =+  partition (isPrereqsSatisfied oc) $+    incomplete $+      completionBuckets oc++getActiveObjectives :: ObjectiveCompletion -> [Objective]+getActiveObjectives =+  fst . partitionActiveObjectives++-- | For debugging only (via Web API)+data PrereqSatisfaction = PrereqSatisfaction+  { objective :: Objective+  , deps :: Set (BE.Signed ObjectiveLabel)+  , prereqsSatisfied :: Bool+  }+  deriving (Generic, ToJSON)++-- | Used only by the web interface for debugging+getSatisfaction :: ObjectiveCompletion -> [PrereqSatisfaction]+getSatisfaction oc =+  map f $+    listAllObjectives $+      completionBuckets oc+ where+  f y =+    PrereqSatisfaction+      y+      (maybe mempty (getDistinctConstants . logic) $ _objectivePrerequisite y)+      (isPrereqsSatisfied oc y)++getDistinctConstants :: (Ord a) => Prerequisite a -> Set (BE.Signed a)+getDistinctConstants = Set.fromList . BE.constants . toBoolExpr
+ src/Swarm/Game/Scenario/RobotLookup.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}++module Swarm.Game.Scenario.RobotLookup where++import Control.Lens hiding (from, (<.>))+import Data.Map (Map)+import Data.Map qualified as M+import Data.Text (Text)+import Swarm.Game.Entity+import Swarm.Game.Robot (TRobot, trobotName)+import Swarm.Util.Yaml++------------------------------------------------------------+-- Robot map+------------------------------------------------------------++-- | A robot template paired with its definition's index within+-- the Scenario file+type IndexedTRobot = (Int, TRobot)++-- | A map from names to robots, used to look up robots in scenario+--   descriptions.+type RobotMap = Map Text IndexedTRobot++-- | Create a 'RobotMap' from a list of robot templates.+buildRobotMap :: [TRobot] -> RobotMap+buildRobotMap rs = M.fromList $ zipWith (\x y -> (view trobotName y, (x, y))) [0 ..] rs++------------------------------------------------------------+-- Lookup utilities+------------------------------------------------------------++-- | Look up a thing by name, throwing a parse error if it is not+--   found.+getThing :: String -> (Text -> m -> Maybe a) -> Text -> ParserE m a+getThing thing lkup name = do+  m <- getE+  case lkup name m of+    Nothing -> fail $ "Unknown " <> thing <> " name: " ++ show name+    Just a -> return a++-- | Look up an entity by name in an 'EntityMap', throwing a parse+--   error if it is not found.+getEntity :: Text -> ParserE EntityMap Entity+getEntity = getThing "entity" lookupEntityName++-- | Look up a robot by name in a 'RobotMap', throwing a parse error+--   if it is not found.+getRobot :: Text -> ParserE RobotMap IndexedTRobot+getRobot = getThing "robot" M.lookup
+ src/Swarm/Game/Scenario/Style.hs view
@@ -0,0 +1,37 @@+module Swarm.Game.Scenario.Style where++import Data.Aeson+import Data.Set (Set)+import Data.Text (Text)+import GHC.Generics (Generic)++data StyleFlag+  = Standout+  | Italic+  | Strikethrough+  | Underline+  | ReverseVideo+  | Blink+  | Dim+  | Bold+  deriving (Eq, Ord, Show, Generic)++styleFlagJsonOptions :: Options+styleFlagJsonOptions =+  defaultOptions+    { sumEncoding = UntaggedValue+    }++instance FromJSON StyleFlag where+  parseJSON = genericParseJSON styleFlagJsonOptions++newtype HexColor = HexColor Text+  deriving (Eq, Show, Generic, FromJSON)++data CustomAttr = CustomAttr+  { name :: String+  , fg :: Maybe HexColor+  , bg :: Maybe HexColor+  , style :: Maybe (Set StyleFlag)+  }+  deriving (Eq, Show, Generic, FromJSON)
+ src/Swarm/Game/Scenario/WorldDescription.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}++module Swarm.Game.Scenario.WorldDescription where++import Data.Aeson.Key qualified as Key+import Data.Aeson.KeyMap (KeyMap)+import Data.Aeson.KeyMap qualified as KeyMap+import Data.Text (Text)+import Data.Text qualified as T+import Data.Yaml as Y+import Swarm.Game.Entity+import Swarm.Game.Scenario.Cell+import Swarm.Game.Scenario.RobotLookup+import Swarm.Util.Location+import Swarm.Util.Yaml+import Witch (into)++------------------------------------------------------------+-- World description+------------------------------------------------------------++-- | A world palette maps characters to 'Cell' values.+newtype WorldPalette e = WorldPalette+  {unPalette :: KeyMap (PCell e)}+  deriving (Eq, Show)++instance FromJSONE (EntityMap, RobotMap) (WorldPalette Entity) where+  parseJSONE = withObjectE "palette" $ fmap WorldPalette . mapM parseJSONE++-- | A description of a world parsed from a YAML file.+-- This type is parameterized to accommodate Cells that+-- utilize a less stateful Entity type.+data PWorldDescription e = WorldDescription+  { defaultTerrain :: Maybe (PCell e)+  , offsetOrigin :: Bool+  , palette :: WorldPalette e+  , ul :: Location+  , area :: [[PCell e]]+  }+  deriving (Eq, Show)++type WorldDescription = PWorldDescription Entity++instance FromJSONE (EntityMap, RobotMap) WorldDescription where+  parseJSONE = withObjectE "world description" $ \v -> do+    pal <- v ..:? "palette" ..!= WorldPalette mempty+    WorldDescription+      <$> v ..:? "default"+      <*> liftE (v .:? "offset" .!= False)+      <*> pure pal+      <*> liftE (v .:? "upperleft" .!= origin)+      <*> liftE ((v .:? "map" .!= "") >>= paintMap pal)++-- | "Paint" a world map using a 'WorldPalette', turning it from a raw+--   string into a nested list of 'Cell' values by looking up each+--   character in the palette, failing if any character in the raw map+--   is not contained in the palette.+paintMap :: MonadFail m => WorldPalette e -> Text -> m [[PCell e]]+paintMap pal = traverse (traverse toCell . into @String) . T.lines+ where+  toCell c = case KeyMap.lookup (Key.fromString [c]) (unPalette pal) of+    Nothing -> fail $ "Char not in world palette: " ++ show c+    Just cell -> return cell
src/Swarm/Game/ScenarioInfo.hs view
@@ -49,7 +49,7 @@ import Control.Carrier.Lift (Lift, sendIO) import Control.Carrier.Throw.Either (Throw, runThrow, throwError) import Control.Lens hiding (from, (<.>))-import Control.Monad (unless, when)+import Control.Monad (filterM, unless, when) import Data.Aeson (   Options (..),   defaultOptions,@@ -59,7 +59,7 @@  ) import Data.Char (isSpace, toLower) import Data.Function (on)-import Data.List (intercalate, stripPrefix, (\\))+import Data.List (intercalate, isPrefixOf, stripPrefix, (\\)) import Data.Map (Map) import Data.Map qualified as M import Data.Maybe (isJust)@@ -90,20 +90,20 @@ data ScenarioStatus   = NotStarted   | InProgress-      { -- | Time when the scenario was started including time zone.-        _scenarioStarted :: ZonedTime-      , -- | Time elapsed until quitting the scenario.-        _scenarioElapsed :: NominalDiffTime-      , -- | Ticks elapsed until quitting the scenario.-        _scenarioElapsedTicks :: Integer+      { _scenarioStarted :: ZonedTime+      -- ^ Time when the scenario was started including time zone.+      , _scenarioElapsed :: NominalDiffTime+      -- ^ Time elapsed until quitting the scenario.+      , _scenarioElapsedTicks :: Integer+      -- ^ Ticks elapsed until quitting the scenario.       }   | Complete-      { -- | Time when the scenario was started including time zone.-        _scenarioStarted :: ZonedTime-      , -- | Time elapsed until quitting the scenario.-        _scenarioElapsed :: NominalDiffTime-      , -- | Ticks elapsed until quitting the scenario.-        _scenarioElapsedTicks :: Integer+      { _scenarioStarted :: ZonedTime+      -- ^ Time when the scenario was started including time zone.+      , _scenarioElapsed :: NominalDiffTime+      -- ^ Time elapsed until quitting the scenario.+      , _scenarioElapsedTicks :: Integer+      -- ^ Ticks elapsed until quitting the scenario.       }   deriving (Eq, Ord, Show, Read, Generic) @@ -256,11 +256,14 @@     False -> do       when (dirName /= "Testing") $         sendIO . putStrLn $-          "Warning: no " <> orderFileName <> " file found in " <> dirName+          "Warning: no "+            <> orderFileName+            <> " file found in "+            <> dirName             <> ", using alphabetical order"       return Nothing     True -> Just . filter (not . null) . lines <$> sendIO (readFile orderFile)-  fs <- sendIO $ keepYamlOrDirectory <$> listDirectory dir+  fs <- sendIO $ keepYamlOrPublicDirectory dir =<< listDirectory dir    case morder of     Just order -> do@@ -269,25 +272,38 @@        unless (null missing) $         sendIO . putStr . unlines $-          ( "Warning: while processing " <> (dirName </> orderFileName) <> ": files not listed in "+          ( "Warning: while processing "+              <> (dirName </> orderFileName)+              <> ": files not listed in "               <> orderFileName               <> " will be ignored"-          ) :-          map ("  - " <>) missing+          )+            : map ("  - " <>) missing        unless (null dangling) $         sendIO . putStr . unlines $-          ( "Warning: while processing " <> (dirName </> orderFileName)+          ( "Warning: while processing "+              <> (dirName </> orderFileName)               <> ": nonexistent files will be ignored"-          ) :-          map ("  - " <>) dangling+          )+            : map ("  - " <>) dangling     Nothing -> pure ()    -- Only keep the files from 00-ORDER.txt that actually exist.   let morder' = filter (`elem` fs) <$> morder   SC morder' . M.fromList <$> mapM (\item -> (item,) <$> loadScenarioItem em (dir </> item)) fs  where-  keepYamlOrDirectory = filter (\f -> takeExtensions f `elem` ["", ".yaml"])+  -- Keep only files which are .yaml files or directories that start+  -- with something other than an underscore.+  keepYamlOrPublicDirectory = filterM . isCatalogEntry++  -- Whether the directory or file should be included in the scenario catalog.+  isCatalogEntry d f = do+    isDir <- doesDirectoryExist $ d </> f+    return $+      if isDir+        then not $ "_" `isPrefixOf` f+        else takeExtensions f == ".yaml"  -- | How to transform scenario path to save path. scenarioPathToSavePath :: FilePath -> FilePath -> FilePath
src/Swarm/Game/State.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-}@@ -20,10 +19,12 @@   -- * Game state record and related types   ViewCenterRule (..),   REPLStatus (..),+  WinStatus (..),   WinCondition (..),+  ObjectiveCompletion (..),   _NoWinCondition,   _WinConditions,-  _Won,+  Announcement (..),   RunStatus (..),   Seed,   GameState,@@ -32,6 +33,8 @@   creativeMode,   winCondition,   winSolution,+  gameAchievements,+  announcementQueue,   runStatus,   paused,   robotMap,@@ -114,18 +117,17 @@ import Data.Array (Array, listArray) import Data.Bifunctor (first) import Data.Foldable (toList)-import Data.Int (Int64)+import Data.Int (Int32) import Data.IntMap (IntMap) import Data.IntMap qualified as IM import Data.IntSet (IntSet) import Data.IntSet qualified as IS import Data.IntSet.Lens (setOf) import Data.List (partition, sortOn)-import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NE import Data.Map (Map) import Data.Map qualified as M-import Data.Maybe (fromMaybe, isJust, mapMaybe)+import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) import Data.Sequence (Seq ((:<|))) import Data.Sequence qualified as Seq import Data.Set qualified as S@@ -134,8 +136,7 @@ import Data.Text.IO qualified as T (readFile) import Data.Time (getZonedTime) import GHC.Generics (Generic)-import Linear-import Swarm.Game.CESK (emptyStore, initMachine)+import Swarm.Game.CESK (emptyStore, finalValue, initMachine) import Swarm.Game.Entity import Swarm.Game.Recipe (   Recipe,@@ -145,9 +146,10 @@   reqRecipeMap,  ) import Swarm.Game.Robot+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Presentation.Model import Swarm.Game.ScenarioInfo import Swarm.Game.Terrain (TerrainType (..))-import Swarm.Game.Value (Value) import Swarm.Game.World (Coords (..), WorldFun (..), locToCoords, worldFunFromArray) import Swarm.Game.World qualified as W import Swarm.Game.WorldGen (Seed, findGoodOrigin, testWorld2FromArray)@@ -155,10 +157,14 @@ import Swarm.Language.Context qualified as Ctx import Swarm.Language.Pipeline (ProcessedTerm) import Swarm.Language.Pipeline.QQ (tmQ)-import Swarm.Language.Syntax (Const, Term (TText), allConst)+import Swarm.Language.Syntax (Const, Term' (TText), allConst) import Swarm.Language.Typed (Typed (Typed)) import Swarm.Language.Types+import Swarm.Language.Value (Value)+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions import Swarm.Util (getDataFileNameSafe, getElemsInArea, isRightOr, manhattan, uniq, (<+=), (<<.=), (?))+import Swarm.Util.Location import System.Clock qualified as Clock import System.Random (StdGen, mkStdGen, randomRIO) import Witch (into)@@ -175,7 +181,7 @@ --   world viewport. data ViewCenterRule   = -- | The view should be centered on an absolute position.-    VCLocation (V2 Int64)+    VCLocation Location   | -- | The view should be centered on a certain robot.     VCRobot RID   deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON)@@ -186,6 +192,7 @@ data REPLStatus   = -- | The REPL is not doing anything actively at the moment.     --   We persist the last value and its type though.+    --   INVARIANT: the Value stored here is not a VResult.     REPLDone (Maybe (Typed Value))   | -- | A command entered at the REPL is currently being run.  The     --   'Polytype' represents the type of the expression that was@@ -194,15 +201,28 @@     REPLWorking (Typed (Maybe Value))   deriving (Eq, Show, Generic, FromJSON, ToJSON) +data WinStatus+  = -- | There are one or more objectives remaining that the player+    -- has not yet accomplished.+    Ongoing+  | -- | The player has won.+    -- The boolean indicates whether they have+    -- already been congratulated.+    Won Bool+  | -- | The player has completed certain "goals" that preclude+    -- (via negative prerequisites) the completion of all of the+    -- required goals.+    -- The boolean indicates whether they have+    -- already been informed.+    Unwinnable Bool+  deriving (Show, Generic, FromJSON, ToJSON)+ data WinCondition   = -- | There is no winning condition.     NoWinCondition-  | -- | There are one or more objectives remaining that the player-    --   has not yet accomplished.-    WinConditions (NonEmpty Objective)-  | -- | The player has won. The boolean indicates whether they have-    --   already been congratulated.-    Won Bool+  | -- | NOTE: It is possible to continue to achieve "optional" objectives+    -- even after the game has been won (or deemed unwinnable).+    WinConditions WinStatus ObjectiveCompletion   deriving (Show, Generic, FromJSON, ToJSON)  makePrisms ''WinCondition@@ -256,6 +276,8 @@   { _creativeMode :: Bool   , _winCondition :: WinCondition   , _winSolution :: Maybe ProcessedTerm+  , _gameAchievements :: Map GameplayAchievement Attainment+  , _announcementQueue :: Seq Announcement   , _runStatus :: RunStatus   , _robotMap :: IntMap Robot   , -- A set of robots to consider for the next game tick. It is guaranteed to@@ -272,7 +294,7 @@     -- Waiting robots for a given time are a list because it is cheaper to     -- append to a list than to a Set.     _waitingRobots :: Map Integer [RID]-  , _robotsByLocation :: Map (V2 Int64) IntSet+  , _robotsByLocation :: Map Location IntSet   , _allDiscoveredEntities :: Inventory   , _availableRecipes :: Notifications (Recipe Entity)   , _availableCommands :: Notifications Const@@ -290,7 +312,7 @@   , _knownEntities :: [Text]   , _world :: W.World Int Entity   , _viewCenterRule :: ViewCenterRule-  , _viewCenter :: V2 Int64+  , _viewCenter :: Location   , _needsRedraw :: Bool   , _replStatus :: REPLStatus   , _replNextValueIndex :: Integer@@ -332,6 +354,16 @@ --   and to show help to cheaters (or testers). winSolution :: Lens' GameState (Maybe ProcessedTerm) +-- | Map of in-game achievements that were attained+gameAchievements :: Lens' GameState (Map GameplayAchievement Attainment)++-- | A queue of global announcments.+-- Note that this is distinct from the "messageQueue",+-- which is for messages emitted by robots.+--+-- Note that we put the newest entry to the right.+announcementQueue :: Lens' GameState (Seq Announcement)+ -- | The current 'RunStatus'. runStatus :: Lens' GameState RunStatus @@ -339,7 +371,7 @@ paused :: Getter GameState Bool paused = to (\s -> s ^. runStatus /= Running) --- | All the robots that currently exist in the game, indexed by name.+-- | All the robots that currently exist in the game, indexed by ID. robotMap :: Lens' GameState (IntMap Robot)  -- | The names of all robots that currently exist in the game, indexed by@@ -351,10 +383,10 @@ --   location of a robot changes, or a robot is created or destroyed. --   Fortunately, there are relatively few ways for these things to --   happen.-robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)+robotsByLocation :: Lens' GameState (Map Location IntSet)  -- | Get a list of all the robots at a particular location.-robotsAtLocation :: V2 Int64 -> GameState -> [Robot]+robotsAtLocation :: Location -> GameState -> [Robot] robotsAtLocation loc gs =   mapMaybe (`IM.lookup` (gs ^. robotMap))     . maybe [] IS.toList@@ -362,8 +394,9 @@     . view robotsByLocation     $ gs --- | Get robots in manhattan distastance from location.-robotsInArea :: V2 Int64 -> Int64 -> GameState -> [Robot]+-- | Get all the robots within a given Manhattan distance from a+--   location.+robotsInArea :: Location -> Int32 -> GameState -> [Robot] robotsInArea o d gs = map (rm IM.!) rids  where   rm = gs ^. robotMap@@ -446,7 +479,7 @@ --   modified directly, since it is calculated automatically from the --   'viewCenterRule'.  To modify the view center, either set the --   'viewCenterRule', or use 'modifyViewCenter'.-viewCenter :: Getter GameState (V2 Int64)+viewCenter :: Getter GameState Location viewCenter = to _viewCenter  -- | Whether the world view needs to be redrawn.@@ -549,14 +582,14 @@ messageIsRecent :: GameState -> LogEntry -> Bool messageIsRecent gs e = e ^. leTime >= gs ^. ticks - 1 -messageIsFromNearby :: V2 Int64 -> LogEntry -> Bool+messageIsFromNearby :: Location -> LogEntry -> Bool messageIsFromNearby l e = manhattan l (e ^. leLocation) <= hearingDistance  -- | Given a current mapping from robot names to robots, apply a --   'ViewCenterRule' to derive the location it refers to.  The result --   is @Maybe@ because the rule may refer to a robot which does not --   exist.-applyViewCenterRule :: ViewCenterRule -> IntMap Robot -> Maybe (V2 Int64)+applyViewCenterRule :: ViewCenterRule -> IntMap Robot -> Maybe Location applyViewCenterRule (VCLocation l) _ = Just l applyViewCenterRule (VCRobot name) m = m ^? at name . _Just . robotLocation @@ -579,7 +612,7 @@ --   current value.  Note that this also modifies the 'viewCenterRule' --   to match.  After calling this function the 'viewCenterRule' will --   specify a particular location, not a robot.-modifyViewCenter :: (V2 Int64 -> V2 Int64) -> GameState -> GameState+modifyViewCenter :: (Location -> Location) -> GameState -> GameState modifyViewCenter update g =   g     & case g ^. viewCenterRule of@@ -588,10 +621,10 @@  -- | Given a width and height, compute the region, centered on the --   'viewCenter', that should currently be in view.-viewingRegion :: GameState -> (Int64, Int64) -> (W.Coords, W.Coords)+viewingRegion :: GameState -> (Int32, Int32) -> (W.Coords, W.Coords) viewingRegion g (w, h) = (W.Coords (rmin, cmin), W.Coords (rmax, cmax))  where-  V2 cx cy = g ^. viewCenter+  Location cx cy = g ^. viewCenter   (rmin, rmax) = over both (+ (-cy - h `div` 2)) (0, h - 1)   (cmin, cmax) = over both (+ (cx - w `div` 2)) (0, w - 1) @@ -682,7 +715,7 @@ ------------------------------------------------------------  -- | Create an initial game state record, first loading entities and---   recipies from disk.+--   recipes from disk. initGameState :: ExceptT Text IO GameState initGameState = do   let guardRight what i = i `isRightOr` (\e -> "Failed to " <> what <> ": " <> e)@@ -705,6 +738,10 @@       { _creativeMode = False       , _winCondition = NoWinCondition       , _winSolution = Nothing+      , -- This does not need to be initialized with anything,+        -- since the master list of achievements is stored in UIState+        _gameAchievements = mempty+      , _announcementQueue = mempty       , _runStatus = Running       , _robotMap = IM.empty       , _robotsByLocation = M.empty@@ -727,7 +764,7 @@       , _knownEntities = []       , _world = W.emptyWorld (fromEnum StoneT)       , _viewCenterRule = VCRobot 0-      , _viewCenter = V2 0 0+      , _viewCenter = origin       , _needsRedraw = False       , _replStatus = REPLDone Nothing       , _replNextValueIndex = 0@@ -775,13 +812,13 @@       , _knownEntities = scenario ^. scenarioKnown       , _world = theWorld theSeed       , _viewCenterRule = VCRobot baseID-      , _viewCenter = V2 0 0+      , _viewCenter = origin       , _needsRedraw = False-      , -- When starting base with the run flag, REPL status must be set to working,-        -- otherwise the store of definition cells is not saved (see #333)-        _replStatus = case toRun of-          Nothing -> REPLDone Nothing-          Just _ -> REPLWorking (Typed Nothing PolyUnit mempty)+      , -- When the base starts out running a program, the REPL status must be set to working,+        -- otherwise the store of definition cells is not saved (see #333, #838)+        _replStatus = case running of+          False -> REPLDone Nothing+          True -> REPLWorking (Typed Nothing PolyUnit mempty)       , _replNextValueIndex = 0       , _messageQueue = Empty       , _focusedRobotID = baseID@@ -829,6 +866,9 @@     zipWith instantiateRobot [baseID ..] robotsByBasePrecedence       -- If the  --run flag was used, use it to replace the CESK machine of the       -- robot whose id is 0, i.e. the first robot listed in the scenario.+      -- Note that this *replaces* any program the base robot otherwise+      -- would have run (i.e. any program specified in the program: field+      -- of the scenario description).       & ix baseID . machine         %~ case getCodeToRun <$> toRun of           Nothing -> id@@ -838,16 +878,20 @@         %~ case scenario ^. scenarioCreative of           False -> id           True -> union (fromElems (map (0,) things))-      & ix baseID . installedDevices+      & ix baseID . equippedDevices         %~ case scenario ^. scenarioCreative of           False -> id           True -> const (fromList devices) +  running = case robotList of+    [] -> False+    (base : _) -> isNothing (finalValue (base ^. machine))+   -- Initial list of available commands = all commands enabled by-  -- devices in inventory or installed; and commands that require no+  -- devices in inventory or equipped; and commands that require no   -- capability.   allCapabilities r =-    inventoryCapabilities (r ^. installedDevices)+    inventoryCapabilities (r ^. equippedDevices)       <> inventoryCapabilities (r ^. robotInventory)   initialCaps = mconcat $ map allCapabilities robotList   initialCommands =@@ -857,7 +901,12 @@    (genRobots, wf) = buildWorld em (scenario ^. scenarioWorld)   theWorld = W.newWorld . wf-  theWinCondition = maybe NoWinCondition WinConditions (NE.nonEmpty (scenario ^. scenarioObjectives))+  theWinCondition =+    maybe+      NoWinCondition+      (\x -> WinConditions Ongoing (ObjectiveCompletion (CompletionBuckets (NE.toList x) mempty mempty) mempty))+      (NE.nonEmpty (scenario ^. scenarioObjectives))+   initGensym = length robotList - 1   addRecipesWith f gRs = IM.unionWith (<>) (f $ scenario ^. scenarioRecipes) (g ^. gRs) @@ -873,7 +922,7 @@   worldGrid :: [[(TerrainType, Maybe Entity)]]   worldGrid = (map . map) (cellTerrain &&& cellEntity) area -  worldArray :: Array (Int64, Int64) (TerrainType, Maybe Entity)+  worldArray :: Array (Int32, Int32) (TerrainType, Maybe Entity)   worldArray = listArray ((ulr, ulc), (ulr + rs - 1, ulc + cs - 1)) (concat worldGrid)    wf = case defaultTerrain of
src/Swarm/Game/Step.hs view
@@ -18,37 +18,38 @@ module Swarm.Game.Step where  import Control.Applicative (liftA2)+import Control.Arrow ((&&&)) import Control.Carrier.Error.Either (runError) import Control.Carrier.State.Lazy import Control.Carrier.Throw.Either (ThrowC, runThrow) import Control.Effect.Error import Control.Effect.Lens import Control.Effect.Lift-import Control.Lens as Lens hiding (Const, from, parts, use, uses, view, (%=), (+=), (.=), (<+=), (<>=))-import Control.Monad (forM, forM_, guard, msum, unless, when)+import Control.Lens as Lens hiding (Const, distrib, from, parts, use, uses, view, (%=), (+=), (.=), (<+=), (<>=))+import Control.Monad (foldM, forM, forM_, guard, msum, unless, when) import Data.Array (bounds, (!)) import Data.Bifunctor (second) import Data.Bool (bool)-import Data.Containers.ListUtils (nubOrd)+import Data.Char (chr, ord) import Data.Either (partitionEithers, rights) import Data.Foldable (asum, traverse_) import Data.Functor (void)-import Data.Int (Int64) import Data.IntMap qualified as IM import Data.IntSet qualified as IS-import Data.List (find)+import Data.List (find, sortOn) import Data.List qualified as L-import Data.List.NonEmpty (NonEmpty ((:|)))-import Data.List.NonEmpty qualified as NE import Data.Map qualified as M-import Data.Maybe (catMaybes, fromMaybe, isNothing, listToMaybe)+import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe)+import Data.Ord (Down (Down))+import Data.Sequence ((><)) import Data.Sequence qualified as Seq import Data.Set (Set) import Data.Set qualified as S import Data.Text (Text) import Data.Text qualified as T+import Data.Time (getZonedTime) import Data.Tuple (swap)-import Linear (V2 (..), zero, (^+^))+import Linear (zero) import Swarm.Game.CESK import Swarm.Game.Display import Swarm.Game.Entity hiding (empty, lookup, singleton, union)@@ -56,18 +57,23 @@ import Swarm.Game.Exception import Swarm.Game.Recipe import Swarm.Game.Robot-import Swarm.Game.Scenario (objectiveCondition)+import Swarm.Game.Scenario.Objective qualified as OB+import Swarm.Game.Scenario.Objective.WinCheck qualified as WC import Swarm.Game.State-import Swarm.Game.Value import Swarm.Game.World qualified as W import Swarm.Language.Capability import Swarm.Language.Context hiding (delete) import Swarm.Language.Pipeline import Swarm.Language.Pipeline.QQ (tmQ)+import Swarm.Language.Pretty (prettyText) import Swarm.Language.Requirement qualified as R import Swarm.Language.Syntax import Swarm.Language.Typed (Typed (..))+import Swarm.Language.Value+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions import Swarm.Util+import Swarm.Util.Location import System.Clock (TimeSpec) import System.Clock qualified import System.Random (UniformRange, uniformR)@@ -126,30 +132,134 @@   -- Possibly see if the winning condition for the current objective is met.   wc <- use winCondition   case wc of-    WinConditions (obj :| objs) -> do+    WinConditions winState oc -> do       g <- get @GameState--      -- Execute the win condition check *hypothetically*: i.e. in a-      -- fresh CESK machine, using a copy of the current game state.-      v <- runThrow @Exn . evalState @GameState g $ evalPT (obj ^. objectiveCondition)-      case v of-        -- Log exceptions in the message queue so we can check for them in tests-        Left exn -> do-          em <- use entityMap-          time <- use ticks-          let h = hypotheticalRobot (Out VUnit emptyStore []) 0-              hid = view robotID h-              hn = view robotName h-              farAway = V2 maxBound maxBound-          let m = LogEntry time ErrorTrace hn hid farAway $ formatExn em exn-          emitMessage m-        Right (VBool True) -> winCondition .= maybe (Won False) WinConditions (NE.nonEmpty objs)-        _ -> return ()+      em <- use entityMap+      hypotheticalWinCheck em g winState oc     _ -> return ()    -- Advance the game time by one.   ticks += 1 +-- | An accumulator for folding over the incomplete+-- objectives to evaluate for their completion+data CompletionsWithExceptions = CompletionsWithExceptions+  { exceptions :: [Text]+  , completions :: ObjectiveCompletion+  , completionAnnouncementQueue :: [OB.Objective]+  -- ^ Upon completion, an objective is enqueued.+  -- It is dequeued when displayed on the UI.+  }++-- | Execute the win condition check *hypothetically*: i.e. in a+-- fresh CESK machine, using a copy of the current game state.+--+-- The win check is performed only on "active" goals; that is,+-- the goals that are currently unmet and have had all of their+-- prerequisites satisfied.+-- Note that it may be possible, while traversing through the+-- goal list, for one goal to be met earlier in the list that+-- happens to be a prerequisite later in the traversal. This+-- is why:+-- 1) We must not pre-filter the goals to be traversed based+--    on satisfied prerequisites (i.e. we cannot use the+--    "getActiveObjectives" function).+-- 2) The traversal order must be "reverse topological" order, so+--    that prerequisites are evaluated before dependent goals.+-- 3) The iteration needs to be a "fold", so that state is updated+--    after each element.+hypotheticalWinCheck ::+  (Has (State GameState) sig m, Has (Lift IO) sig m) =>+  EntityMap ->+  GameState ->+  WinStatus ->+  ObjectiveCompletion ->+  m ()+hypotheticalWinCheck em g ws oc = do+  -- We can fully and accurately evaluate the new state of the objectives DAG+  -- in a single pass, so long as we visit it in reverse topological order.+  --+  -- N.B. The "reverse" is essential due to the re-population of the+  -- "incomplete" goal list by cons-ing.+  finalAccumulator <-+    foldM foldFunc initialAccumulator $+      reverse incompleteGoals++  let newWinState = case ws of+        Ongoing -> getNextWinState $ completions finalAccumulator+        _ -> ws++  winCondition .= WinConditions newWinState (completions finalAccumulator)++  case newWinState of+    Unwinnable _ -> do+      currentTime <- sendIO getZonedTime+      gameAchievements+        %= M.insertWith+          (<>)+          LoseScenario+          (Attainment (GameplayAchievement LoseScenario) Nothing currentTime)+    _ -> return ()++  announcementQueue %= (>< Seq.fromList (map ObjectiveCompleted $ completionAnnouncementQueue finalAccumulator))++  mapM_ handleException $ exceptions finalAccumulator+ where+  getNextWinState completedObjs+    | WC.didWin completedObjs = Won False+    | WC.didLose completedObjs = Unwinnable False+    | otherwise = Ongoing++  (withoutIncomplete, incompleteGoals) = OB.extractIncomplete oc+  initialAccumulator = CompletionsWithExceptions [] withoutIncomplete []++  -- All of the "incomplete" goals have been emptied from the initial accumulator, and+  -- these are what we iterate over with the fold.+  -- Each iteration, we either place the goal back into the "incomplete" bucket, or+  -- we determine that it has been met or impossible and place it into the "completed"+  -- or "unwinnable" bucket, respectively.+  foldFunc (CompletionsWithExceptions exnTexts currentCompletions announcements) obj = do+    v <-+      if WC.isPrereqsSatisfied currentCompletions obj+        then runThrow @Exn . evalState @GameState g $ evalPT $ obj ^. OB.objectiveCondition+        else return $ Right $ VBool False+    let simplified = simplifyResult $ stripVResult <$> v+    return $ case simplified of+      Left exnText ->+        CompletionsWithExceptions+          (exnText : exnTexts)+          currentCompletions+          announcements+      Right boolResult ->+        CompletionsWithExceptions+          exnTexts+          (modifyCompletions obj currentCompletions)+          (modifyAnnouncements announcements)+       where+        (modifyCompletions, modifyAnnouncements)+          | boolResult = (OB.addCompleted, (obj :))+          | WC.isUnwinnable currentCompletions obj = (OB.addUnwinnable, id)+          | otherwise = (OB.addIncomplete, id)++  simplifyResult = \case+    Left exn -> Left $ formatExn em exn+    Right (VBool x) -> Right x+    Right val ->+      Left $+        T.unwords+          [ "Non boolean value:"+          , prettyValue val+          , "real:"+          , T.pack (show val)+          ]++  -- Log exceptions in the message queue so we can check for them in tests+  handleException exnText = do+    m <- evalState @Robot h $ createLogEntry (ErrorTrace Critical) exnText+    emitMessage m+   where+    h = hypotheticalRobot (Out VUnit emptyStore []) 0+ evalPT ::   (Has (Lift IO) sig m, Has (Throw Exn) sig m, Has (State GameState) sig m) =>   ProcessedTerm ->@@ -206,12 +316,12 @@   return a  -- | Get the entity (if any) at a given location.-entityAt :: (Has (State GameState) sig m) => V2 Int64 -> m (Maybe Entity)+entityAt :: (Has (State GameState) sig m) => Location -> m (Maybe Entity) entityAt loc = zoomWorld (W.lookupEntityM @Int (W.locToCoords loc))  -- | Modify the entity (if any) at a given location. updateEntityAt ::-  (Has (State GameState) sig m) => V2 Int64 -> (Maybe Entity -> Maybe Entity) -> m ()+  (Has (State GameState) sig m) => Location -> (Maybe Entity -> Maybe Entity) -> m () updateEntityAt loc upd = zoomWorld (W.updateM @Int (W.locToCoords loc) upd)  -- | Get the robot with a given ID.@@ -312,7 +422,7 @@       robotCaps <- use robotCapabilities       let hasCaps = cap `S.member` robotCaps       (sys || creative || hasCaps)-        `holdsOr` Incapable FixByInstall (R.singletonCap cap) (TConst c)+        `holdsOr` Incapable FixByEquip (R.singletonCap cap) (TConst c)  -- | Test whether the current robot has a given capability (either --   because it has a device which gives it that capability, or it is a@@ -330,12 +440,16 @@   (Has (State Robot) sig m, Has (State GameState) sig m, Has (Throw Exn) sig m) => Capability -> Term -> m () hasCapabilityFor cap term = do   h <- hasCapability cap-  h `holdsOr` Incapable FixByInstall (R.singletonCap cap) term+  h `holdsOr` Incapable FixByEquip (R.singletonCap cap) term  -- | Create an exception about a command failing. cmdExn :: Const -> [Text] -> Exn-cmdExn c parts = CmdFailed c (T.unwords parts)+cmdExn c parts = CmdFailed c (T.unwords parts) Nothing +-- | Create an exception about a command failing, with an achievement+cmdExnWithAchievement :: Const -> [Text] -> GameplayAchievement -> Exn+cmdExnWithAchievement c parts a = CmdFailed c (T.unwords parts) $ Just a+ -- | Raise an exception about a command failing with a formatted error message. raise :: (Has (Throw Exn) sig m) => Const -> [Text] -> m a raise c parts = throwError (cmdExn c parts)@@ -369,7 +483,7 @@ tickRobotRec :: (Has (State GameState) sig m, Has (Lift IO) sig m) => Robot -> m Robot tickRobotRec r   | isActive r && (r ^. runningAtomic || r ^. tickSteps > 0) =-    stepRobot r >>= tickRobotRec+      stepRobot r >>= tickRobotRec   | otherwise = return r  -- | Single-step a robot by decrementing its 'tickSteps' counter and@@ -377,14 +491,28 @@ stepRobot :: (Has (State GameState) sig m, Has (Lift IO) sig m) => Robot -> m Robot stepRobot r = do   (r', cesk') <- runState (r & tickSteps -~ 1) (stepCESK (r ^. machine))+  -- sendIO $ appendFile "out.txt" (prettyString cesk' ++ "\n")   return $ r' & machine .~ cesk' +-- replace some entity in the world with another entity+updateWorld ::+  (Has (State GameState) sig m, Has (Throw Exn) sig m) =>+  Const ->+  WorldUpdate Entity ->+  m ()+updateWorld c (ReplaceEntity loc eThen down) = do+  w <- use world+  let eNow = W.lookupEntity (W.locToCoords loc) w+  if Just eThen /= eNow+    then throwError $ cmdExn c ["The", eThen ^. entityName, "is not there."]+    else do+      world %= W.update (W.locToCoords loc) (const down)+      pure ()+ -- | The main CESK machine workhorse.  Given a robot, look at its CESK --   machine state and figure out a single next step. stepCESK :: (Has (State GameState) sig m, Has (State Robot) sig m, Has (Lift IO) sig m) => CESK -> m CESK stepCESK cesk = case cesk of-  -- (sendIO $ appendFile "out.txt" (prettyCESK cesk)) >>-   ------------------------------------------------------------   -- Evaluation @@ -395,13 +523,14 @@     if wakeupTime <= time       then stepCESK cesk'       else return cesk-  Out v s (FImmediate wf rf : k) -> do-    wc <- worldUpdate wf <$> use world+  Out v s (FImmediate cmd wf rf : k) -> do+    wc <- runError $ mapM_ (updateWorld cmd) wf     case wc of       Left exn -> return $ Up exn s k-      Right wo -> do-        robotInventory %= robotUpdateInventory rf-        world .= wo+      Right () -> do+        forM_ rf $ \case+          AddEntity c e -> robotInventory %= E.insertCount c e+          LearnEntity e -> robotInventory %= E.insertCount 0 e         needsRedraw .= True         stepCESK (Out v s k) @@ -458,7 +587,7 @@   Out v2 s (FApp (VCApp c args) : k)     | not (isCmd c)         && arity c == length args + 1 ->-      evalConst c (reverse (v2 : args)) s k+        evalConst c (reverse (v2 : args)) s k     | otherwise -> return $ Out (VCApp c (v2 : args)) s k   Out _ s (FApp _ : _) -> badMachineState s "FApp of non-function"   -- To evaluate non-recursive let expressions, we start by focusing on the@@ -527,6 +656,15 @@     runningAtomic .= False     return $ Out v s k +  -- Machinery for implementing the 'meetAll' command.+  -- First case: done meeting everyone.+  Out b s (FMeetAll _ [] : k) -> return $ Out b s k+  -- More still to meet: apply the function to the current value b and+  -- then the next robot id.  This will result in a command which we+  -- execute, discard any generated environment, and then pass the+  -- result to continue meeting the rest of the robots.+  Out b s (FMeetAll f (rid : rids) : k) ->+    return $ Out b s (FApp f : FArg (TRobot rid) empty : FExec : FDiscardEnv : FMeetAll f rids : k)   -- To execute a bind expression, evaluate and execute the first   -- command, and remember the second for execution later.   Out (VBind mx c1 c2 e) s (FExec : k) -> return $ In c1 e s (FExec : FBind mx c2 e : k)@@ -558,6 +696,9 @@   -- Or, if a command completes with no environment, but there is a   -- previous environment to union with, just use that environment.   Out v s (FUnionEnv e : k) -> return $ Out (VResult v e) s k+  -- If there's an explicit DiscardEnv frame, throw away any returned environment.+  Out (VResult v _) s (FDiscardEnv : k) -> return $ Out v s k+  Out v s (FDiscardEnv : k) -> return $ Out v s k   -- If the top of the continuation stack contains a 'FLoadEnv' frame,   -- it means we are supposed to load up the resulting definition   -- environment, store, and type and capability contexts into the robot's@@ -594,21 +735,34 @@   -- First, if we were running a try block but evaluation completed normally,   -- just ignore the try block and continue.   Out v s (FTry {} : k) -> return $ Out v s k-  -- If an exception rises all the way to the top level without being-  -- handled, turn it into an error message.--  -- HOWEVER, we have to make sure to check that the robot has the-  -- 'log' capability which is required to collect and view logs.-  ---  -- Notice how we call resetBlackholes on the store, so that any-  -- cells which were in the middle of being evaluated will be reset.   Up exn s [] -> do+    -- Here, an exception has risen all the way to the top level without being+    -- handled.+    case exn of+      CmdFailed _ _ (Just a) -> do+        currentTime <- sendIO getZonedTime+        scenarioPath <- use currentScenarioPath+        gameAchievements+          %= M.insertWith+            (<>)+            a+            (Attainment (GameplayAchievement a) scenarioPath currentTime)+      _ -> return ()++    -- If an exception rises all the way to the top level without being+    -- handled, turn it into an error message.++    -- HOWEVER, we have to make sure to check that the robot has the+    -- 'log' capability which is required to collect and view logs.+    --+    -- Notice how we call resetBlackholes on the store, so that any+    -- cells which were in the middle of being evaluated will be reset.     let s' = resetBlackholes s     h <- hasCapability CLog     em <- use entityMap     if h       then do-        void $ traceLog ErrorTrace (formatExn em exn)+        void $ traceLog (ErrorTrace Error) (formatExn em exn)         return $ Out VUnit s []       else return $ Out VUnit s' []   -- Fatal errors, capability errors, and infinite loop errors can't@@ -630,7 +784,7 @@     let msg' =           T.unlines             [ T.append "Bad machine state in stepRobot: " msg-            , from (prettyCESK cesk)+            , prettyText cesk             ]      in return $ Up (Fatal msg') s [] @@ -681,7 +835,7 @@ -- | Construct a "seed robot" from entity, time range and position, --   and add it to the world.  It has low priority and will be covered --   by placed entities.-addSeedBot :: Has (State GameState) sig m => Entity -> (Integer, Integer) -> V2 Int64 -> TimeSpec -> m ()+addSeedBot :: Has (State GameState) sig m => Entity -> (Integer, Integer) -> Location -> TimeSpec -> m () addSeedBot e (minT, maxT) loc ts =   void $     addTRobot $@@ -691,7 +845,7 @@         "seed"         ["A growing seed."]         (Just loc)-        (V2 0 0)+        zero         ( defaultEntityDisplay '.'             & displayAttr .~ (e ^. entityDisplay . displayAttr)             & displayPriority .~ 0@@ -734,14 +888,14 @@         return $ Waiting (time + d) (Out VUnit s k)       _ -> badConst     Selfdestruct -> do-      destroyIfNotBase+      destroyIfNotBase $ Just AttemptSelfDestructBase       flagRedraw       return $ Out VUnit s k     Move -> do       -- Figure out where we're going       loc <- use robotLocation       orient <- use robotOrientation-      let nextLoc = loc ^+^ (orient ? zero)+      let nextLoc = loc .+^ (orient ? zero)       checkMoveAhead nextLoc $         MoveFailure           { failIfBlocked = ThrowExn@@ -755,7 +909,7 @@         target <- getRobotWithinTouch rid         -- either change current robot or one in robot map         let oldLoc = target ^. robotLocation-            nextLoc = V2 (fromIntegral x) (fromIntegral y)+            nextLoc = Location (fromIntegral x) (fromIntegral y)          onTarget rid $ do           checkMoveAhead nextLoc $@@ -834,44 +988,37 @@          return $ Out VUnit s k       _ -> badConst-    Install -> case vs of-      [VRobot otherID, VText itemName] -> do-        -- Make sure the other robot exists and is close-        _other <- getRobotWithinTouch otherID--        item <- ensureItem itemName "install"-+    Equip -> case vs of+      [VText itemName] -> do+        item <- ensureItem itemName "equip"         myID <- use robotID         focusedID <- use focusedRobotID-        case otherID == myID of-          -- We have to special case installing something on ourselves-          -- for the same reason as Give.-          True -> do-            -- Don't do anything if the robot already has the device.-            already <- use (installedDevices . to (`E.contains` item))-            unless already $ do-              installedDevices %= insert item-              robotInventory %= delete item--              -- Flag the UI for a redraw if we are currently showing our inventory-              when (focusedID == myID) flagRedraw-          False -> do-            let otherDevices = robotMap . at otherID . _Just . installedDevices-            already <- use $ pre (otherDevices . to (`E.contains` item))-            unless (already == Just True) $ do-              robotMap . at otherID . _Just . installedDevices %= insert item-              robotInventory %= delete item+        -- Don't do anything if the robot already has the device.+        already <- use (equippedDevices . to (`E.contains` item))+        unless already $ do+          equippedDevices %= insert item+          robotInventory %= delete item -              -- Flag the UI for a redraw if we are currently showing-              -- either robot's inventory-              when (focusedID == myID || focusedID == otherID) flagRedraw+          -- Flag the UI for a redraw if we are currently showing our inventory+          when (focusedID == myID) flagRedraw          return $ Out VUnit s k       _ -> badConst+    Unequip -> case vs of+      [VText itemName] -> do+        item <- ensureEquipped itemName+        myID <- use robotID+        focusedID <- use focusedRobotID+        equippedDevices %= delete item+        robotInventory %= insert item+        -- Flag the UI for a redraw if we are currently showing our inventory+        when (focusedID == myID) flagRedraw+        return $ Out VUnit s k+      _ -> badConst     Make -> case vs of       [VText name] -> do         inv <- use robotInventory-        ins <- use installedDevices+        ins <- use equippedDevices         em <- use entityMap         e <-           lookupEntityName name em@@ -893,7 +1040,7 @@          let displayMissingCount mc = \case               MissingInput -> from (show mc)-              MissingCatalyst -> "not installed"+              MissingCatalyst -> "not equipped"             displayMissingIngredient (MissingIngredient mk mc me) =               "  - " <> me ^. entityName <> " (" <> displayMissingCount mc mk <> ")"             displayMissingIngredients xs = L.intercalate ["OR"] (map displayMissingIngredient <$> xs)@@ -913,16 +1060,16 @@         -- take recipe inputs from inventory and add outputs after recipeTime         robotInventory .= invTaken         traverse_ (updateDiscoveredEntities . snd) (recipe ^. recipeOutputs)-        finishCookingRecipe recipe (WorldUpdate Right) (RobotUpdate changeInv)+        finishCookingRecipe recipe [] (map (uncurry AddEntity) changeInv)       _ -> badConst     Has -> case vs of       [VText name] -> do         inv <- use robotInventory         return $ Out (VBool ((> 0) $ countByName name inv)) s k       _ -> badConst-    Installed -> case vs of+    Equipped -> case vs of       [VText name] -> do-        inv <- use installedDevices+        inv <- use equippedDevices         return $ Out (VBool ((> 0) $ countByName name inv)) s k       _ -> badConst     Count -> case vs of@@ -931,8 +1078,20 @@         return $ Out (VInt (fromIntegral $ countByName name inv)) s k       _ -> badConst     Whereami -> do-      V2 x y <- use robotLocation+      loc <- use robotLocation+      let Location x y = loc       return $ Out (VPair (VInt (fromIntegral x)) (VInt (fromIntegral y))) s k+    Heading -> do+      mh <- use robotOrientation+      -- In general, (1) entities might not have an orientation, and+      -- (2) even if they do, orientation is a general vector, which+      -- might not correspond to a cardinal direction.  We could make+      -- 'heading' return a 'maybe dir' i.e. 'unit + dir', or return a+      -- vector of type 'int * int', but those would both be annoying+      -- for players in the vast majority of cases.  We rather choose+      -- to just return the direction 'down' in any case where we don't+      -- otherwise have anything reasonable to return.+      return $ Out (VDir (fromMaybe (DRelative DDown) $ mh >>= toDirection)) s k     Time -> do       t <- use ticks       return $ Out (VInt t) s k@@ -940,18 +1099,19 @@       [VDir d] -> do         rname <- use robotName         inv <- use robotInventory-        ins <- use installedDevices+        ins <- use equippedDevices -        let toyDrill = lookupByName "drill" ins-            metalDrill = lookupByName "metal drill" ins-            insDrill = listToMaybe $ metalDrill <> toyDrill+        let equippedDrills = extantElemsWithCapability CDrill ins+            -- Heuristic: choose the drill with the more elaborate name.+            -- E.g. "metal drill" vs. "drill"+            preferredDrill = listToMaybe $ sortOn (Down . T.length . (^. entityName)) equippedDrills -        drill <- insDrill `isJustOr` Fatal "Drill is required but not installed?!"+        drill <- preferredDrill `isJustOr` Fatal "Drill is required but not equipped?!"          let directionText = case d of-              DDown -> "under"-              DForward -> "ahead of"-              DBack -> "behind"+              DRelative DDown -> "under"+              DRelative DForward -> "ahead of"+              DRelative DBack -> "behind"               _ -> dirSyntax (dirInfo d) <> " of"          (nextLoc, nextME) <- lookInDirection d@@ -974,19 +1134,28 @@             `isJustOrFail` ["You don't have the ingredients to drill", indefinite (nextE ^. entityName) <> "."]          let (out, down) = L.partition ((`hasProperty` Portable) . snd) outs-            changeInv =-              flip (L.foldl' (flip $ uncurry insertCount)) out-                . flip (L.foldl' (flip $ insertCount 0)) (map snd down)-            changeWorld = changeWorld' nextE nextLoc down+        let learn = map (LearnEntity . snd) down+        let gain = map (uncurry AddEntity) out +        newEntity <- case down of+          [] -> pure Nothing+          [(1, de)] -> pure $ Just de+          _ -> throwError $ Fatal "Bad recipe:\n more than one unmovable entity produced."+        let changeWorld =+              ReplaceEntity+                { updatedLoc = nextLoc+                , originalEntity = nextE+                , newEntity = newEntity+                }+         -- take recipe inputs from inventory and add outputs after recipeTime         robotInventory .= invTaken-        finishCookingRecipe recipe (WorldUpdate changeWorld) (RobotUpdate changeInv)+        finishCookingRecipe recipe [changeWorld] (learn <> gain)       _ -> badConst     Blocked -> do       loc <- use robotLocation       orient <- use robotOrientation-      let nextLoc = loc ^+^ (orient ? zero)+      let nextLoc = loc .+^ (orient ? zero)       me <- entityAt nextLoc       return $ Out (VBool (maybe False (`hasProperty` Unwalkable) me)) s k     Scan -> case vs of@@ -1004,7 +1173,7 @@     Knows -> case vs of       [VText name] -> do         inv <- use robotInventory-        ins <- use installedDevices+        ins <- use equippedDevices         let allKnown = inv `E.union` ins         let knows = case E.lookupByName name allKnown of               [] -> False@@ -1043,7 +1212,7 @@     As -> case vs of       [VRobot rid, prog] -> do         -- Get the named robot and current game state-        r <- robotWithID rid >>= (`isJustOrFail` ["There is no robot with ID", from (show rid)])+        r <- robotWithID rid >>= (`isJustOrFail` ["There is no actor with ID", from (show rid)])         g <- get @GameState          -- Execute the given program *hypothetically*: i.e. in a fresh@@ -1082,7 +1251,7 @@         m <- traceLog Said msg -- current robot will inserted to robot set, so it needs the log         emitMessage m         let addLatestClosest rl = \case-              Seq.Empty -> Seq.Empty+              Seq.Empty -> Seq.singleton m               es Seq.:|> e                 | e ^. leTime < m ^. leTime -> es |> e |> m                 | manhattan rl (e ^. leLocation) > manhattan rl (m ^. leLocation) -> es |> m@@ -1129,7 +1298,7 @@       [VRobot rid] -> do         _ <-           robotWithID rid-            >>= (`isJustOrFail` ["There is no robot with ID", from (show rid), "to view."])+            >>= (`isJustOrFail` ["There is no actor with ID", from (show rid), "to view."])          -- Only the base can actually change the view in the UI.  Other robots can         -- execute this command but it does nothing (at least for now).@@ -1176,6 +1345,10 @@           Nothing -> return $ Out (VBool False) s k           Just e -> return $ Out (VBool (T.toLower (e ^. entityName) == T.toLower name)) s k       _ -> badConst+    Isempty -> do+      loc <- use robotLocation+      me <- entityAt loc+      return $ Out (VBool (isNothing me)) s k     Self -> do       rid <- use robotID       return $ Out (VRobot rid) s k@@ -1184,6 +1357,23 @@       rid <- use robotID       return $ Out (VRobot (fromMaybe rid mp)) s k     Base -> return $ Out (VRobot 0) s k+    Meet -> do+      loc <- use robotLocation+      rid <- use robotID+      g <- get @GameState+      let neighbor =+            find ((/= rid) . (^. robotID)) -- pick one other than ourself+              . sortOn (manhattan loc . (^. robotLocation)) -- prefer closer+              $ robotsInArea loc 1 g -- all robots within Manhattan distance 1+      return $ Out (VInj (isJust neighbor) (maybe VUnit (VRobot . (^. robotID)) neighbor)) s k+    MeetAll -> case vs of+      [f, b] -> do+        loc <- use robotLocation+        rid <- use robotID+        g <- get @GameState+        let neighborIDs = filter (/= rid) . map (^. robotID) $ robotsInArea loc 1 g+        return $ Out b s (FMeetAll f neighborIDs : k)+      _ -> badConst     Whoami -> case vs of       [] -> do         name <- use robotName@@ -1258,7 +1448,7 @@         -- check if robot exists         childRobot <-           robotWithID childRobotID-            >>= (`isJustOrFail` ["There is no robot with ID", from (show childRobotID) <> "."])+            >>= (`isJustOrFail` ["There is no actor with ID", from (show childRobotID) <> "."])          -- check that current robot is not trying to reprogram self         myID <- use robotID@@ -1279,11 +1469,11 @@          -- Figure out if we can supply what the target robot requires,         -- and if so, what is needed.-        (toInstall, toGive) <-+        (toEquip, toGive) <-           checkRequirements             (r ^. robotInventory)             (childRobot ^. robotInventory)-            (childRobot ^. installedDevices)+            (childRobot ^. equippedDevices)             cmd             "The target robot"             FixByObtain@@ -1297,7 +1487,7 @@          -- Provision the target robot with any required devices and         -- inventory that are lacking.-        provisionChild childRobotID (fromList . S.toList $ toInstall) toGive+        provisionChild childRobotID (fromList . S.toList $ toEquip) toGive          -- Finally, re-activate the reprogrammed target robot.         activateRobot childRobotID@@ -1331,7 +1521,7 @@         r <- get @Robot         pid <- use robotID -        (toInstall, toGive) <-+        (toEquip, toGive) <-           checkRequirements (r ^. robotInventory) E.empty E.empty cmd "You" FixByObtain          -- Pick a random display name.@@ -1339,8 +1529,9 @@         createdAt <- getNow          -- Construct the new robot and add it to the world.+        parentCtx <- use robotContext         newRobot <--          addTRobot $+          addTRobot . (trobotContext .~ parentCtx) $             mkRobot               ()               (Just pid)@@ -1359,7 +1550,7 @@               createdAt          -- Provision the new robot with the necessary devices and inventory.-        provisionChild (newRobot ^. robotID) (fromList . S.toList $ toInstall) toGive+        provisionChild (newRobot ^. robotID) (fromList . S.toList $ toEquip) toGive          -- Flag the world for a redraw and return the name of the newly constructed robot.         flagRedraw@@ -1373,16 +1564,16 @@         case mtarget of           Nothing -> return $ Out VUnit s k -- Nothing to salvage           Just target -> do-            -- Copy the salvaged robot's installed devices into its inventory, in preparation+            -- Copy the salvaged robot's equipped devices into its inventory, in preparation             -- for transferring it.-            let salvageInventory = E.union (target ^. robotInventory) (target ^. installedDevices)+            let salvageInventory = E.union (target ^. robotInventory) (target ^. equippedDevices)             robotMap . at (target ^. robotID) . traverse . robotInventory .= salvageInventory              let salvageItems = concatMap (\(n, e) -> replicate n (e ^. entityName)) (E.elems salvageInventory)                 numItems = length salvageItems              -- Copy over the salvaged robot's log, if we have one-            inst <- use installedDevices+            inst <- use equippedDevices             em <- use entityMap             creative <- use creativeMode             logger <-@@ -1409,7 +1600,10 @@                 giveItem item = TApp (TApp (TConst Give) (TRobot ourID)) (TText item)              -- Reprogram and activate the salvaged robot-            robotMap . at (target ^. robotID) . traverse . machine+            robotMap+              . at (target ^. robotID)+              . traverse+              . machine               .= In giveInventory empty emptyStore [FExec]             activateRobot (target ^. robotID) @@ -1433,9 +1627,15 @@           processTerm (into @Text f) `isRightOr` \err ->             cmdExn Run ["Error in", fileName, "\n", err] -        return $ case mt of-          Nothing -> Out VUnit s k-          Just t -> initMachine' t empty s k+        case mt of+          Nothing -> return $ Out VUnit s k+          Just t@(ProcessedTerm _ _ reqCtx) -> do+            -- Add the reqCtx from the ProcessedTerm to the current robot's defReqs.+            -- See #827 for an explanation of (1) why this is needed, (2) why+            -- it's slightly technically incorrect, and (3) why it is still way+            -- better than what we had before.+            robotContext . defReqs <>= reqCtx+            return $ initMachine' t empty s k       _ -> badConst     Not -> case vs of       [VBool b] -> return $ Out (VBool (not b)) s k@@ -1475,6 +1675,19 @@     Concat -> case vs of       [VText v1, VText v2] -> return $ Out (VText (v1 <> v2)) s k       _ -> badConst+    CharAt -> case vs of+      [VInt i, VText t]+        | i < 0 || i >= fromIntegral (T.length t) ->+            raise CharAt ["Index", prettyValue (VInt i), "out of bounds for length", from @String $ show (T.length t)]+        | otherwise -> return $ Out (VInt . fromIntegral . ord . T.index t . fromIntegral $ i) s k+      _ -> badConst+    ToChar -> case vs of+      [VInt i]+        | i < 0 || i > fromIntegral (ord (maxBound :: Char)) ->+            raise ToChar ["Value", prettyValue (VInt i), "is an invalid character code"]+        | otherwise ->+            return $ Out (VText . T.singleton . chr . fromIntegral $ i) s k+      _ -> badConst     AppF ->       let msg = "The operator '$' should only be a syntactic sugar and removed in elaboration:\n"        in throwError . Fatal $ msg <> badConstMsg@@ -1486,28 +1699,37 @@   badConstMsg =     T.unlines       [ "Bad application of execConst:"-      , from (prettyCESK (Out (VCApp c (reverse vs)) s k))+      , T.pack (show c)+      , T.pack (show (reverse vs))+      , prettyText (Out (VCApp c (reverse vs)) s k)       ] -  finishCookingRecipe :: HasRobotStepState sig m => Recipe e -> WorldUpdate -> RobotUpdate -> m CESK+  finishCookingRecipe :: HasRobotStepState sig m => Recipe e -> [WorldUpdate Entity] -> [RobotUpdate] -> m CESK   finishCookingRecipe r wf rf = do     time <- use ticks     let remTime = r ^. recipeTime     return . (if remTime <= 1 then id else Waiting (remTime + time)) $-      Out VUnit s (FImmediate wf rf : k)+      Out VUnit s (FImmediate c wf rf : k) -  lookInDirection :: HasRobotStepState sig m => Direction -> m (V2 Int64, Maybe Entity)+  lookInDirection :: HasRobotStepState sig m => Direction -> m (Location, Maybe Entity)   lookInDirection d = do     loc <- use robotLocation     orient <- use robotOrientation     when (isCardinal d) $ hasCapabilityFor COrient (TDir d)-    let nextLoc = loc ^+^ applyTurn d (orient ? zero)+    let nextLoc = loc .+^ applyTurn d (orient ? zero)     (nextLoc,) <$> entityAt nextLoc++  ensureEquipped :: HasRobotStepState sig m => Text -> m Entity+  ensureEquipped itemName = do+    inst <- use equippedDevices+    listToMaybe (lookupByName itemName inst)+      `isJustOrFail` ["You don't have a", indefinite itemName, "equipped."]+   ensureItem :: HasRobotStepState sig m => Text -> Text -> m Entity   ensureItem itemName action = do     -- First, make sure we know about the entity.     inv <- use robotInventory-    inst <- use installedDevices+    inst <- use equippedDevices     item <-       asum (map (listToMaybe . lookupByName itemName) [inv, inst])         `isJustOrFail` ["What is", indefinite itemName <> "?"]@@ -1527,14 +1749,14 @@   -- both 'Build' and 'Reprogram'.   --   -- It is given as inputs the parent robot inventory, the inventory-  -- and installed devices of the child (these will be empty in the+  -- and equipped devices of the child (these will be empty in the   -- case of 'Build'), and the command to be run (along with a few   -- inputs to configure any error messages to be generated).   --   -- Throws an exception if it's not possible to set up the child   -- robot with the things it needs to execute the given program.   -- Otherwise, returns a pair consisting of the set of devices to be-  -- installed, and the inventory that should be transferred from+  -- equipped, and the inventory that should be transferred from   -- parent to child.   checkRequirements ::     HasRobotStepState sig m =>@@ -1557,74 +1779,94 @@         -- See #349         (R.Requirements (S.toList -> caps) (S.toList -> devNames) reqInvNames, _capCtx) = R.requirements currentContext cmd -    -- Check that all required device names exist, and fail with-    -- an exception if not-    devs <- forM devNames $ \devName ->+    -- Check that all required device names exist (fail with+    -- an exception if not) and convert them to 'Entity' values.+    (devs :: [Entity]) <- forM devNames $ \devName ->       E.lookupEntityName devName em `isJustOrFail` ["Unknown device required: " <> devName] -    -- Check that all required inventory entity names exist, and fail-    -- with an exception if not-    reqElems <- forM (M.assocs reqInvNames) $ \(eName, n) ->+    -- Check that all required inventory entity names exist (fail with+    -- an exception if not) and convert them to 'Entity' values, with+    -- an associated count for each.+    (reqInv :: Inventory) <- fmap E.fromElems . forM (M.assocs reqInvNames) $ \(eName, n) ->       (n,)         <$> ( E.lookupEntityName eName em                 `isJustOrFail` ["Unknown entity required: " <> eName]             )-    let reqInv = E.fromElems reqElems -    let -- List of possible devices per requirement.  Devices for-        -- required capabilities come first, then singleton devices-        -- that are required directly.  This order is important since-        -- later we zip required capabilities with this list to figure-        -- out which capabilities are missing.-        capDevices = map (`deviceForCap` em) caps ++ map (: []) devs+    let -- List of possible devices per requirement.  For the+        -- requirements that stem from a required capability, we+        -- remember the capability alongside the possible devices, to+        -- help with later error message generation.+        possibleDevices :: [(Maybe Capability, [Entity])]+        possibleDevices =+          map (Just &&& (`deviceForCap` em)) caps -- Possible devices for capabilities+            ++ map ((Nothing,) . (: [])) devs -- Outright required devices          -- A device is OK if it is available in the inventory of the-        -- parent robot, or already installed in the child robot.+        -- parent robot, or already equipped in the child robot.+        deviceOK :: Entity -> Bool         deviceOK d = parentInventory `E.contains` d || childDevices `E.contains` d -        -- take a pair of device sets providing capabilities that is-        -- split into (AVAIL,MISSING) and if there are some available-        -- ignore missing because we only need them for error message-        ignoreOK ([], miss) = ([], miss)-        ignoreOK (ds, _miss) = (ds, [])--        (deviceSets, missingDeviceSets) =-          Lens.over both (nubOrd . map S.fromList) . unzip $-            map (ignoreOK . L.partition deviceOK) capDevices--        formatDevices = T.intercalate " or " . map (^. entityName) . S.toList-        -- capabilities not provided by any device in inventory-        missingCaps = S.fromList . map fst . filter (null . snd) $ zip caps deviceSets+        -- Partition each list of possible devices into a set of+        -- available devices and a set of unavailable devices.+        -- There's a problem if some capability is required but no+        -- devices that provide it are available.  In that case we can+        -- print an error message, using the second set as a list of+        -- suggestions.+        partitionedDevices :: [(Set Entity, Set Entity)]+        partitionedDevices =+          map (Lens.over both S.fromList . L.partition deviceOK . snd) possibleDevices -        alreadyInstalled = S.fromList . map snd . E.elems $ childDevices+        -- Devices equipped on the child, as a Set instead of an+        -- Inventory for convenience.+        alreadyEquipped :: Set Entity+        alreadyEquipped = S.fromList . map snd . E.elems $ childDevices -        -- Figure out what is missing from the required inventory+        -- Figure out what is still missing of the required inventory:+        -- the required inventory, less any inventory the child robot+        -- already has.         missingChildInv = reqInv `E.difference` childInventory      if creative-      then -- In creative mode, just return ALL the devices-        return (S.unions (map S.fromList capDevices) `S.difference` alreadyInstalled, missingChildInv)+      then+        return+          ( -- In creative mode, just equip ALL the devices+            -- providing each required capability (because, why+            -- not?). But don't re-equip any that are already+            -- equipped.+            S.unions (map (S.fromList . snd) possibleDevices) `S.difference` alreadyEquipped+          , -- Conjure the necessary missing inventory out of thin+            -- air.+            missingChildInv+          )       else do-        -- check if robot has all devices to execute new command-        all null missingDeviceSets-          `holdsOrFail` ( singularSubjectVerb subject "do" :-                          "not have required devices, please" :-                          formatIncapableFix fixI <> ":" :-                          (("\n  - " <>) . formatDevices <$> filter (not . null) missingDeviceSets)+        -- First, check that devices actually exist AT ALL to provide every+        -- required capability.  If not, we will generate an error message saying+        -- something like "missing capability X but no device yet provides it".+        let capsWithNoDevice = mapMaybe fst . filter (null . snd) $ possibleDevices+        null capsWithNoDevice+          `holdsOr` Incapable fixI (R.Requirements (S.fromList capsWithNoDevice) S.empty M.empty) cmd++        -- Now, ensure there is at least one device available to be+        -- equipped for each requirement.+        let missingDevices = map snd . filter (null . fst) $ partitionedDevices+        null missingDevices+          `holdsOrFail` ( singularSubjectVerb subject "do"+                            : "not have required devices, please"+                            : formatIncapableFix fixI <> ":"+                            : (("\n  - " <>) . formatDevices <$> missingDevices)                         )-        -- check that there are in fact devices to provide every required capability-        not (any null deviceSets) `holdsOr` Incapable fixI (R.Requirements missingCaps S.empty M.empty) cmd -        let minimalInstallSet = smallHittingSet (filter (S.null . S.intersection alreadyInstalled) deviceSets)+        let minimalEquipSet = smallHittingSet (filter (S.null . S.intersection alreadyEquipped) (map fst partitionedDevices))              -- Check that we have enough in our inventory to cover the-            -- required installs PLUS what's missing from the child+            -- required devices PLUS what's missing from the child             -- inventory.              -- What do we need?             neededParentInv =               missingChildInv-                `E.union` (fromList . S.toList $ minimalInstallSet)+                `E.union` (fromList . S.toList $ minimalEquipSet)              -- What are we missing?             missingParentInv = neededParentInv `E.difference` parentInventory@@ -1639,33 +1881,19 @@         E.isEmpty missingParentInv           `holdsOr` Incapable fixI (R.Requirements S.empty S.empty missingMap) cmd -        return (minimalInstallSet, missingChildInv)--  -- replace some entity in the world with another entity-  changeWorld' ::-    Entity ->-    V2 Int64 ->-    IngredientList Entity ->-    W.World Int Entity ->-    Either Exn (W.World Int Entity)-  changeWorld' eThen loc down w =-    let eNow = W.lookupEntity (W.locToCoords loc) w-     in if Just eThen /= eNow-          then Left $ cmdExn c ["The", eThen ^. entityName, "is not there."]-          else-            w `updateLoc` loc <$> case down of-              [] -> Right Nothing-              [de] -> Right $ Just $ snd de-              _ -> Left $ Fatal "Bad recipe:\n more than one unmovable entity produced."+        return (minimalEquipSet, missingChildInv) -  destroyIfNotBase :: HasRobotStepState sig m => m ()-  destroyIfNotBase = do+  destroyIfNotBase :: HasRobotStepState sig m => Maybe GameplayAchievement -> m ()+  destroyIfNotBase mAch = do     rid <- use robotID-    (rid /= 0) `holdsOrFail` ["You consider destroying your base, but decide not to do it after all."]+    holdsOrFailWithAchievement+      (rid /= 0)+      ["You consider destroying your base, but decide not to do it after all."]+      mAch     selfDestruct .= True    -- Make sure nothing is in the way. Note that system robots implicitly ignore and base throws on failure.-  checkMoveAhead :: HasRobotStepState sig m => V2 Int64 -> MoveFailure -> m ()+  checkMoveAhead :: HasRobotStepState sig m => Location -> MoveFailure -> m ()   checkMoveAhead nextLoc MoveFailure {..} = do     me <- entityAt nextLoc     systemRob <- use systemRobot@@ -1674,20 +1902,20 @@       Just e         | systemRob -> return ()         | otherwise -> do-          -- robots can not walk through walls-          when (e `hasProperty` Unwalkable) $-            case failIfBlocked of-              Destroy -> destroyIfNotBase-              ThrowExn -> throwError $ cmdExn c ["There is a", e ^. entityName, "in the way!"]-              IgnoreFail -> return ()+            -- robots can not walk through walls+            when (e `hasProperty` Unwalkable) $+              case failIfBlocked of+                Destroy -> destroyIfNotBase Nothing+                ThrowExn -> throwError $ cmdExn c ["There is a", e ^. entityName, "in the way!"]+                IgnoreFail -> return () -          -- robots drown if they walk over liquid without boat-          caps <- use robotCapabilities-          when (e `hasProperty` Liquid && CFloat `S.notMember` caps) $-            case failIfDrown of-              Destroy -> destroyIfNotBase-              ThrowExn -> throwError $ cmdExn c ["There is a dangerous liquid", e ^. entityName, "in the way!"]-              IgnoreFail -> return ()+            -- robots drown if they walk over liquid without boat+            caps <- use robotCapabilities+            when (e `hasProperty` Liquid && CFloat `S.notMember` caps) $+              case failIfDrown of+                Destroy -> destroyIfNotBase Nothing+                ThrowExn -> throwError $ cmdExn c ["There is a dangerous liquid", e ^. entityName, "in the way!"]+                IgnoreFail -> return ()    getRobotWithinTouch :: HasRobotStepState sig m => RID -> m Robot   getRobotWithinTouch rid = do@@ -1704,12 +1932,14 @@           `holdsOrFail` ["The robot with ID", from (show rid), "is not close enough."]         return other -  -- update some tile in the world setting it to entity or making it empty-  updateLoc w loc res = W.update (W.locToCoords loc) (const res) w-   holdsOrFail :: (Has (Throw Exn) sig m) => Bool -> [Text] -> m ()   holdsOrFail a ts = a `holdsOr` cmdExn c ts +  holdsOrFailWithAchievement :: (Has (Throw Exn) sig m) => Bool -> [Text] -> Maybe GameplayAchievement -> m ()+  holdsOrFailWithAchievement a ts mAch = case mAch of+    Nothing -> holdsOrFail a ts+    Just ach -> a `holdsOr` cmdExnWithAchievement c ts ach+   isJustOrFail :: (Has (Throw Exn) sig m) => Maybe a -> [Text] -> m a   isJustOrFail a ts = a `isJustOr` cmdExn c ts @@ -1808,10 +2038,15 @@   Grab' -> "grabbed"   Swap' -> "swapped" +-- | Format a set of suggested devices for use in an error message,+--   in the format @device1 or device2 or ... or deviceN@.+formatDevices :: Set Entity -> Text+formatDevices = T.intercalate " or " . map (^. entityName) . S.toList+ -- | Give some entities from a parent robot (the robot represented by --   the ambient @State Robot@ effect) to a child robot (represented --   by the given 'RID') as part of a 'Build' or 'Reprogram' command.---   The first 'Inventory' is devices to be installed, and the second+--   The first 'Inventory' is devices to be equipped, and the second --   is entities to be transferred. -- --   In classic mode, the entities will be /transferred/ (that is,@@ -1824,15 +2059,15 @@   Inventory ->   Inventory ->   m ()-provisionChild childID toInstall toGive = do-  -- Install and give devices to child-  robotMap . ix childID . installedDevices %= E.union toInstall+provisionChild childID toEquip toGive = do+  -- Equip and give devices to child+  robotMap . ix childID . equippedDevices %= E.union toEquip   robotMap . ix childID . robotInventory %= E.union toGive    -- Delete all items from parent in classic mode   creative <- use creativeMode   unless creative $-    robotInventory %= (`E.difference` (toInstall `E.union` toGive))+    robotInventory %= (`E.difference` (toEquip `E.union` toGive))  -- | Update the location of a robot, and simultaneously update the --   'robotsByLocation' map, so we can always look up robots by@@ -1840,17 +2075,17 @@ --   of a robot. updateRobotLocation ::   (HasRobotStepState sig m) =>-  V2 Int64 ->-  V2 Int64 ->+  Location ->+  Location ->   m () updateRobotLocation oldLoc newLoc   | oldLoc == newLoc = return ()   | otherwise = do-    rid <- use robotID-    robotsByLocation . at oldLoc %= deleteOne rid-    robotsByLocation . at newLoc . non Empty %= IS.insert rid-    modify (unsafeSetRobotLocation newLoc)-    flagRedraw+      rid <- use robotID+      robotsByLocation . at oldLoc %= deleteOne rid+      robotsByLocation . at newLoc . non Empty %= IS.insert rid+      modify (unsafeSetRobotLocation newLoc)+      flagRedraw  where   -- Make sure empty sets don't hang around in the   -- robotsByLocation map.  We don't want a key with an@@ -1943,8 +2178,9 @@ incomparable :: Has (Throw Exn) sig m => Value -> Value -> m a incomparable v1 v2 =   throwError $-    CmdFailed Lt $-      T.unwords ["Comparison is undefined for ", prettyValue v1, "and", prettyValue v2]+    cmdExn+      Lt+      ["Comparison is undefined for ", prettyValue v1, "and", prettyValue v2]  ------------------------------------------------------------ -- Arithmetic@@ -1967,13 +2203,13 @@ -- | Perform an integer division, but return @Nothing@ for division by --   zero. safeDiv :: Has (Throw Exn) sig m => Integer -> Integer -> m Integer-safeDiv _ 0 = throwError $ CmdFailed Div "Division by zero"+safeDiv _ 0 = throwError $ cmdExn Div $ pure "Division by zero" safeDiv a b = return $ a `div` b  -- | Perform exponentiation, but return @Nothing@ if the power is negative. safeExp :: Has (Throw Exn) sig m => Integer -> Integer -> m Integer safeExp a b-  | b < 0 = throwError $ CmdFailed Exp "Negative exponent"+  | b < 0 = throwError $ cmdExn Exp $ pure "Negative exponent"   | otherwise = return $ a ^ b  ------------------------------------------------------------@@ -2013,7 +2249,7 @@  updateAvailableCommands :: Has (State GameState) sig m => Entity -> m () updateAvailableCommands e = do-  let newCaps = S.fromList (e ^. entityCapabilities)+  let newCaps = e ^. entityCapabilities       keepConsts = \case         Just cap -> cap `S.member` newCaps         Nothing -> False
− src/Swarm/Game/Value.hs
@@ -1,117 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GADTs #-}---- |--- Module      :  Swarm.Game.Value--- Copyright   :  Brent Yorgey--- Maintainer  :  byorgey@gmail.com------ SPDX-License-Identifier: BSD-3-Clause------ Values and environments used for interpreting the Swarm language.-module Swarm.Game.Value (-  -- * Values-  Value (..),-  prettyValue,-  valueToTerm,--  -- * Environments-  Env,-) where--import Data.Aeson (FromJSON, ToJSON)-import Data.Bool (bool)-import Data.List (foldl')-import Data.Map qualified as M-import Data.Set qualified as S-import Data.Set.Lens (setOf)-import Data.Text (Text)-import GHC.Generics (Generic)-import Swarm.Language.Context-import Swarm.Language.Pretty (prettyText)-import Swarm.Language.Syntax-import Prelude---- | A /value/ is a term that cannot (or does not) take any more---   evaluation steps on its own.-data Value where-  -- | The unit value.-  VUnit :: Value-  -- | An integer.-  VInt :: Integer -> Value-  -- | Literal text.-  VText :: Text -> Value-  -- | A direction.-  VDir :: Direction -> Value-  -- | A boolean.-  VBool :: Bool -> Value-  -- | A reference to a robot.-  VRobot :: Int -> Value-  -- | An injection into a sum type.  False = left, True = right.-  VInj :: Bool -> Value -> Value-  -- | A pair.-  VPair :: Value -> Value -> Value-  -- | A /closure/, representing a lambda term along with an-  --   environment containing bindings for any free variables in the-  --   body of the lambda.-  VClo :: Var -> Term -> Env -> Value-  -- | An application of a constant to some value arguments,-  --   potentially waiting for more arguments.  If a constant-  --   application is fully saturated (as defined by its 'arity'),-  --   whether it is a value or not depends on whether or not it-  --   represents a command (as defined by 'isCmd').  If a command-  --   (e.g. 'Build'), it is a value, and awaits an 'Swarm.Game.CESK.FExec' frame-  --   which will cause it to execute.  Otherwise (e.g. 'If'), it is-  --   not a value, and will immediately reduce.-  VCApp :: Const -> [Value] -> Value-  -- | A definition, which does not take effect until executed.-  --   The @Bool@ indicates whether the definition is recursive.-  VDef :: Bool -> Var -> Term -> Env -> Value-  -- | The result of a command, consisting of the result of the-  --   command as well as an environment of bindings from 'TDef'-  --   commands.-  VResult :: Value -> Env -> Value-  -- | An unevaluated bind expression, waiting to be executed, of the-  --   form /i.e./ @c1 ; c2@ or @x <- c1; c2@.  We also store an 'Env'-  --   in which to interpret the commands.-  VBind :: Maybe Var -> Term -> Term -> Env -> Value-  -- | A (non-recursive) delayed term, along with its environment. If-  --   a term would otherwise be evaluated but we don't want it to be-  --   (/e.g./ as in the case of arguments to an 'if', or a recursive-  --   binding), we can stick a 'TDelay' on it, which turns it into a-  --   value.  Delayed terms won't be evaluated until 'Force' is-  --   applied to them.-  VDelay :: Term -> Env -> Value-  -- | A reference to a memory cell in the store.-  VRef :: Int -> Value-  deriving (Eq, Show, Generic, FromJSON, ToJSON)---- | Pretty-print a value.-prettyValue :: Value -> Text-prettyValue = prettyText . valueToTerm---- | Inject a value back into a term.-valueToTerm :: Value -> Term-valueToTerm VUnit = TUnit-valueToTerm (VInt n) = TInt n-valueToTerm (VText s) = TText s-valueToTerm (VDir d) = TDir d-valueToTerm (VBool b) = TBool b-valueToTerm (VRobot r) = TRobot r-valueToTerm (VInj s v) = TApp (TConst (bool Inl Inr s)) (valueToTerm v)-valueToTerm (VPair v1 v2) = TPair (valueToTerm v1) (valueToTerm v2)-valueToTerm (VClo x t e) =-  M.foldrWithKey-    (\y v -> TLet False y Nothing (valueToTerm v))-    (TLam x Nothing t)-    (M.restrictKeys (unCtx e) (S.delete x (setOf fv t)))-valueToTerm (VCApp c vs) = foldl' TApp (TConst c) (reverse (map valueToTerm vs))-valueToTerm (VDef r x t _) = TDef r x Nothing t-valueToTerm (VResult v _) = valueToTerm v-valueToTerm (VBind mx c1 c2 _) = TBind mx c1 c2-valueToTerm (VDelay t _) = TDelay SimpleDelay t-valueToTerm (VRef n) = TRef n---- | An environment is a mapping from variable names to values.-type Env = Ctx Value
src/Swarm/Game/World.hs view
@@ -42,6 +42,9 @@   lookupTerrainM,   lookupEntityM,   updateM,++  -- ** Runtime updates+  WorldUpdate (..), ) where  import Control.Algebra (Has)@@ -53,11 +56,12 @@ import Data.Array.Unboxed qualified as U import Data.Bits import Data.Foldable (foldl')-import Data.Int (Int64)+import Data.Int (Int32) import Data.Map.Strict qualified as M+import Data.Yaml (FromJSON, ToJSON) import GHC.Generics (Generic)-import Linear import Swarm.Util+import Swarm.Util.Location import Prelude hiding (lookup)  ------------------------------------------------------------@@ -65,21 +69,25 @@ ------------------------------------------------------------  -- | World coordinates use (row,column) format, with the row---   increasing as we move down the screen.  This format plays nicely---   with drawing the screen.-newtype Coords = Coords {unCoords :: (Int64, Int64)}+--   increasing as we move down the screen.  We use this format for+--   indexing worlds internally, since it plays nicely with things+--   like drawing the screen, and reading maps from configuration+--   files. The 'locToCoords' and 'coordsToLoc' functions convert back+--   and forth between this type and 'Location', which is used when+--   presenting coordinates externally to the player.+newtype Coords = Coords {unCoords :: (Int32, Int32)}   deriving (Eq, Ord, Show, Ix, Generic)  instance Rewrapped Coords t instance Wrapped Coords --- | Convert an (x,y) location to a 'Coords' value.-locToCoords :: V2 Int64 -> Coords-locToCoords (V2 x y) = Coords (-y, x)+-- | Convert an external (x,y) location to an internal 'Coords' value.+locToCoords :: Location -> Coords+locToCoords (Location x y) = Coords (-y, x) --- | Convert 'Coords' to an (x,y) location.-coordsToLoc :: Coords -> V2 Int64-coordsToLoc (Coords (r, c)) = V2 c (-r)+-- | Convert an internal 'Coords' value to an external (x,y) location.+coordsToLoc :: Coords -> Location+coordsToLoc (Coords (r, c)) = Location c (-r)  ------------------------------------------------------------ -- World function@@ -95,7 +103,7 @@  -- | Create a world function from a finite array of specified cells --   plus a single default cell to use everywhere else.-worldFunFromArray :: Array (Int64, Int64) (t, Maybe e) -> (t, Maybe e) -> WorldFun t e+worldFunFromArray :: Array (Int32, Int32) (t, Maybe e) -> (t, Maybe e) -> WorldFun t e worldFunFromArray arr def = WF $ \(Coords (r, c)) ->   if inRange bnds (r, c)     then arr ! (r, c)@@ -120,7 +128,7 @@  -- | The number consisting of 'tileBits' many 1 bits.  We can use this --   to mask out the tile offset of a coordinate.-tileMask :: Int64+tileMask :: Int32 tileMask = (1 `shiftL` tileBits) - 1  -- | If we think of the world as a grid of /tiles/, we can assign each@@ -274,3 +282,18 @@    where     tileCorner = tileOrigin tc     (terrain, entities) = unzip $ map (runWF f . plusOffset tileCorner) (range tileBounds)++-- ------------------------------------------------------------------+-- Runtime world update+-- ------------------------------------------------------------------++-- | Update world in an inspectable way.+--+-- This type is used for changes by e.g. the drill command at later+-- tick. Using ADT allows us to serialize and inspect the updates.+data WorldUpdate e = ReplaceEntity+  { updatedLoc :: Location+  , originalEntity :: e+  , newEntity :: Maybe e+  }+  deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON)
src/Swarm/Game/WorldGen.hs view
@@ -18,7 +18,7 @@ import Data.ByteString (ByteString) import Data.Enumeration import Data.Hash.Murmur-import Data.Int (Int64)+import Data.Int (Int32) import Data.List (find) import Data.Maybe (fromMaybe, mapMaybe) import Data.Set qualified as S@@ -102,9 +102,9 @@       | sample ix cl0 > 0.5 = (StoneT, Just "mountain")       | h `mod` 30 == 0 = (StoneT, Just "boulder")       | sample ix cl0 > 0 =-        case h `mod` 30 of-          1 -> (DirtT, Just "LaTeX")-          _ -> (DirtT, Just "tree")+          case h `mod` 30 of+            1 -> (DirtT, Just "LaTeX")+            _ -> (DirtT, Just "tree")       | otherwise = (GrassT, Nothing)     genBiome Small Hard Natural       | h `mod` 100 == 0 = (StoneT, Just "lodestone")@@ -153,7 +153,7 @@  -- | Create a world function from a finite array of specified cells --   plus a seed to randomly generate the rest.-testWorld2FromArray :: EntityMap -> Array (Int64, Int64) (TerrainType, Maybe Entity) -> Seed -> WorldFun TerrainType Entity+testWorld2FromArray :: EntityMap -> Array (Int32, Int32) (TerrainType, Maybe Entity) -> Seed -> WorldFun TerrainType Entity testWorld2FromArray em arr seed = WF $ \co@(Coords (r, c)) ->   if inRange bnds (r, c)     then arr ! (r, c)@@ -167,7 +167,7 @@ findOffset :: Integer -> ((Coords -> (t, Maybe e)) -> Bool) -> WorldFun t e -> WorldFun t e findOffset skip isGood (WF f) = WF f'  where-  offset :: Enumeration Int64+  offset :: Enumeration Int32   offset = fromIntegral . (skip *) <$> int    f' =
src/Swarm/Language/Capability.hs view
@@ -8,7 +8,7 @@ -- Capabilities needed to evaluate and execute programs.  Language -- constructs or commands require certain capabilities, and in turn -- capabilities are provided by various devices.  A robot must have an--- appropriate device installed in order to make use of each language+-- appropriate device equipped in order to make use of each language -- construct or command. module Swarm.Language.Capability (   Capability (..),@@ -51,8 +51,10 @@     CPlace   | -- | Execute the 'Give' command     CGive-  | -- | Execute the 'Install' command-    CInstall+  | -- | Execute the 'Equip' command+    CEquip+  | -- | Execute the 'Unequip' command+    CUnequip   | -- | Execute the 'Make' command     CMake   | -- | Execute the 'Count' command@@ -67,7 +69,7 @@     CSenseloc   | -- | Execute the 'Blocked' command     CSensefront-  | -- | Execute the 'Ishere' command+  | -- | Execute the 'Ishere' and 'Isempty' commands     CSensehere   | -- | Execute the 'Scan' command     CScan@@ -83,6 +85,8 @@     CLog   | -- | Manipulate text values     CText+  | -- | Convert between characters/text and Unicode values+    CCode   | -- | Don't drown in liquid     CFloat   | -- | Evaluate conditional expressions@@ -103,6 +107,8 @@     CRecursion   | -- | Execute the 'Reprogram' command     CReprogram+  | -- | Execute the `meet` and `meetAll` commands.+    CMeet   | -- | Capability to introspect and see its own name     CWhoami   | -- | Capability to set its own name@@ -157,7 +163,7 @@   Undefined -> Nothing   Fail -> Nothing   Has -> Nothing-  Installed -> Nothing+  Equipped -> Nothing   -- speaking is natural to robots (unlike listening)   Say -> Nothing   -- TODO: #495@@ -175,17 +181,21 @@   Harvest -> Just CHarvest   Place -> Just CPlace   Give -> Just CGive-  Install -> Just CInstall+  Equip -> Just CEquip+  Unequip -> Just CUnequip   Make -> Just CMake   Count -> Just CCount   If -> Just CCond   Blocked -> Just CSensefront   Scan -> Just CScan   Ishere -> Just CSensehere+  Isempty -> Just CSensehere   Upload -> Just CScan   Build -> Just CBuild   Salvage -> Just CSalvage   Reprogram -> Just CReprogram+  Meet -> Just CMeet+  MeetAll -> Just CMeet   Drill -> Just CDrill   Neg -> Just CArith   Add -> Just CArith@@ -199,12 +209,16 @@   Atomic -> Just CAtomic   Time -> Just CTime   Wait -> Just CTime+  Whereami -> Just CSenseloc+  Heading -> Just COrient   -- ----------------------------------------------------------------   -- Text operations   Format -> Just CText   Concat -> Just CText   Split -> Just CText   Chars -> Just CText+  CharAt -> Just CCode+  ToChar -> Just CCode   -- ----------------------------------------------------------------   -- Some God-like abilities.   As -> Just CGod@@ -234,7 +248,7 @@   Case -> Just CSum   Fst -> Just CProd   Snd -> Just CProd-  -- XXX pair syntax should require CProd too+  -- TODO: #563 pair syntax (1,2,3...) should require CProd too    -- ----------------------------------------------------------------   -- Some additional straightforward ones, which however currently@@ -242,10 +256,9 @@   -- which conveys their capability. TODO: #26   Teleport -> Just CTeleport -- Some space-time machine like Tardis?   Appear -> Just CAppear -- paint?-  Whereami -> Just CSenseloc -- GPS?   Random -> Just CRandom -- randomness device (with bitcoins)?   -- ----------------------------------------------------------------   -- Some more constants which *ought* to have their own capability but-  -- currently don't. TODO: #26-  View -> Nothing -- XXX this should also require something.+  -- currently don't.+  View -> Nothing -- TODO: #17 should require equipping an antenna   Knows -> Nothing
src/Swarm/Language/Context.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveDataTypeable #-}  -- |
src/Swarm/Language/Elaborate.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ -- | -- Module      :  Swarm.Language.Elaborate -- Copyright   :  Brent Yorgey@@ -8,40 +10,51 @@ -- Term elaboration which happens after type checking. module Swarm.Language.Elaborate where -import Control.Lens (transform, (%~))+import Control.Lens (transform, (%~), (^.)) import Swarm.Language.Syntax+import Swarm.Language.Types  -- | Perform some elaboration / rewriting on a fully type-annotated---   term, given its top-level type.  This currently performs such---   operations as rewriting @if@ expressions and recursive let---   expressions to use laziness appropriately.  In theory it could---   also perform rewriting for overloaded constants depending on the---   actual type they are used at, but currently that sort of thing---   tends to make type inference fall over.-elaborate :: Term -> Term+--   term.  This currently performs such operations as rewriting @if@+--   expressions and recursive let expressions to use laziness+--   appropriately.  In theory it could also perform rewriting for+--   overloaded constants depending on the actual type they are used+--   at, but currently that sort of thing tends to make type inference+--   fall over.+elaborate :: Syntax' Polytype -> Syntax' Polytype elaborate =   -- Wrap all *free* variables in 'Force'.  Free variables must be   -- referring to a previous definition, which are all wrapped in   -- 'TDelay'.-  (fvT %~ TApp (TConst Force))+  (freeVarsS %~ \s -> Syntax' (s ^. sLoc) (SApp sForce s) (s ^. sType))     -- Now do additional rewriting on all subterms.     . transform rewrite  where-  -- For recursive let bindings, rewrite any occurrences of x to-  -- (force x).  When interpreting t1, we will put a binding (x |->-  -- delay t1) in the context.-  rewrite (TLet True x ty t1 t2) = TLet True x ty (wrapForce x t1) (wrapForce x t2)-  -- Rewrite any recursive occurrences of x inside t1 to (force x).-  -- When a TDef is encountered at runtime its body will immediately-  -- be wrapped in a VDelay. However, to make this work we also need-  -- to wrap all free variables in any term with 'force' --- since-  -- any such variables must in fact refer to things previously-  -- bound by 'def'.-  rewrite (TDef True x ty t1) = TDef True x ty (mapFree1 x (TApp (TConst Force)) t1)-  -- Rewrite @f $ x@ to @f x@.-  rewrite (TApp (TApp (TConst AppF) r) l) = TApp r l-  -- Leave any other subterms alone.-  rewrite t = t+  rewrite :: Syntax' Polytype -> Syntax' Polytype+  rewrite (Syntax' l t ty) = Syntax' l (rewriteTerm t) ty -wrapForce :: Var -> Term -> Term-wrapForce x = mapFree1 x (TApp (TConst Force))+  rewriteTerm :: Term' Polytype -> Term' Polytype+  rewriteTerm = \case+    -- For recursive let bindings, rewrite any occurrences of x to+    -- (force x).  When interpreting t1, we will put a binding (x |->+    -- delay t1) in the context.+    SLet True x ty t1 t2 -> SLet True x ty (wrapForce (lvVar x) t1) (wrapForce (lvVar x) t2)+    -- Rewrite any recursive occurrences of x inside t1 to (force x).+    -- When a TDef is encountered at runtime its body will immediately+    -- be wrapped in a VDelay. However, to make this work we also need+    -- to wrap all free variables in any term with 'force' --- since+    -- any such variables must in fact refer to things previously+    -- bound by 'def'.+    SDef True x ty t1 -> SDef True x ty (wrapForce (lvVar x) t1)+    -- Rewrite @f $ x@ to @f x@.+    SApp (Syntax' _ (SApp (Syntax' _ (TConst AppF) _) l) _) r -> SApp l r+    -- Leave any other subterms alone.+    t -> t++wrapForce :: Var -> Syntax' Polytype -> Syntax' Polytype+wrapForce x = mapFreeS x (\s@(Syntax' l _ ty) -> Syntax' l (SApp sForce s) ty)++-- Note, TyUnit is not the right type, but I don't want to bother++sForce :: Syntax' Polytype+sForce = Syntax' NoLoc (TConst Force) (Forall ["a"] (TyDelay (TyVar "a") :->: TyVar "a"))
src/Swarm/Language/LSP.hs view
@@ -14,15 +14,17 @@ import Control.Lens (to, (^.)) import Control.Monad (void) import Control.Monad.IO.Class-import Data.Foldable (forM_)-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, mapMaybe) import Data.Text (Text) import Data.Text.IO qualified as Text import Language.LSP.Diagnostics import Language.LSP.Server+import Language.LSP.Types (Hover (Hover)) import Language.LSP.Types qualified as J import Language.LSP.Types.Lens qualified as J import Language.LSP.VFS+import Swarm.Language.LSP.Hover qualified as H+import Swarm.Language.LSP.VarUsage qualified as VU import Swarm.Language.Parse import Swarm.Language.Pipeline import System.IO (stderr)@@ -40,7 +42,7 @@         , interpretHandler = \env -> Iso (runLspT env) liftIO         , options =             defaultOptions-              { -- set sync options to get DidSave event+              { -- set sync options to get DidSave event, as well as Open and Close events.                 textDocumentSync =                   Just                     ( J.TextDocumentSyncOptions@@ -58,39 +60,75 @@   -- handler is called for each key-stroke.   syncKind = J.TdSyncFull +diagnosticSourcePrefix :: Text+diagnosticSourcePrefix = "swarm-lsp"+ debug :: MonadIO m => Text -> m () debug msg = liftIO $ Text.hPutStrLn stderr $ "[swarm-lsp] " <> msg  validateSwarmCode :: J.NormalizedUri -> J.TextDocumentVersion -> Text -> LspM () () validateSwarmCode doc version content = do   -- debug $ "Validating: " <> from (show doc) <> " ( " <> content <> ")"-  flushDiagnosticsBySource 0 (Just "swarm-lsp")-  let err = case readTerm' content of-        Right Nothing -> Nothing-        Right (Just term) -> case processParsedTerm' mempty mempty term of-          Right _ -> Nothing-          Left e -> Just $ showTypeErrorPos content e-        Left e -> Just $ showErrorPos e++  -- FIXME: #1040 With this call to flushDiagnosticsBySource in place, the warnings+  -- in other buffers (editor tabs) end up getting cleared when switching between+  -- (focusing on) other buffers in VS Code.+  -- However, getting rid of this seems to break error highlighting.+  flushDiagnosticsBySource 0 (Just diagnosticSourcePrefix)++  let (parsingErrs, unusedVarWarnings) = case readTerm' content of+        Right Nothing -> ([], [])+        Right (Just term) -> (parsingErrors, unusedWarnings)+         where+          VU.Usage _ problems = VU.getUsage mempty term+          unusedWarnings = mapMaybe (VU.toErrPos content) problems++          parsingErrors = case processParsedTerm' mempty mempty term of+            Right _ -> []+            Left e -> pure $ showTypeErrorPos content e+        Left e -> (pure $ showErrorPos e, [])   -- debug $ "-> " <> from (show err)-  forM_ err sendDiagnostic++  publishDiags $+    map makeUnusedVarDiagnostic unusedVarWarnings++  -- NOTE: "publishDiags" keeps only one diagnostic at a+  -- time (the most recent) so we make sure the errors are+  -- issued last (after any warnings).+  -- Note that it does not achieve the desired effect to simply+  -- concatenate the two diagnostic lists into a single+  -- publishDiagnostics function call (regardless of the order+  -- of the lists).+  publishDiags $+    map makeParseErrorDiagnostic parsingErrs  where-  sendDiagnostic :: ((Int, Int), (Int, Int), Text) -> LspM () ()-  sendDiagnostic ((startLine, startCol), (endLine, endCol), msg) = do-    let diags =-          [ J.Diagnostic-              ( J.Range-                  (J.Position (fromIntegral startLine) (fromIntegral startCol))-                  (J.Position (fromIntegral endLine) (fromIntegral endCol))-              )-              (Just J.DsWarning) -- severity-              Nothing -- code-              (Just "swarm-lsp") -- source-              msg-              Nothing -- tags-              (Just (J.List []))-          ]-    publishDiagnostics 1 doc version (partitionBySource diags)+  publishDiags :: [J.Diagnostic] -> LspM () ()+  publishDiags = publishDiagnostics 1 doc version . partitionBySource +  makeUnusedVarDiagnostic :: (J.Range, Text) -> J.Diagnostic+  makeUnusedVarDiagnostic (range, msg) =+    J.Diagnostic+      range+      (Just J.DsWarning) -- severity+      Nothing -- code+      (Just diagnosticSourcePrefix) -- source+      msg+      (Just (J.List [J.DtUnnecessary])) -- tags+      Nothing -- related source code info+  makeParseErrorDiagnostic :: ((Int, Int), (Int, Int), Text) -> J.Diagnostic+  makeParseErrorDiagnostic ((startLine, startCol), (endLine, endCol), msg) =+    J.Diagnostic+      ( J.Range+          (J.Position (fromIntegral startLine) (fromIntegral startCol))+          (J.Position (fromIntegral endLine) (fromIntegral endCol))+      )+      (Just J.DsError) -- severity+      Nothing -- code+      (Just diagnosticSourcePrefix) -- source+      msg+      Nothing -- tags+      (Just (J.List []))+ handlers :: Handlers (LspM ()) handlers =   mconcat@@ -111,4 +149,13 @@           Just vf@(VirtualFile _ version _rope) -> do             validateSwarmCode doc (Just $ fromIntegral version) (virtualFileText vf)           _ -> debug $ "No virtual file found for: " <> from (show msg)+    , requestHandler J.STextDocumentHover $ \req responder -> do+        let doc = req ^. J.params . J.textDocument . J.uri . to J.toNormalizedUri+            pos = req ^. J.params . J.position+        mdoc <- getVirtualFile doc+        let maybeHover = do+              vf <- mdoc+              (markdownText, maybeRange) <- H.showHoverInfo doc Nothing pos vf+              return $ Hover (J.HoverContents $ J.MarkupContent J.MkMarkdown markdownText) maybeRange+        responder $ Right maybeHover     ]
+ src/Swarm/Language/LSP/Hover.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.Language.LSP.Hover (+  showHoverInfo,++  -- * Documentation rendering+  renderDoc,+  treeToMarkdown,++  -- * Finding source location+  narrowToPosition,++  -- * Explaining source position+  explain,+) where++import Control.Applicative ((<|>))+import Control.Lens ((^.))+import Control.Monad (guard, void)+import Data.Graph+import Data.List.NonEmpty (NonEmpty (..))+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Utf16.Rope qualified as R+import Language.LSP.Types qualified as J+import Language.LSP.VFS+import Swarm.Language.Context as Ctx+import Swarm.Language.Module (Module (..))+import Swarm.Language.Parse (readTerm', unTuple)+import Swarm.Language.Pipeline (ProcessedTerm (..), processParsedTerm)+import Swarm.Language.Pretty (prettyText)+import Swarm.Language.Syntax+import Swarm.Language.Typecheck (inferConst)+import Swarm.Language.Types+import Swarm.Util qualified as U++withinBound :: Int -> SrcLoc -> Bool+withinBound pos (SrcLoc s e) = pos >= s && pos < e+withinBound _ NoLoc = False++ropeToLspPosition :: R.Position -> J.Position+ropeToLspPosition (R.Position l c) =+  J.Position (fromIntegral l) (fromIntegral c)++lspToRopePosition :: J.Position -> R.Position+lspToRopePosition (J.Position myLine myCol) =+  R.Position (fromIntegral myLine) (fromIntegral myCol)++showHoverInfo ::+  J.NormalizedUri ->+  J.TextDocumentVersion ->+  J.Position ->+  VirtualFile ->+  Maybe (Text, Maybe J.Range)+showHoverInfo _ _ p vf@(VirtualFile _ _ myRope) =+  case readTerm' content of+    Left _ -> Nothing+    Right Nothing -> Nothing+    Right (Just stx) -> Just $ case processParsedTerm stx of+      Left _e ->+        let found@(Syntax foundSloc _) = narrowToPosition stx $ fromIntegral absolutePos+            finalPos = posToRange myRope foundSloc+         in (,finalPos) . treeToMarkdown 0 $ explain found+      Right (ProcessedTerm modul _req _reqCtx) ->+        let found@(Syntax' foundSloc _ _) = narrowToPosition (moduleAST modul) $ fromIntegral absolutePos+            finalPos = posToRange myRope foundSloc+         in (,finalPos) . treeToMarkdown 0 $ explain found+ where+  content = virtualFileText vf+  absolutePos =+    maybe 0 (R.length . fst) $+      R.splitAtPosition (lspToRopePosition p) myRope++posToRange :: R.Rope -> SrcLoc -> Maybe J.Range+posToRange myRope foundSloc = do+  (s, e) <- case foundSloc of+    SrcLoc s e -> Just (s, e)+    _ -> Nothing+  (startRope, _) <- R.splitAt (fromIntegral s) myRope+  (endRope, _) <- R.splitAt (fromIntegral e) myRope+  return $+    J.Range+      (ropeToLspPosition $ R.lengthAsPosition startRope)+      (ropeToLspPosition $ R.lengthAsPosition endRope)++descend ::+  ExplainableType ty =>+  -- | position+  Int ->+  -- | next element to inspect+  Syntax' ty ->+  Maybe (Syntax' ty)+descend pos s1@(Syntax' l1 _ _) = do+  guard $ withinBound pos l1+  return $ narrowToPosition s1 pos++-- | Find the most specific term for a given+-- position within the code.+narrowToPosition ::+  ExplainableType ty =>+  -- | parent term+  Syntax' ty ->+  -- | absolute offset within the file+  Int ->+  Syntax' ty+narrowToPosition s0@(Syntax' _ t ty) pos = fromMaybe s0 $ case t of+  SLam lv _ s -> d (locVarToSyntax' lv $ getInnerType ty) <|> d s+  SApp s1 s2 -> d s1 <|> d s2+  SLet _ lv _ s1@(Syntax' _ _ lty) s2 -> d (locVarToSyntax' lv lty) <|> d s1 <|> d s2+  SDef _ lv _ s@(Syntax' _ _ lty) -> d (locVarToSyntax' lv lty) <|> d s+  SBind mlv s1@(Syntax' _ _ lty) s2 -> (mlv >>= d . flip locVarToSyntax' (getInnerType lty)) <|> d s1 <|> d s2+  SPair s1 s2 -> d s1 <|> d s2+  SDelay _ s -> d s+  -- atoms - return their position and end recursion+  TUnit -> Nothing+  TConst {} -> Nothing+  TDir {} -> Nothing+  TInt {} -> Nothing+  TText {} -> Nothing+  TBool {} -> Nothing+  TVar {} -> Nothing+  TRequire {} -> Nothing+  TRequireDevice {} -> Nothing+  -- these should not show up in surface language+  TRef {} -> Nothing+  TRobot {} -> Nothing+  TAntiInt {} -> Nothing+  TAntiText {} -> Nothing+ where+  d = descend pos++renderDoc :: Int -> Text -> Text+renderDoc d t+  | d == 0 = t+  | otherwise = T.drop 2 . indent (max 0 (4 * (d - 1)) + 2) $ "* " <> t+ where+  indent x = T.unlines . map (T.replicate x " " <>) . T.lines++treeToMarkdown :: Int -> Tree Text -> Text+treeToMarkdown d (Node t children) =+  T.unlines $ renderDoc d t : map (treeToMarkdown $ d + 1) children++class Show t => ExplainableType t where+  -- | Pretty print the type.+  prettyType :: t -> Text++  -- | Strip the type of its outermost layer.+  --+  -- This allows us to strip lambda or command type+  -- and get the type of the bound variable.+  getInnerType :: t -> t++  -- | Check if this type is same as the given 'Polytype'.+  --+  -- We use it to not print same type twice (e.g. inferred and generic).+  eq :: t -> Polytype -> Bool++instance ExplainableType () where+  prettyType = const "?"+  getInnerType = id+  eq _ _ = False++instance ExplainableType Polytype where+  prettyType = prettyText+  getInnerType = fmap $ \case+    (l :->: _r) -> l+    (TyCmd t) -> t+    t -> t+  eq = (==)++explain :: ExplainableType ty => Syntax' ty -> Tree Text+explain trm = case trm ^. sTerm of+  TUnit -> literal "The unit value."+  TConst c -> literal . constGenSig c $ briefDoc (constDoc $ constInfo c)+  TDir {} -> literal "A direction literal."+  TInt {} -> literal "An integer literal."+  TText {} -> literal "A text literal."+  TBool {} -> literal "A boolean literal."+  TVar v -> pure $ typeSignature v ty ""+  -- special forms (function application will show for `$`, but really should be rare)+  SApp {} -> explainFunction trm+  TRequireDevice {} -> pure "Require a specific device to be equipped."+  TRequire {} -> pure "Require a certain number of an entity."+  -- definition or bindings+  SLet isRecursive var mTypeAnn rhs _b -> pure $ explainDefinition False isRecursive var (rhs ^. sType) mTypeAnn+  SDef isRecursive var mTypeAnn rhs -> pure $ explainDefinition True isRecursive var (rhs ^. sType) mTypeAnn+  SLam (LV _s v) _mType _syn ->+    pure $+      typeSignature v ty $+        "A lambda expression binding the variable " <> U.bquote v <> "."+  SBind mv rhs _cmds ->+    pure $+      typeSignature (maybe "__rhs" lvVar mv) (getInnerType $ rhs ^. sType) $+        "A monadic bind for commands" <> maybe "." (\(LV _s v) -> ", that binds variable " <> U.bquote v <> ".") mv+  -- composite types+  SPair {} ->+    Node+      (typeSignature "_" ty "A tuple consisting of:")+      (explain <$> unTuple trm)+  SDelay {} ->+    pure . T.unlines $+      [ "Delay evaluation of a term, written `{...}`."+      , ""+      , "Swarm is an eager language, but in some cases (e.g. for `if` statements and recursive bindings) we need to delay evaluation."+      , ""+      , "The counterpart to `{...}` is `force`:"+      , "```"+      , "force {t} = t"+      , "```"+      ]+  -- internal syntax that should not actually show in hover+  TRef {} -> internal "A memory reference."+  TAntiInt {} -> internal "An antiquoted Haskell variable name of type Integer."+  TAntiText {} -> internal "An antiquoted Haskell variable name of type Text."+  TRobot {} -> internal "A robot reference."+ where+  ty = trm ^. sType+  literal = pure . typeSignature (prettyText . void $ trm ^. sTerm) ty+  internal description = literal $ description <> "\n**These should never show up in surface syntax.**"+  constGenSig c =+    let ity = inferConst c+     in if ty `eq` ity then id else typeSignature (prettyText c) ity++-- | Helper function to explain function application.+--+-- Note that 'Force' is often inserted internally, so+-- if it shows up here we drop it.+explainFunction :: ExplainableType ty => Syntax' ty -> Tree Text+explainFunction s =+  case unfoldApps s of+    (Syntax' _ (TConst Force) _ :| [innerT]) -> explain innerT+    (Syntax' _ (TConst Force) _ :| f : params) -> explainF f params+    (f :| params) -> explainF f params+ where+  explainF f params =+    Node+      "Function application of:"+      [ explain f+      , Node+          "with parameters:"+          (map explain params)+      ]++explainDefinition :: ExplainableType ty => Bool -> Bool -> LocVar -> ty -> Maybe Polytype -> Text+explainDefinition isDef isRecursive (LV _s var) ty maybeTypeAnnotation =+  typeSignature var ty $+    T.unwords+      [ "A"+      , (if isRecursive then "" else "non-") <> "recursive"+      , if isDef then "definition" else "let"+      , "expression"+      , if null maybeTypeAnnotation then "without" else "with"+      , "a type annotation on the variable."+      ]++typeSignature :: ExplainableType ty => Var -> ty -> Text -> Text+typeSignature v typ body = T.unlines ["```", short, "```", body]+ where+  short = v <> ": " <> prettyType typ
+ src/Swarm/Language/LSP/VarUsage.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.Language.LSP.VarUsage where++import Control.Monad (guard)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Map (Map)+import Data.Map qualified as M+import Data.Set (Set)+import Data.Set qualified as S+import Data.Text (Text)+import Data.Text qualified as T+import Language.LSP.Types qualified as J+import Swarm.Language.Parse qualified as P+import Swarm.Language.Syntax+import Swarm.Util qualified as U++data BindingType+  = Lambda+  | Let+  | Bind+  deriving (Eq, Show)++data VarUsage = VarUsage LocVar BindingType++type BindingSites = Map Var (NonEmpty SrcLoc)++data Usage = Usage+  { usages :: Set LocVar+  -- ^ Variable references+  , problems :: [VarUsage]+  -- ^ Variable declarations without any references+  }++instance Semigroup Usage where+  Usage y1 z1 <> Usage y2 z2 =+    Usage+      (y1 <> y2)+      (z1 <> z2)++instance Monoid Usage where+  mempty = Usage mempty mempty++toErrPos :: Text -> VarUsage -> Maybe (J.Range, Text)+toErrPos code (VarUsage (LV loc v) scope) = do+  -- A leading underscore will suppress the unused variable warning+  guard $ not $ "_" `T.isPrefixOf` v+  rangePair <- case loc of+    SrcLoc s e -> Just (s, e)+    _ -> Nothing+  let (start, end) = P.getLocRange code rangePair+      ((startLine, startCol), (endLine, endCol)) = (minusOne start, minusOne end)+      range =+        J.Range+          (J.Position (fromIntegral startLine) (fromIntegral startCol))+          (J.Position (fromIntegral endLine) (fromIntegral endCol))+  return (range, txt)+ where+  txt =+    T.unwords+      [ "Unused variable"+      , U.quote v+      , "in"+      , T.pack $ show scope+      , "expression"+      ]+  minusOne (x, y) = (x - 1, y - 1)++-- | Descends the syntax tree rooted at a variable declaration,+-- accumulating variable references.+-- Generates a "problem" if an associated variable reference+-- is not encountered in the subtree for this declaration.+checkOccurrences ::+  BindingSites ->+  LocVar ->+  BindingType ->+  [Syntax] ->+  Usage+checkOccurrences bindings lv@(LV loc v) declType childSyntaxes =+  Usage childUsages $ missing <> deeperMissing+ where+  deeperBindings = M.insertWith (<>) v (pure loc) bindings+  Usage childUsages deeperMissing = mconcat $ map (getUsage deeperBindings) childSyntaxes+  missing = [VarUsage lv declType | lv `S.notMember` childUsages]++-- | Build up the bindings map as a function argument as+-- we descend into the syntax tree.+-- Aggregates unused bindings as we return from each layer.+getUsage ::+  BindingSites ->+  Syntax ->+  Usage+getUsage bindings (Syntax _pos t) = case t of+  TVar v -> Usage myUsages mempty+   where+    myUsages = case M.lookup v bindings of+      Nothing -> mempty+      Just (loc :| _) -> S.singleton $ LV loc v+  SLam v _ s -> checkOccurrences bindings v Lambda [s]+  SApp s1 s2 -> getUsage bindings s1 <> getUsage bindings s2+  SLet _ v _ s1 s2 -> getUsage bindings s1 <> checkOccurrences bindings v Let [s2]+  SPair s1 s2 -> getUsage bindings s1 <> getUsage bindings s2+  SDef _ _v _ s -> getUsage bindings s+  SBind maybeVar s1 s2 -> case maybeVar of+    Just v -> checkOccurrences bindings v Bind [s1, s2]+    Nothing -> getUsage bindings s1 <> getUsage bindings s2+  SDelay _ s -> getUsage bindings s+  _ -> mempty
+ src/Swarm/Language/Module.hs view
@@ -0,0 +1,51 @@+-- |+-- Module      :  Swarm.Language.Module+-- Copyright   :  Brent Yorgey+-- Maintainer  :  byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- A 'Module' packages together a type-annotated syntax tree along+-- with a context of top-level definitions.+module Swarm.Language.Module (+  -- * Modules+  Module (..),+  TModule,+  UModule,+  trivMod,+) where++import Data.Data (Data)+import Data.Yaml (FromJSON, ToJSON)+import GHC.Generics (Generic)+import Swarm.Language.Context (Ctx, empty)+import Swarm.Language.Syntax (Syntax')+import Swarm.Language.Types (Polytype, UPolytype, UType)++------------------------------------------------------------+-- Modules+------------------------------------------------------------++-- | A module generally represents the result of performing type+--   inference on a top-level expression, which in particular can+--   contain definitions ('Swarm.Language.Syntax.TDef').  A module+--   contains the type-annotated AST of the expression itself, as well+--   as the context giving the types of any defined variables.+data Module s t = Module {moduleAST :: Syntax' s, moduleCtx :: Ctx t}+  deriving (Show, Eq, Functor, Data, Generic, FromJSON, ToJSON)++-- | A 'TModule' is the final result of the type inference process on+--   an expression: we get a polytype for the expression, and a+--   context of polytypes for the defined variables.+type TModule = Module Polytype Polytype++-- | A 'UModule' represents the type of an expression at some+--   intermediate stage during the type inference process.  We get a+--   'UType' (/not/ a 'UPolytype') for the expression, which may+--   contain some free unification or type variables, as well as a+--   context of 'UPolytype's for any defined variables.+type UModule = Module UType UPolytype++-- | The trivial module for a given AST, with the empty context.+trivMod :: Syntax' s -> Module s t+trivMod t = Module t empty
src/Swarm/Language/Parse.hs view
@@ -33,8 +33,10 @@   showShortError,   showErrorPos,   getLocRange,+  unTuple, ) where +import Control.Lens (view, (^.)) import Control.Monad.Combinators.Expr import Control.Monad.Reader import Data.Bifunctor@@ -87,7 +89,7 @@        , "text"        , "dir"        , "bool"-       , "robot"+       , "actor"        , "cmd"        , "delay"        , "let"@@ -128,13 +130,17 @@ -- | Parse an identifier, i.e. any non-reserved string containing --   alphanumeric characters and underscores and not starting with a --   number.-identifier :: Parser Text-identifier = (lexeme . try) (p >>= check) <?> "variable name"+identifier :: Parser Var+identifier = lvVar <$> locIdentifier++-- | Parse an identifier together with its source location info.+locIdentifier :: Parser LocVar+locIdentifier = uncurry LV <$> parseLocG ((lexeme . try) (p >>= check) <?> "variable name")  where   p = (:) <$> (letterChar <|> char '_') <*> many (alphaNumChar <|> char '_' <|> char '\'')   check s     | toLower t `elem` reservedWords =-      fail $ "reserved word '" ++ s ++ "' cannot be used as variable name"+        fail $ "reserved word '" ++ s ++ "' cannot be used as variable name"     | otherwise = return t    where     t = into @Text s@@ -185,11 +191,11 @@     -- Otherwise, require all variables to be explicitly quantified     | S.null free = return $ Forall xs ty     | otherwise =-      fail $-        unlines-          [ "  Type contains free variable(s): " ++ unwords (map from (S.toList free))-          , "  Try adding them to the 'forall'."-          ]+        fail $+          unlines+            [ "  Type contains free variable(s): " ++ unwords (map from (S.toList free))+            , "  Try adding them to the 'forall'."+            ]    where     free = tyVars ty `S.difference` S.fromList xs @@ -212,7 +218,7 @@     <|> TyText <$ reserved "text"     <|> TyDir <$ reserved "dir"     <|> TyBool <$ reserved "bool"-    <|> TyRobot <$ reserved "robot"+    <|> TyActor <$ reserved "actor"     <|> TyCmd <$> (reserved "cmd" *> parseTypeAtom)     <|> TyDelay <$> braces parseType     <|> parens parseType@@ -229,15 +235,15 @@   consts = filter isUserFunc allConst   alternative c = c <$ reserved (syntax $ constInfo c) --- | Add 'Location' to a parser-parseLocG :: Parser a -> Parser (Location, a)+-- | Add 'SrcLoc' to a parser+parseLocG :: Parser a -> Parser (SrcLoc, a) parseLocG pa = do   start <- getOffset   a <- pa   end <- getOffset-  pure (Location start end, a)+  pure (SrcLoc start end, a) --- | Add 'Location' to a 'Term' parser+-- | Add 'SrcLoc' to a 'Term' parser parseLoc :: Parser Term -> Parser Syntax parseLoc pterm = uncurry Syntax <$> parseLocG pterm @@ -255,21 +261,25 @@           *> ( ( TRequireDevice                   <$> (textLiteral <?> "device name in double quotes")                )-                <|> ( TRequire <$> (fromIntegral <$> integer)+                <|> ( TRequire+                        <$> (fromIntegral <$> integer)                         <*> (textLiteral <?> "entity name in double quotes")                     )              )-        <|> SLam <$> (symbol "\\" *> identifier)+        <|> SLam+          <$> (symbol "\\" *> locIdentifier)           <*> optional (symbol ":" *> parseType)           <*> (symbol "." *> parseTerm)-        <|> sLet <$> (reserved "let" *> identifier)+        <|> sLet+          <$> (reserved "let" *> locIdentifier)           <*> optional (symbol ":" *> parsePolytype)           <*> (symbol "=" *> parseTerm)           <*> (reserved "in" *> parseTerm)-        <|> sDef <$> (reserved "def" *> identifier)+        <|> sDef+          <$> (reserved "def" *> locIdentifier)           <*> optional (symbol ":" *> parsePolytype)           <*> (symbol "=" *> parseTerm <* reserved "end")-        <|> parens (mkTuple <$> (parseTerm `sepBy` symbol ","))+        <|> parens (view sTerm . mkTuple <$> (parseTerm `sepBy` symbol ","))     )     -- Potential syntax for explicitly requesting memoized delay.     -- Perhaps we will not need this in the end; see the discussion at@@ -281,20 +291,27 @@     <|> parseLoc (SDelay SimpleDelay <$> braces parseTerm)     <|> parseLoc (ask >>= (guard . (== AllowAntiquoting)) >> parseAntiquotation) -mkTuple :: [Syntax] -> Term-mkTuple [] = TUnit-mkTuple [STerm x] = x-mkTuple (x : xs) = SPair x (STerm (mkTuple xs))+mkTuple :: [Syntax] -> Syntax+mkTuple [] = Syntax NoLoc TUnit -- should never happen+mkTuple [x] = x+mkTuple (x : xs) = let r = mkTuple xs in loc x r $ SPair x r+ where+  loc a b = Syntax $ (a ^. sLoc) <> (b ^. sLoc) +unTuple :: Syntax' ty -> [Syntax' ty]+unTuple = \case+  Syntax' _ (SPair s1 s2) _ -> s1 : unTuple s2+  s -> [s]+ -- | Construct an 'SLet', automatically filling in the Boolean field --   indicating whether it is recursive.-sLet :: Var -> Maybe Polytype -> Syntax -> Syntax -> Term-sLet x ty t1 = SLet (x `S.member` setOf fv (sTerm t1)) x ty t1+sLet :: LocVar -> Maybe Polytype -> Syntax -> Syntax -> Term+sLet x ty t1 = SLet (lvVar x `S.member` setOf freeVarsV t1) x ty t1  -- | Construct an 'SDef', automatically filling in the Boolean field --   indicating whether it is recursive.-sDef :: Var -> Maybe Polytype -> Syntax -> Term-sDef x ty t = SDef (x `S.member` setOf fv (sTerm t)) x ty t+sDef :: LocVar -> Maybe Polytype -> Syntax -> Term+sDef x ty t = SDef (lvVar x `S.member` setOf freeVarsV t) x ty t  parseAntiquotation :: Parser Term parseAntiquotation =@@ -307,23 +324,23 @@  mkBindChain :: [Stmt] -> Parser Syntax mkBindChain stmts = case last stmts of-  Binder x _ -> return $ foldr mkBind (STerm (TApp (TConst Return) (TVar x))) stmts+  Binder x _ -> return $ foldr mkBind (STerm (TApp (TConst Return) (TVar (lvVar x)))) stmts   BareTerm t -> return $ foldr mkBind t (init stmts)  where-  mkBind (BareTerm t1) t2 = loc t1 t2 $ SBind Nothing t1 t2-  mkBind (Binder x t1) t2 = loc t1 t2 $ SBind (Just x) t1 t2-  loc a b = Syntax $ sLoc a <> sLoc b+  mkBind (BareTerm t1) t2 = loc Nothing t1 t2 $ SBind Nothing t1 t2+  mkBind (Binder x t1) t2 = loc (Just x) t1 t2 $ SBind (Just x) t1 t2+  loc mx a b = Syntax $ maybe NoLoc lvSrcLoc mx <> (a ^. sLoc) <> (b ^. sLoc)  data Stmt   = BareTerm Syntax-  | Binder Text Syntax+  | Binder LocVar Syntax   deriving (Show)  parseStmt :: Parser Stmt parseStmt =-  mkStmt <$> optional (try (identifier <* symbol "<-")) <*> parseExpr+  mkStmt <$> optional (try (locIdentifier <* symbol "<-")) <*> parseExpr -mkStmt :: Maybe Text -> Syntax -> Stmt+mkStmt :: Maybe LocVar -> Syntax -> Stmt mkStmt Nothing = BareTerm mkStmt (Just x) = Binder x @@ -339,7 +356,7 @@     [] -> term     defs -> foldr1 mkBind defs  where-  mkBind t1 t2 = Syntax (sLoc t1 <> sLoc t2) $ SBind Nothing t1 t2+  mkBind t1 t2 = Syntax ((t1 ^. sLoc) <> (t2 ^. sLoc)) $ SBind Nothing t1 t2   nestedDefs term' acc = case term' of     def@(Syntax _ SDef {}) -> def : acc     (Syntax _ (SApp nestedTerm def@(Syntax _ SDef {}))) -> nestedDefs nestedTerm (def : acc)@@ -362,7 +379,7 @@   exprLoc2 :: Parser (Syntax -> Syntax -> Term) -> Parser (Syntax -> Syntax -> Syntax)   exprLoc2 p = do     (l, f) <- parseLocG p-    pure $ \s1 s2 -> Syntax (l <> sLoc s1 <> sLoc s2) $ f s1 s2+    pure $ \s1 s2 -> Syntax (l <> (s1 ^. sLoc) <> (s2 ^. sLoc)) $ f s1 s2  -- | Precedences and parsers of binary operators. --@@ -405,7 +422,7 @@   exprLoc1 :: Parser (Syntax -> Term) -> Parser (Syntax -> Syntax)   exprLoc1 p = do     (l, f) <- parseLocG p-    pure $ \s -> Syntax (l <> sLoc s) $ f s+    pure $ \s -> Syntax (l <> s ^. sLoc) $ f s  operatorString :: Text -> Parser Text operatorString n = (lexeme . try) (string n <* notFollowedBy operatorSymbol)@@ -514,7 +531,7 @@   line = unPos $ sourceLine $ pstateSourcePos ps   col = unPos $ sourceColumn $ pstateSourcePos ps --- | A utility for converting a Location into a range+-- | A utility for converting a SrcLoc into a range getLocRange :: Text -> (Int, Int) -> ((Int, Int), (Int, Int)) getLocRange code (locStart, locEnd) = (start, end)  where
src/Swarm/Language/Pipeline.hs view
@@ -21,6 +21,7 @@   showTypeErrorPos, ) where +import Control.Lens ((^.)) import Data.Bifunctor (first) import Data.Data (Data) import Data.Text (Text)@@ -28,6 +29,7 @@ import GHC.Generics (Generic) import Swarm.Language.Context import Swarm.Language.Elaborate+import Swarm.Language.Module import Swarm.Language.Parse import Swarm.Language.Pretty import Swarm.Language.Requirement@@ -40,10 +42,8 @@ --   pipeline.  Put a 'Term' in, and get one of these out. data ProcessedTerm   = ProcessedTerm-      Term-      -- ^ The elaborated term       TModule-      -- ^ The type of the term (and of any embedded definitions)+      -- ^ The elaborated + type-annotated term, plus types of any embedded definitions       Requirements       -- ^ Requirements of the term       ReqCtx@@ -60,7 +60,7 @@       Right (Just pt) -> return pt  instance ToJSON ProcessedTerm where-  toJSON (ProcessedTerm t _ _ _) = String $ prettyText t+  toJSON (ProcessedTerm t _ _) = String $ prettyText (moduleAST t)  -- | Given a 'Text' value representing a Swarm program, --@@ -87,8 +87,8 @@ prettyTypeErr :: Text -> TypeErr -> Text prettyTypeErr code te = teLoc <> prettyText te  where-  teLoc = case getTypeErrLocation te of-    Just (Location s e) -> (from . show . fst . fst $ getLocRange code (s, e)) <> ": "+  teLoc = case getTypeErrSrcLoc te of+    Just (SrcLoc s e) -> (from . show . fst . fst $ getLocRange code (s, e)) <> ": "     _anyOtherLoc -> ""  showTypeErrorPos :: Text -> TypeErr -> ((Int, Int), (Int, Int), Text)@@ -96,14 +96,17 @@  where   minusOne (x, y) = (x - 1, y - 1) -  (start, end) = case getTypeErrLocation te of-    Just (Location s e) -> getLocRange code (s, e)+  (start, end) = case getTypeErrSrcLoc te of+    Just (SrcLoc s e) -> getLocRange code (s, e)     _anyOtherLoc -> ((1, 1), (65535, 65535)) -- unknown loc spans the whole document   msg = prettyText te  -- | Like 'processTerm'', but use a term that has already been parsed. processParsedTerm' :: TCtx -> ReqCtx -> Syntax -> Either TypeErr ProcessedTerm processParsedTerm' ctx capCtx t = do-  ty <- inferTop ctx t-  let (caps, capCtx') = requirements capCtx (sTerm t)-  return $ ProcessedTerm (elaborate (sTerm t)) ty caps capCtx'+  m <- inferTop ctx t+  let (caps, capCtx') = requirements capCtx (t ^. sTerm)+  return $ ProcessedTerm (elaborateModule m) caps capCtx'++elaborateModule :: TModule -> TModule+elaborateModule (Module ast ctx) = Module (elaborate ast) ctx
src/Swarm/Language/Pipeline/QQ.hs view
@@ -15,6 +15,7 @@ import Swarm.Language.Pipeline import Swarm.Language.Pretty (prettyText) import Swarm.Language.Syntax+import Swarm.Language.Types (Polytype) import Swarm.Util (liftText) import Witch (from) @@ -48,7 +49,7 @@     Left errMsg -> fail $ from $ prettyText errMsg     Right ptm -> dataToExpQ ((fmap liftText . cast) `extQ` antiTermExp) ptm -antiTermExp :: Term -> Maybe TH.ExpQ+antiTermExp :: Term' Polytype -> Maybe TH.ExpQ antiTermExp (TAntiText v) =   Just $ TH.appE (TH.conE (TH.mkName "TText")) (TH.varE (TH.mkName (from v))) antiTermExp (TAntiInt v) =
src/Swarm/Language/Pretty.hs view
@@ -61,7 +61,7 @@   prettyPrec _ BDir = "dir"   prettyPrec _ BText = "text"   prettyPrec _ BBool = "bool"-  prettyPrec _ BRobot = "robot"+  prettyPrec _ BActor = "actor"  instance PrettyPrec IntVar where   prettyPrec _ = pretty . mkVarName "u"@@ -111,6 +111,9 @@ instance PrettyPrec Const where   prettyPrec p c = pparens (p > fixity (constInfo c)) $ pretty . syntax . constInfo $ c +instance PrettyPrec (Syntax' ty) where+  prettyPrec p = prettyPrec p . eraseS+ instance PrettyPrec Term where   prettyPrec _ TUnit = "()"   prettyPrec p (TConst c) = prettyPrec p c@@ -120,10 +123,10 @@   prettyPrec _ (TText s) = fromString (show s)   prettyPrec _ (TAntiText v) = "$str:" <> pretty v   prettyPrec _ (TBool b) = bool "false" "true" b-  prettyPrec _ (TRobot r) = "<r" <> pretty r <> ">"+  prettyPrec _ (TRobot r) = "<a" <> pretty r <> ">"   prettyPrec _ (TRef r) = "@" <> pretty r-  prettyPrec p (TRequireDevice d) = pparens (p > 10) $ "require" <+> ppr (TText d)-  prettyPrec p (TRequire n e) = pparens (p > 10) $ "require" <+> pretty n <+> ppr (TText e)+  prettyPrec p (TRequireDevice d) = pparens (p > 10) $ "require" <+> ppr @Term (TText d)+  prettyPrec p (TRequire n e) = pparens (p > 10) $ "require" <+> pretty n <+> ppr @Term (TText e)   prettyPrec _ (TVar s) = pretty s   prettyPrec _ (TDelay _ t) = braces $ ppr t   prettyPrec _ t@TPair {} = prettyTuple t
src/Swarm/Language/Requirement.hs view
@@ -45,15 +45,15 @@ -- | A /requirement/ is something a robot must have when it is --   built. There are three types: --   - A robot can require a certain 'Capability', which should be fulfilled---     by installing an appropriate device.---   - A robot can require a specific /device/, which should be installed.+--     by equipping an appropriate device.+--   - A robot can require a specific /device/, which should be equipped. --   - A robot can require some number of a specific entity in its inventory. data Requirement   = -- | Require a specific capability.  This must be fulfilled by-    --   installing an appropriate device.  Requiring the same+    --   equipping an appropriate device.  Requiring the same     --   capability multiple times is the same as requiring it once.     ReqCap Capability-  | -- | Require a specific device to be installed.  Note that at this+  | -- | Require a specific device to be equipped.  Note that at this     --   point it is only a name, and has not been resolved to an actual     --   'Entity'.  That's because programs have to be type- and     --   capability-checked independent of an 'EntityMap'.  The name@@ -230,7 +230,8 @@     -- lambda.     TLet r x _ t1 t2 ->       (if r then insert (ReqCap CRecursion) else id) $-        insert (ReqCap CEnv) $ go (Ctx.delete x ctx) t1 <> go (Ctx.delete x ctx) t2+        insert (ReqCap CEnv) $+          go (Ctx.delete x ctx) t1 <> go (Ctx.delete x ctx) t2     -- We also delete the name in a TBind, if any, while recursing on     -- the RHS.     TBind mx t1 t2 -> go ctx t1 <> go (maybe id Ctx.delete mx ctx) t2
src/Swarm/Language/Syntax.hs view
@@ -2,7 +2,10 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}  -- | -- Module      :  Swarm.Language.Syntax@@ -15,6 +18,8 @@ module Swarm.Language.Syntax (   -- * Directions   Direction (..),+  AbsoluteDir (..),+  RelativeDir (..),   DirInfo (..),   applyTurn,   toDirection,@@ -45,8 +50,14 @@   isLong,    -- * Syntax-  Syntax (..),-  Location (..),+  Syntax' (..),+  sLoc,+  sTerm,+  sType,+  Syntax,+  pattern Syntax,+  LocVar (..),+  SrcLoc (..),   noLoc,   pattern STerm,   pattern TPair,@@ -61,24 +72,35 @@   -- * Terms   Var,   DelayType (..),-  Term (..),+  Term' (..),+  Term,   mkOp,   mkOp',+  unfoldApps, +  -- * Erasure+  erase,+  eraseS,+   -- * Term traversal-  fvT,-  fv,-  mapFree1,+  freeVarsS,+  freeVarsT,+  freeVarsV,+  mapFreeS,+  locVarToSyntax', ) where -import Control.Lens (Plated (..), Traversal', (%~))+import Control.Arrow (Arrow ((&&&)))+import Control.Lens (Plated (..), Traversal', makeLenses, (%~), (^.)) import Data.Aeson.Types+import Data.Char qualified as C (toLower) import Data.Data (Data) import Data.Data.Lens (uniplate) import Data.Hashable (Hashable)-import Data.Int (Int64)+import Data.List qualified as L (tail)+import Data.List.NonEmpty (NonEmpty)+import Data.List.NonEmpty qualified as NonEmpty import Data.Map qualified as M-import Data.Maybe (fromMaybe, isJust, mapMaybe) import Data.Set qualified as S import Data.String (IsString (fromString)) import Data.Text hiding (filter, map)@@ -86,103 +108,135 @@ import GHC.Generics (Generic) import Linear import Swarm.Language.Types+import Swarm.Util qualified as Util+import Swarm.Util.Location (Heading) import Witch.From (from)  --------------------------------------------------------------- Constants+-- Directions ------------------------------------------------------------ --- | The type of directions. Used /e.g./ to indicate which way a robot---   will turn.-data Direction = DLeft | DRight | DBack | DForward | DNorth | DSouth | DEast | DWest | DDown+-- | An absolute direction is one which is defined with respect to an+--   external frame of reference; robots need a compass in order to+--   use them.+data AbsoluteDir = DNorth | DSouth | DEast | DWest   deriving (Eq, Ord, Show, Read, Generic, Data, Hashable, ToJSON, FromJSON, Enum, Bounded) -instance ToJSONKey Direction where-  toJSONKey = genericToJSONKey defaultJSONKeyOptions+cardinalDirectionKeyOptions :: JSONKeyOptions+cardinalDirectionKeyOptions =+  defaultJSONKeyOptions+    { keyModifier = map C.toLower . L.tail+    } -instance FromJSONKey Direction where-  fromJSONKey = genericFromJSONKey defaultJSONKeyOptions+instance ToJSONKey AbsoluteDir where+  toJSONKey = genericToJSONKey cardinalDirectionKeyOptions +instance FromJSONKey AbsoluteDir where+  fromJSONKey = genericFromJSONKey cardinalDirectionKeyOptions++-- | A relative direction is one which is defined with respect to the+--   robot's frame of reference; no special capability is needed to+--   use them.+data RelativeDir = DLeft | DRight | DBack | DForward | DDown+  deriving (Eq, Ord, Show, Read, Generic, Data, Hashable, ToJSON, FromJSON, Enum, Bounded)++-- | The type of directions. Used /e.g./ to indicate which way a robot+--   will turn.+data Direction = DAbsolute AbsoluteDir | DRelative RelativeDir+  deriving (Eq, Ord, Show, Read, Generic, Data, Hashable, ToJSON, FromJSON)+ data DirInfo = DirInfo   { dirSyntax :: Text-  , -- absolute direction if it exists-    dirAbs :: Maybe (V2 Int64)-  , -- the turning for the direction-    dirApplyTurn :: V2 Int64 -> V2 Int64+  , dirApplyTurn :: Heading -> Heading+  -- ^ the turning for the direction   }  allDirs :: [Direction]-allDirs = [minBound .. maxBound]+allDirs = map DAbsolute Util.listEnums <> map DRelative Util.listEnums +toHeading :: AbsoluteDir -> Heading+toHeading = \case+  DNorth -> north+  DSouth -> south+  DEast -> east+  DWest -> west+ -- | Information about all directions dirInfo :: Direction -> DirInfo dirInfo d = case d of-  DLeft -> relative (\(V2 x y) -> V2 (-y) x)-  DRight -> relative (\(V2 x y) -> V2 y (-x))-  DBack -> relative (\(V2 x y) -> V2 (-x) (-y))-  DDown -> relative (const down)-  DForward -> relative id-  DNorth -> cardinal north-  DSouth -> cardinal south-  DEast -> cardinal east-  DWest -> cardinal west+  DRelative e -> case e of+    DLeft -> relative perp+    DRight -> relative (fmap negate . perp)+    DBack -> relative (fmap negate)+    DDown -> relative (const down)+    DForward -> relative id+  DAbsolute e -> cardinal $ toHeading e  where   -- name is generate from Direction data constuctor   -- e.g. DLeft becomes "left"-  directionSyntax = toLower . T.tail . from . show $ d-  cardinal v2 = DirInfo directionSyntax (Just v2) (const v2)-  relative = DirInfo directionSyntax Nothing+  directionSyntax = toLower . T.tail . from $ case d of+    DAbsolute x -> show x+    DRelative x -> show x +  cardinal = DirInfo directionSyntax . const+  relative = DirInfo directionSyntax+ -- | Check if the direction is absolute (e.g. 'north' or 'south'). isCardinal :: Direction -> Bool-isCardinal = isJust . dirAbs . dirInfo+isCardinal = \case+  DAbsolute _ -> True+  _ -> False  -- | The cardinal direction north = @V2 0 1@.-north :: V2 Int64+north :: Heading north = V2 0 1  -- | The cardinal direction south = @V2 0 (-1)@.-south :: V2 Int64+south :: Heading south = V2 0 (-1)  -- | The cardinal direction east = @V2 1 0@.-east :: V2 Int64+east :: Heading east = V2 1 0  -- | The cardinal direction west = @V2 (-1) 0@.-west :: V2 Int64+west :: Heading west = V2 (-1) 0  -- | The direction for viewing the current cell = @V2 0 0@.-down :: V2 Int64-down = V2 0 0+down :: Heading+down = zero  -- | The 'applyTurn' function gives the meaning of each 'Direction' by---   turning relative to the given vector or by turning to an absolute---   direction vector.-applyTurn :: Direction -> V2 Int64 -> V2 Int64+--   turning relative to the given heading or by turning to an absolute+--   heading+applyTurn :: Direction -> Heading -> Heading applyTurn = dirApplyTurn . dirInfo --- | Mapping from heading to their corresponding cardinal directions---   only directions with a 'dirAbs` value are mapped-cardinalDirs :: M.Map (V2 Int64) Direction+-- | Mapping from heading to their corresponding cardinal directions.+--   Only absolute directions are mapped.+cardinalDirs :: M.Map Heading Direction cardinalDirs =-  M.fromList-    . mapMaybe (\d -> (,d) <$> (dirAbs . dirInfo $ d))-    $ allDirs+  M.fromList $ map (toHeading &&& DAbsolute) Util.listEnums --- | Possibly convert a vector into a 'Direction'---that is, if the+-- | Possibly convert a heading into a 'Direction'---that is, if the --   vector happens to be a unit vector in one of the cardinal --   directions.-toDirection :: V2 Int64 -> Maybe Direction+toDirection :: Heading -> Maybe Direction toDirection v = M.lookup v cardinalDirs --- | Convert a 'Direction' into a corresponding vector.  Note that+-- | Convert a 'Direction' into a corresponding heading.  Note that --   this only does something reasonable for 'DNorth', 'DSouth', 'DEast', --   and 'DWest'---other 'Direction's return the zero vector.-fromDirection :: Direction -> V2 Int64-fromDirection = fromMaybe (V2 0 0) . dirAbs . dirInfo+fromDirection :: Direction -> Heading+fromDirection = \case+  DAbsolute x -> toHeading x+  _ -> zero +------------------------------------------------------------+-- Constants+------------------------------------------------------------+ -- | Constants, representing various built-in functions and commands. -- --   IF YOU ADD A NEW CONSTANT, be sure to also update:@@ -193,9 +247,10 @@ --   5. the emacs mode syntax highlighter (@contribs/swarm-mode.el@) -- --   GHC will warn you about incomplete pattern matches for the first---   four, so it's not really possible to forget.  Note you do not---   need to update the parser or pretty-printer, since they are---   auto-generated from 'constInfo'.+--   four, and CI will warn you about the last, so in theory it's not+--   really possible to forget.  Note you do not need to update the+--   parser or pretty-printer, since they are auto-generated from+--   'constInfo'. data Const   = -- Trivial actions @@ -220,14 +275,16 @@     Place   | -- | Give an item to another robot at the current location.     Give-  | -- | Install a device on a robot.-    Install+  | -- | Equip a device on oneself.+    Equip+  | -- | Unequip an equipped device, returning to inventory.+    Unequip   | -- | Make an item.     Make   | -- | Sense whether we have a certain item.     Has-  | -- | Sense whether we have a certain device installed.-    Installed+  | -- | Sense whether we have a certain device equipped.+    Equipped   | -- | Sense how many of a certain item we have.     Count   | -- | Drill through an entity.@@ -258,20 +315,28 @@     Time   | -- | Get the current x, y coordinates     Whereami+  | -- | Get the current heading.+    Heading   | -- | See if we can move forward or not.     Blocked   | -- | Scan a nearby cell     Scan   | -- | Upload knowledge to another robot     Upload-  | -- | See if a specific entity is here. (This may be removed.)+  | -- | See if a specific entity is here.     Ishere+  | -- | Check whether the current cell is empty+    Isempty   | -- | Get a reference to oneself     Self   | -- | Get the robot's parent     Parent   | -- | Get a reference to the base     Base+  | -- | Meet a nearby robot+    Meet+  | -- | Meet all nearby robots+    MeetAll   | -- | Get the robot's display name     Whoami   | -- | Set the robot's display name@@ -352,6 +417,10 @@     Chars   | -- | Split string into two parts.     Split+  | -- | Get the character at an index.+    CharAt+  | -- | Create a singleton text value with the given character code.+    ToChar   | -- Function composition with nice operators      -- | Application operator - helps to avoid parentheses:@@ -371,16 +440,16 @@     Teleport   | -- | Run a command as if you were another robot.     As-  | -- | Find a robot by name.+  | -- | Find an actor by name.     RobotNamed-  | -- | Find a robot by number.+  | -- | Find an actor by number.     RobotNumbered   | -- | Check if an entity is known.     Knows   deriving (Eq, Ord, Enum, Bounded, Data, Show, Generic, FromJSON, ToJSON)  allConst :: [Const]-allConst = [minBound .. maxBound]+allConst = Util.listEnums  data ConstInfo = ConstInfo   { syntax :: Text@@ -451,7 +520,7 @@  -- | The arity of a constant, /i.e./ how many arguments it expects. --   The runtime system will collect arguments to a constant (see---   'Swarm.Game.Value.VCApp') until it has enough, then dispatch+--   'Swarm.Language.Value.VCApp') until it has enough, then dispatch --   the constant's behavior. arity :: Const -> Int arity c = case constMeta $ constInfo c of@@ -524,7 +593,7 @@       , "Usually it is automatically inserted where needed, so you do not have to worry about it."       ]   Selfdestruct ->-    command 0 short . doc "Self-destruct the robot." $+    command 0 short . doc "Self-destruct a robot." $       [ "Useful to not clutter the world."       , "This destroys the robot's inventory, so consider `salvage` as an alternative."       ]@@ -539,11 +608,12 @@   Place ->     command 1 short . doc "Place an item at the current location." $       ["The current location has to be empty for this to work."]-  Give -> command 2 short "Give an item to another robot nearby."-  Install -> command 2 short "Install a device from inventory on a robot."+  Give -> command 2 short "Give an item to another actor nearby."+  Equip -> command 1 short "Equip a device on oneself."+  Unequip -> command 1 short "Unequip an equipped device, returning to inventory."   Make -> command 1 long "Make an item using a recipe."   Has -> command 1 Intangible "Sense whether the robot has a given item in its inventory."-  Installed -> command 1 Intangible "Sense whether the robot has a specific device installed."+  Equipped -> command 1 Intangible "Sense whether the robot has a specific device equipped."   Count -> command 1 Intangible "Get the count of a given item in a robot's inventory."   Reprogram ->     command 2 long . doc "Reprogram another robot with a new command." $@@ -557,29 +627,30 @@   Build ->     command 1 long . doc "Construct a new robot." $       [ "You can specify a command for the robot to execute."-      , "If the command requires devices they will be installed from your inventory."+      , "If the command requires devices they will be taken from your inventory and "+          <> "equipped on the new robot."       ]   Salvage ->     command 0 long . doc "Deconstruct an old robot." $-      ["Salvaging a robot will give you its inventory, installed devices and log."]+      ["Salvaging a robot will give you its inventory, equipped devices and log."]   Say ->     command 1 short . doc "Emit a message." $-      [ "The message will be in the robots log (if it has one) and the global log."+      [ "The message will be in the robot's log (if it has one) and the global log."       , "You can view the message that would be picked by `listen` from the global log "           <> "in the messages panel, along with your own messages and logs."       , "This means that to see messages from other robots you have to be able to listen for them, "-          <> "so once you have a listening device installed messages will be added to your log."+          <> "so once you have a listening device equipped messages will be added to your log."       , "In creative mode, there is of course no such limitation."       ]   Listen ->-    command 1 long . doc "Listen for a message from other robots." $-      [ "It will take the first message said by the closest robot."+    command 1 long . doc "Listen for a message from other actors." $+      [ "It will take the first message said by the closest actor."       , "You do not need to actively listen for the message to be logged though, "-          <> "that is done automatically once you have a listening device installed."+          <> "that is done automatically once you have a listening device equipped."       , "Note that you can see the messages either in your logger device or the message panel."       ]-  Log -> command 1 Intangible "Log the string in the robot's logger."-  View -> command 1 short "View the given robot."+  Log -> command 1 short "Log the string in the robot's logger."+  View -> command 1 short "View the given actor."   Appear ->     command 1 short . doc "Set how the robot is displayed." $       [ "You can either specify one character or five (for each direction)."@@ -590,17 +661,25 @@       ["Only available in creative mode."]   Time -> command 0 Intangible "Get the current time."   Whereami -> command 0 Intangible "Get the current x and y coordinates."+  Heading -> command 0 Intangible "Get the current heading."   Blocked -> command 0 Intangible "See if the robot can move forward."   Scan ->-    command 0 Intangible . doc "Scan a nearby location for entities." $-      [ "Adds the entity (not robot) to your inventory with count 0 if there is any."+    command 1 Intangible . doc "Scan a nearby location for entities." $+      [ "Adds the entity (not actor) to your inventory with count 0 if there is any."       , "If you can use sum types, you can also inspect the result directly."       ]   Upload -> command 1 short "Upload a robot's known entities and log to another robot."   Ishere -> command 1 Intangible "See if a specific entity is in the current location."+  Isempty ->+    command 0 Intangible . doc "Check if the current location is empty." $+      [ "Detects whether or not the current location contains an entity."+      , "Does not detect robots or other actors."+      ]   Self -> function 0 "Get a reference to the current robot."   Parent -> function 0 "Get a reference to the robot's parent."   Base -> function 0 "Get a reference to the base."+  Meet -> command 0 Intangible "Get a reference to a nearby actor, if there is one."+  MeetAll -> command 0 long "Run a command for each nearby actor."   Whoami -> command 0 Intangible "Get the robot's display name."   Setname -> command 1 short "Set the robot's display name."   Random ->@@ -644,6 +723,15 @@       , "`(s1,s2) == split (chars s1) (s1 ++ s2)`"       , "So split can be used to undo concatenation if you know the length of the original string."       ]+  CharAt ->+    function 2 . doc "Get the character at a given index." $+      [ "Gets the character (as an `int` representing a Unicode codepoint) at a specific index in a `text` value.  Valid indices are 0 through `chars t - 1`."+      , "Throws an exception if given an out-of-bounds index."+      ]+  ToChar ->+    function 1 . doc "Create a singleton `text` value from the given character code." $+      [ "That is, `chars (toChar c) == 1` and `charAt 0 (toChar c) == c`."+      ]   AppF ->     binaryOp "$" 0 R . doc "Apply the function on the left to the value on the right." $       [ "This operator is useful to avoid nesting parentheses."@@ -661,8 +749,8 @@       ]   Teleport -> command 2 short "Teleport a robot to the given location."   As -> command 2 Intangible "Hypothetically run a command as if you were another robot."-  RobotNamed -> command 1 Intangible "Find a robot by name."-  RobotNumbered -> command 1 Intangible "Find a robot by number."+  RobotNamed -> command 1 Intangible "Find an actor by name."+  RobotNumbered -> command 1 Intangible "Find an actor by number."   Knows -> command 1 Intangible "Check if the robot knows about an entity."  where   doc b ls = ConstDoc b (T.unlines ls)@@ -699,86 +787,12 @@       , tangibility = Intangible       } --- | Make infix operation, discarding any syntax related location-mkOp' :: Const -> Term -> Term -> Term-mkOp' c t1 = TApp (TApp (TConst c) t1)---- | Make infix operation (e.g. @2 + 3@) a curried function---   application (@((+) 2) 3@).-mkOp :: Const -> Syntax -> Syntax -> Syntax-mkOp c s1@(Syntax l1 _) s2@(Syntax l2 _) = Syntax newLoc newTerm- where-  -- The new syntax span both terms-  newLoc = l1 <> l2-  -- We don't assign a source location for the operator since it is-  -- usually provided as-is and it is not likely to be useful.-  sop = noLoc (TConst c)-  newTerm = SApp (Syntax l1 $ SApp sop s1) s2---- | The surface syntax for the language-data Syntax = Syntax {sLoc :: Location, sTerm :: Term}-  deriving (Eq, Show, Data, Generic, FromJSON, ToJSON)--data Location = NoLoc | Location Int Int-  deriving (Eq, Show, Data, Generic, FromJSON, ToJSON)--instance Semigroup Location where-  NoLoc <> l = l-  l <> NoLoc = l-  Location s1 e1 <> Location s2 e2 = Location (min s1 s2) (max e1 e2)--instance Monoid Location where-  mempty = NoLoc--noLoc :: Term -> Syntax-noLoc = Syntax mempty---- | Match a term without its a syntax-pattern STerm :: Term -> Syntax-pattern STerm t <--  Syntax _ t-  where-    STerm t = Syntax mempty t---- | Match a TPair without syntax-pattern TPair :: Term -> Term -> Term-pattern TPair t1 t2 = SPair (STerm t1) (STerm t2)---- | Match a TLam without syntax-pattern TLam :: Var -> Maybe Type -> Term -> Term-pattern TLam v ty t = SLam v ty (STerm t)---- | Match a TApp without syntax-pattern TApp :: Term -> Term -> Term-pattern TApp t1 t2 = SApp (STerm t1) (STerm t2)--infixl 0 :$:---- | Convenient infix pattern synonym for application.-pattern (:$:) :: Term -> Syntax -> Term-pattern (:$:) t1 s2 = SApp (STerm t1) s2---- | Match a TLet without syntax-pattern TLet :: Bool -> Var -> Maybe Polytype -> Term -> Term -> Term-pattern TLet r v pt t1 t2 = SLet r v pt (STerm t1) (STerm t2)---- | Match a TDef without syntax-pattern TDef :: Bool -> Var -> Maybe Polytype -> Term -> Term-pattern TDef r v pt t = SDef r v pt (STerm t)---- | Match a TBind without syntax-pattern TBind :: Maybe Var -> Term -> Term -> Term-pattern TBind v t1 t2 = SBind v (STerm t1) (STerm t2)---- | Match a TDelay without syntax-pattern TDelay :: DelayType -> Term -> Term-pattern TDelay m t = SDelay m (STerm t)---- | COMPLETE pragma tells GHC using this set of pattern is complete for Term-{-# COMPLETE TUnit, TConst, TDir, TInt, TAntiInt, TText, TAntiText, TBool, TRequireDevice, TRequire, TVar, TPair, TLam, TApp, TLet, TDef, TBind, TDelay #-}+  lowShow :: Show a => a -> Text+  lowShow a = toLower (from (show a))  --------------------------------------------------------------- Terms+-- Basic terms+------------------------------------------------------------  -- | Different runtime behaviors for delayed expressions. data DelayType@@ -797,8 +811,17 @@     MemoizedDelay (Maybe Var)   deriving (Eq, Show, Data, Generic, FromJSON, ToJSON) +-- | A variable with associated source location, used for variable+--   binding sites. (Variable occurrences are a bare TVar which gets+--   wrapped in a Syntax node, so we don't need LocVar for those.)+data LocVar = LV {lvSrcLoc :: SrcLoc, lvVar :: Var}+  deriving (Eq, Ord, Show, Data, Generic, FromJSON, ToJSON)++locVarToSyntax' :: LocVar -> ty -> Syntax' ty+locVarToSyntax' (LV s v) = Syntax' s (TVar v)+ -- | Terms of the Swarm language.-data Term+data Term' ty   = -- | The unit value.     TUnit   | -- | A constant.@@ -815,7 +838,7 @@     TAntiText Text   | -- | A Boolean literal.     TBool Bool-  | -- | A robot value.  These never show up in surface syntax, but are+  | -- | A robot reference.  These never show up in surface syntax, but are     --   here so we can factor pretty-printing for Values through     --   pretty-printing for Terms.     TRobot Int@@ -829,22 +852,22 @@   | -- | A variable.     TVar Var   | -- | A pair.-    SPair Syntax Syntax+    SPair (Syntax' ty) (Syntax' ty)   | -- | A lambda expression, with or without a type annotation on the     --   binder.-    SLam Var (Maybe Type) Syntax+    SLam LocVar (Maybe Type) (Syntax' ty)   | -- | Function application.-    SApp Syntax Syntax+    SApp (Syntax' ty) (Syntax' ty)   | -- | A (recursive) let expression, with or without a type     --   annotation on the variable. The @Bool@ indicates whether     --   it is known to be recursive.-    SLet Bool Var (Maybe Polytype) Syntax Syntax+    SLet Bool LocVar (Maybe Polytype) (Syntax' ty) (Syntax' ty)   | -- | A (recursive) definition command, which binds a variable to a     --   value in subsequent commands. The @Bool@ indicates whether the     --   definition is known to be recursive.-    SDef Bool Var (Maybe Polytype) Syntax+    SDef Bool LocVar (Maybe Polytype) (Syntax' ty)   | -- | A monadic bind for commands, of the form @c1 ; c2@ or @x <- c1; c2@.-    SBind (Maybe Var) Syntax Syntax+    SBind (Maybe LocVar) (Syntax' ty) (Syntax' ty)   | -- | Delay evaluation of a term, written @{...}@.  Swarm is an     --   eager language, but in some cases (e.g. for @if@ statements     --   and recursive bindings) we need to delay evaluation.  The@@ -852,57 +875,239 @@     --   Note that 'Force' is just a constant, whereas 'SDelay' has to     --   be a special syntactic form so its argument can get special     --   treatment during evaluation.-    SDelay DelayType Syntax-  deriving (Eq, Show, Data, Generic, FromJSON, ToJSON)+    SDelay DelayType (Syntax' ty)+  deriving (Eq, Show, Functor, Foldable, Traversable, Data, Generic, FromJSON, ToJSON) -instance Plated Term where+-- The Traversable instance for Term (and for Syntax') is used during+-- typechecking: during intermediate type inference, many of the type+-- annotations placed on AST nodes will have unification variables in+-- them. Once we have finished solving everything we need to do a+-- final traversal over all the types in the AST to substitute away+-- all the unification variables (and generalize, i.e. stick 'forall'+-- on, as appropriate).  See the call to 'mapM' in+-- Swarm.Language.Typecheck.runInfer.++type Term = Term' ()++instance Data ty => Plated (Term' ty) where   plate = uniplate +------------------------------------------------------------+-- Syntax: annotation on top of Terms with SrcLoc and type+------------------------------------------------------------++-- | The surface syntax for the language, with location and type annotations.+data Syntax' ty = Syntax'+  { _sLoc :: SrcLoc+  , _sTerm :: Term' ty+  , _sType :: ty+  }+  deriving (Eq, Show, Functor, Foldable, Traversable, Data, Generic, FromJSON, ToJSON)++instance Data ty => Plated (Syntax' ty) where+  plate = uniplate++data SrcLoc+  = NoLoc+  | -- | Half-open interval from start (inclusive) to end (exclusive)+    SrcLoc Int Int+  deriving (Eq, Ord, Show, Data, Generic, FromJSON, ToJSON)++instance Semigroup SrcLoc where+  NoLoc <> l = l+  l <> NoLoc = l+  SrcLoc s1 e1 <> SrcLoc s2 e2 = SrcLoc (min s1 s2) (max e1 e2)++instance Monoid SrcLoc where+  mempty = NoLoc++------------------------------------------------------------+-- Pattern synonyms for untyped terms+------------------------------------------------------------++type Syntax = Syntax' ()++pattern Syntax :: SrcLoc -> Term -> Syntax+pattern Syntax l t = Syntax' l t ()++{-# COMPLETE Syntax #-}++makeLenses ''Syntax'++noLoc :: Term -> Syntax+noLoc = Syntax mempty++-- | Match an untyped term without its 'SrcLoc'.+pattern STerm :: Term -> Syntax+pattern STerm t <-+  Syntax _ t+  where+    STerm t = Syntax mempty t++-- | Match a TPair without syntax+pattern TPair :: Term -> Term -> Term+pattern TPair t1 t2 = SPair (STerm t1) (STerm t2)++-- | Match a TLam without syntax+pattern TLam :: Var -> Maybe Type -> Term -> Term+pattern TLam v ty t <- SLam (lvVar -> v) ty (STerm t)+  where+    TLam v ty t = SLam (LV NoLoc v) ty (STerm t)++-- | Match a TApp without syntax+pattern TApp :: Term -> Term -> Term+pattern TApp t1 t2 = SApp (STerm t1) (STerm t2)++infixl 0 :$:++-- | Convenient infix pattern synonym for application.+pattern (:$:) :: Term -> Syntax -> Term+pattern (:$:) t1 s2 = SApp (STerm t1) s2++-- | Match a TLet without syntax+pattern TLet :: Bool -> Var -> Maybe Polytype -> Term -> Term -> Term+pattern TLet r v pt t1 t2 <- SLet r (lvVar -> v) pt (STerm t1) (STerm t2)+  where+    TLet r v pt t1 t2 = SLet r (LV NoLoc v) pt (STerm t1) (STerm t2)++-- | Match a TDef without syntax+pattern TDef :: Bool -> Var -> Maybe Polytype -> Term -> Term+pattern TDef r v pt t <- SDef r (lvVar -> v) pt (STerm t)+  where+    TDef r v pt t = SDef r (LV NoLoc v) pt (STerm t)++-- | Match a TBind without syntax+pattern TBind :: Maybe Var -> Term -> Term -> Term+pattern TBind mv t1 t2 <- SBind (fmap lvVar -> mv) (STerm t1) (STerm t2)+  where+    TBind mv t1 t2 = SBind (LV NoLoc <$> mv) (STerm t1) (STerm t2)++-- | Match a TDelay without syntax+pattern TDelay :: DelayType -> Term -> Term+pattern TDelay m t = SDelay m (STerm t)++-- | COMPLETE pragma tells GHC using this set of pattern is complete for Term+{-# COMPLETE TUnit, TConst, TDir, TInt, TAntiInt, TText, TAntiText, TBool, TRequireDevice, TRequire, TVar, TPair, TLam, TApp, TLet, TDef, TBind, TDelay #-}++-- | Make infix operation (e.g. @2 + 3@) a curried function+--   application (@((+) 2) 3@).+mkOp :: Const -> Syntax -> Syntax -> Syntax+mkOp c s1@(Syntax l1 _) s2@(Syntax l2 _) = Syntax newLoc newTerm+ where+  -- The new syntax span both terms+  newLoc = l1 <> l2+  -- We don't assign a source location for the operator since it is+  -- usually provided as-is and it is not likely to be useful.+  sop = noLoc (TConst c)+  newTerm = SApp (Syntax l1 $ SApp sop s1) s2++-- | Make infix operation, discarding any syntax related location+mkOp' :: Const -> Term -> Term -> Term+mkOp' c t1 = TApp (TApp (TConst c) t1)++-- $setup+-- >>> import Control.Lens ((^.))++-- | Turn function application chain into a list.+--+-- >>> syntaxWrap f = fmap (^. sTerm) . f . Syntax NoLoc+-- >>> syntaxWrap unfoldApps (mkOp' Mul (TInt 1) (TInt 2)) -- 1 * 2+-- TConst Mul :| [TInt 1,TInt 2]+unfoldApps :: Syntax' ty -> NonEmpty (Syntax' ty)+unfoldApps trm = NonEmpty.reverse . flip NonEmpty.unfoldr trm $ \case+  Syntax' _ (SApp s1 s2) _ -> (s2, Just s1)+  s -> (s, Nothing)++--------------------------------------------------+-- Erasure++-- | Erase a 'Syntax' tree annotated with @SrcLoc@ and type+--   information to a bare unannotated 'Term'.+eraseS :: Syntax' ty -> Term+eraseS (Syntax' _ t _) = erase t++-- | Erase a type-annotated term to a bare term.+erase :: Term' ty -> Term+erase TUnit = TUnit+erase (TConst c) = TConst c+erase (TDir d) = TDir d+erase (TInt n) = TInt n+erase (TAntiInt v) = TAntiInt v+erase (TText t) = TText t+erase (TAntiText v) = TAntiText v+erase (TBool b) = TBool b+erase (TRobot r) = TRobot r+erase (TRef r) = TRef r+erase (TRequireDevice d) = TRequireDevice d+erase (TRequire n e) = TRequire n e+erase (TVar s) = TVar s+erase (SDelay x s) = TDelay x (eraseS s)+erase (SPair s1 s2) = TPair (eraseS s1) (eraseS s2)+erase (SLam x mty body) = TLam (lvVar x) mty (eraseS body)+erase (SApp s1 s2) = TApp (eraseS s1) (eraseS s2)+erase (SLet r x mty s1 s2) = TLet r (lvVar x) mty (eraseS s1) (eraseS s2)+erase (SDef r x mty s) = TDef r (lvVar x) mty (eraseS s)+erase (SBind mx s1 s2) = TBind (lvVar <$> mx) (eraseS s1) (eraseS s2)++------------------------------------------------------------+-- Free variable traversals+------------------------------------------------------------+ -- | Traversal over those subterms of a term which represent free---   variables.-fvT :: Traversal' Term Term-fvT f = go S.empty+--   variables.  The S suffix indicates that it is a `Traversal' over+--   the `Syntax` nodes (which contain type and source location info)+--   containing free variables inside a larger `Syntax` value.  Note+--   that if you want to get the list of all `Syntax` nodes+--   representing free variables, you can do so via @'toListOf'+--   'freeVarsS'@.+freeVarsS :: forall ty. Traversal' (Syntax' ty) (Syntax' ty)+freeVarsS f = go S.empty  where-  go bound t = case t of-    TUnit -> pure t-    TConst {} -> pure t-    TDir {} -> pure t-    TInt {} -> pure t-    TAntiInt {} -> pure t-    TText {} -> pure t-    TAntiText {} -> pure t-    TBool {} -> pure t-    TRobot {} -> pure t-    TRef {} -> pure t-    TRequireDevice {} -> pure t-    TRequire {} -> pure t+  -- go :: Applicative f => Set Var -> Syntax' ty -> f (Syntax' ty)+  go bound s@(Syntax' l t ty) = case t of+    TUnit -> pure s+    TConst {} -> pure s+    TDir {} -> pure s+    TInt {} -> pure s+    TAntiInt {} -> pure s+    TText {} -> pure s+    TAntiText {} -> pure s+    TBool {} -> pure s+    TRobot {} -> pure s+    TRef {} -> pure s+    TRequireDevice {} -> pure s+    TRequire {} -> pure s     TVar x-      | x `S.member` bound -> pure t-      | otherwise -> f (TVar x)-    SLam x ty (Syntax l1 t1) -> SLam x ty <$> (Syntax l1 <$> go (S.insert x bound) t1)-    SApp (Syntax l1 t1) (Syntax l2 t2) ->-      SApp <$> (Syntax l1 <$> go bound t1) <*> (Syntax l2 <$> go bound t2)-    SLet r x ty (Syntax l1 t1) (Syntax l2 t2) ->-      let bound' = S.insert x bound-       in SLet r x ty <$> (Syntax l1 <$> go bound' t1) <*> (Syntax l2 <$> go bound' t2)-    SPair (Syntax l1 t1) (Syntax l2 t2) ->-      SPair <$> (Syntax l1 <$> go bound t1) <*> (Syntax l2 <$> go bound t2)-    SDef r x ty (Syntax l1 t1) ->-      SDef r x ty <$> (Syntax l1 <$> go (S.insert x bound) t1)-    SBind mx (Syntax l1 t1) (Syntax l2 t2) ->-      SBind mx <$> (Syntax l1 <$> go bound t1) <*> (Syntax l2 <$> go (maybe id S.insert mx bound) t2)-    SDelay m (Syntax l1 t1) ->-      SDelay m <$> (Syntax l1 <$> go bound t1)+      | x `S.member` bound -> pure s+      | otherwise -> f s+    SLam x xty s1 -> rewrap $ SLam x xty <$> go (S.insert (lvVar x) bound) s1+    SApp s1 s2 -> rewrap $ SApp <$> go bound s1 <*> go bound s2+    SLet r x xty s1 s2 ->+      let bound' = S.insert (lvVar x) bound+       in rewrap $ SLet r x xty <$> go bound' s1 <*> go bound' s2+    SPair s1 s2 -> rewrap $ SPair <$> go bound s1 <*> go bound s2+    SDef r x xty s1 -> rewrap $ SDef r x xty <$> go (S.insert (lvVar x) bound) s1+    SBind mx s1 s2 -> rewrap $ SBind mx <$> go bound s1 <*> go (maybe id (S.insert . lvVar) mx bound) s2+    SDelay m s1 -> rewrap $ SDelay m <$> go bound s1+   where+    rewrap s' = Syntax' l <$> s' <*> pure ty --- | Traversal over the free variables of a term.  Note that if you---   want to get the set of all free variables, you can do so via---   @'Data.Set.Lens.setOf' 'fv'@.-fv :: Traversal' Term Var-fv = fvT . (\f -> \case TVar x -> TVar <$> f x; t -> pure t)+-- | Like 'freeVarsS', but traverse over the 'Term's containing free+--   variables.  More direct if you don't need to know the types or+--   source locations of the variables.  Note that if you want to get+--   the list of all `Term`s representing free variables, you can do so via+--   @'toListOf' 'freeVarsT'@.+freeVarsT :: forall ty. Traversal' (Syntax' ty) (Term' ty)+freeVarsT = freeVarsS . sTerm --- | Apply a function to all free occurrences of a particular variable.-mapFree1 :: Var -> (Term -> Term) -> Term -> Term-mapFree1 x f = fvT %~ (\t -> if t == TVar x then f t else t)+-- | Traversal over the free variables of a term.  Like 'freeVarsS'+--   and 'freeVarsT', but traverse over the variable names+--   themselves.  Note that if you want to get the set of all free+--   variable names, you can do so via @'Data.Set.Lens.setOf'+--   'freeVarsV'@.+freeVarsV :: Traversal' (Syntax' ty) Var+freeVarsV = freeVarsT . (\f -> \case TVar x -> TVar <$> f x; t -> pure t) -lowShow :: Show a => a -> Text-lowShow a = toLower (from (show a))+-- | Apply a function to all free occurrences of a particular variable.+mapFreeS :: Var -> (Syntax' ty -> Syntax' ty) -> Syntax' ty -> Syntax' ty+mapFreeS x f = freeVarsS %~ (\t -> case t ^. sTerm of TVar y | y == x -> f t; _ -> t)
src/Swarm/Language/Typecheck.hs view
@@ -1,5 +1,7 @@+{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-}  -- For 'Ord IntVar' instance@@ -18,7 +20,7 @@   -- * Type errors   TypeErr (..),   InvalidAtomicReason (..),-  getTypeErrLocation,+  getTypeErrSrcLoc,    -- * Inference monad   Infer,@@ -46,13 +48,16 @@ ) where  import Control.Category ((>>>))+import Control.Lens ((^.)) import Control.Monad.Except import Control.Monad.Reader import Control.Unification hiding (applyBindings, (=:=)) import Control.Unification qualified as U import Control.Unification.IntVar+import Data.Data (Data, gmapM) import Data.Foldable (fold) import Data.Functor.Identity+import Data.Generics (mkM) import Data.Map (Map) import Data.Map qualified as M import Data.Maybe@@ -60,6 +65,7 @@ import Data.Set qualified as S import Swarm.Language.Context hiding (lookup) import Swarm.Language.Context qualified as Ctx+import Swarm.Language.Module import Swarm.Language.Parse.QQ (tyQ) import Swarm.Language.Syntax import Swarm.Language.Types@@ -79,7 +85,12 @@ runInfer :: TCtx -> Infer UModule -> Either TypeErr TModule runInfer ctx =   (>>= applyBindings)-    >>> (>>= \(Module uty uctx) -> Module <$> (fromU <$> generalize uty) <*> pure (fromU uctx))+    >>> ( >>=+            \(Module u uctx) ->+              Module+                <$> mapM (fmap fromU . generalize) u+                <*> pure (fromU uctx)+        )     >>> flip runReaderT (toU ctx)     >>> runExceptT     >>> evalIntBindingT@@ -89,7 +100,7 @@ --   an 'UnboundVar' error if it is not found, or opening its --   associated 'UPolytype' with fresh unification variables via --   'instantiate'.-lookup :: Location -> Var -> Infer UType+lookup :: SrcLoc -> Var -> Infer UType lookup loc x = do   ctx <- ask   maybe (throwError $ UnboundVar loc x) instantiate (Ctx.lookup x ctx)@@ -142,8 +153,8 @@ infix 4 =:=  -- | Constrain two types to be equal.-(=:=) :: UType -> UType -> Infer ()-s =:= t = void (lift $ s U.=:= t)+(=:=) :: UType -> UType -> Infer UType+s =:= t = lift $ s U.=:= t  -- | @unification-fd@ provides a function 'U.applyBindings' which --   fully substitutes for any bound unification variables (for@@ -163,8 +174,14 @@ instance HasBindings UCtx where   applyBindings = mapM applyBindings +instance (HasBindings u, Data u) => HasBindings (Term' u) where+  applyBindings = gmapM (mkM (applyBindings @(Syntax' u)))++instance (HasBindings u, Data u) => HasBindings (Syntax' u) where+  applyBindings (Syntax' l t u) = Syntax' l <$> applyBindings t <*> applyBindings u+ instance HasBindings UModule where-  applyBindings (Module uty uctx) = Module <$> applyBindings uty <*> applyBindings uctx+  applyBindings (Module u uctx) = Module <$> applyBindings u <*> applyBindings uctx  ------------------------------------------------------------ -- Converting between mono- and polytypes@@ -212,25 +229,25 @@ --   separately be pretty-printed to display them to the user. data TypeErr   = -- | An undefined variable was encountered.-    UnboundVar Location Var+    UnboundVar SrcLoc Var   | -- | A Skolem variable escaped its local context.-    EscapedSkolem Location Var+    EscapedSkolem SrcLoc Var   | Infinite IntVar UType   | -- | The given term was expected to have a certain type, but has a     -- different type instead.-    Mismatch Location (TypeF UType) (TypeF UType)+    Mismatch SrcLoc (TypeF UType) (TypeF UType)   | -- | A definition was encountered not at the top level.-    DefNotTopLevel Location Term+    DefNotTopLevel SrcLoc Term   | -- | A term was encountered which we cannot infer the type of.     --   This should never happen.-    CantInfer Location Term+    CantInfer SrcLoc Term   | -- | An invalid argument was provided to @atomic@.-    InvalidAtomic Location InvalidAtomicReason Term+    InvalidAtomic SrcLoc InvalidAtomicReason Term   deriving (Show)  -- | Various reasons the body of an @atomic@ might be invalid. data InvalidAtomicReason-  = -- | The arugment has too many tangible commands.+  = -- | The argument has too many tangible commands.     TooManyTicks Int   | -- | The argument uses some way to duplicate code: @def@, @let@, or lambda.     AtomicDupingThing@@ -246,8 +263,8 @@   occursFailure = Infinite   mismatchFailure = Mismatch NoLoc -getTypeErrLocation :: TypeErr -> Maybe Location-getTypeErrLocation te = case te of+getTypeErrSrcLoc :: TypeErr -> Maybe SrcLoc+getTypeErrSrcLoc te = case te of   UnboundVar l _ -> Just l   EscapedSkolem l _ -> Just l   Infinite _ _ -> Nothing@@ -268,33 +285,33 @@ -- | Infer the signature of a top-level expression which might --   contain definitions. inferModule :: Syntax -> Infer UModule-inferModule s@(Syntax _ t) = (`catchError` addLocToTypeErr s) $ case t of+inferModule s@(Syntax l t) = (`catchError` addLocToTypeErr s) $ case t of   -- For definitions with no type signature, make up a fresh type   -- variable for the body, infer the body under an extended context,   -- and unify the two.  Then generalize the type and return an   -- appropriate context.-  SDef _ x Nothing t1 -> do+  SDef r x Nothing t1 -> do     xTy <- fresh-    ty <- withBinding x (Forall [] xTy) $ infer t1-    xTy =:= ty-    pty <- generalize ty-    return $ Module (UTyCmd UTyUnit) (singleton x pty)+    t1' <- withBinding (lvVar x) (Forall [] xTy) $ infer t1+    _ <- xTy =:= t1' ^. sType+    pty <- generalize (t1' ^. sType)+    return $ Module (Syntax' l (SDef r x Nothing t1') (UTyCmd UTyUnit)) (singleton (lvVar x) pty)    -- If a (poly)type signature has been provided, skolemize it and   -- check the definition.-  SDef _ x (Just pty) t1 -> do+  SDef r x (Just pty) t1 -> do     let upty = toU pty     uty <- skolemize upty-    withBinding x upty $ check t1 uty-    return $ Module (UTyCmd UTyUnit) (singleton x upty)+    t1' <- withBinding (lvVar x) upty $ check t1 uty+    return $ Module (Syntax' l (SDef r x (Just pty) t1') (UTyCmd UTyUnit)) (singleton (lvVar x) upty)    -- To handle a 'TBind', infer the types of both sides, combining the   -- returned modules appropriately.  Have to be careful to use the   -- correct context when checking the right-hand side in particular.   SBind mx c1 c2 -> do     -- First, infer the left side.-    Module cmda ctx1 <- inferModule c1-    a <- decomposeCmdTy cmda+    Module c1' ctx1 <- inferModule c1+    a <- decomposeCmdTy (c1' ^. sType)      -- Now infer the right side under an extended context: things in     -- scope on the right-hand side include both any definitions@@ -304,47 +321,55 @@     -- case the bound x should shadow the defined one; hence, we apply     -- that binding /after/ (i.e. /within/) the application of @ctx1@.     withBindings ctx1 $-      maybe id (`withBinding` Forall [] a) mx $ do-        Module cmdb ctx2 <- inferModule c2+      maybe id ((`withBinding` Forall [] a) . lvVar) mx $ do+        Module c2' ctx2 <- inferModule c2 -        -- We don't actually need the result type since we're just going-        -- to return cmdb, but it's important to ensure it's a command-        -- type anyway.  Otherwise something like 'move; 3' would be-        -- accepted with type int.-        _ <- decomposeCmdTy cmdb+        -- We don't actually need the result type since we're just+        -- going to return the entire type, but it's important to+        -- ensure it's a command type anyway.  Otherwise something+        -- like 'move; 3' would be accepted with type int.+        _ <- decomposeCmdTy (c2' ^. sType)          -- Ctx.union is right-biased, so ctx1 `union` ctx2 means later         -- definitions will shadow previous ones.  Include the binder         -- (if any) as well, since binders are made available at the top         -- level, just like definitions. e.g. if the user writes `r <- build {move}`,         -- then they will be able to refer to r again later.-        let ctxX = maybe Ctx.empty (`Ctx.singleton` Forall [] a) mx-        return $ Module cmdb (ctx1 `Ctx.union` ctxX `Ctx.union` ctx2)+        let ctxX = maybe Ctx.empty ((`Ctx.singleton` Forall [] a) . lvVar) mx+        return $+          Module+            (Syntax' l (SBind mx c1' c2') (c2' ^. sType))+            (ctx1 `Ctx.union` ctxX `Ctx.union` ctx2)    -- In all other cases, there can no longer be any definitions in the   -- term, so delegate to 'infer'.   _anyOtherTerm -> trivMod <$> infer s --- | Infer the type of a term which does not contain definitions.-infer :: Syntax -> Infer UType+-- | Infer the type of a term which does not contain definitions,+--   returning a type-annotated term.+infer :: Syntax -> Infer (Syntax' UType) infer s@(Syntax l t) = (`catchError` addLocToTypeErr s) $ case t of-  TUnit -> return UTyUnit-  TConst c -> instantiate . toU $ inferConst c-  TDir _ -> return UTyDir-  TInt _ -> return UTyInt-  TAntiInt _ -> return UTyInt-  TText _ -> return UTyText-  TAntiText _ -> return UTyText-  TBool _ -> return UTyBool-  TRobot _ -> return UTyRobot+  TUnit -> return $ Syntax' l TUnit UTyUnit+  TConst c -> Syntax' l (TConst c) <$> (instantiate . toU $ inferConst c)+  TDir d -> return $ Syntax' l (TDir d) UTyDir+  TInt n -> return $ Syntax' l (TInt n) UTyInt+  TAntiInt x -> return $ Syntax' l (TAntiInt x) UTyInt+  TText x -> return $ Syntax' l (TText x) UTyText+  TAntiText x -> return $ Syntax' l (TAntiText x) UTyText+  TBool b -> return $ Syntax' l (TBool b) UTyBool+  TRobot r -> return $ Syntax' l (TRobot r) UTyActor   -- We should never encounter a TRef since they do not show up in   -- surface syntax, only as values while evaluating (*after*   -- typechecking).   TRef _ -> throwError $ CantInfer l t-  TRequireDevice _ -> return $ UTyCmd UTyUnit-  TRequire _ _ -> return $ UTyCmd UTyUnit+  TRequireDevice d -> return $ Syntax' l (TRequireDevice d) (UTyCmd UTyUnit)+  TRequire n d -> return $ Syntax' l (TRequire n d) (UTyCmd UTyUnit)   -- To infer the type of a pair, just infer both components.-  SPair t1 t2 -> UTyProd <$> infer t1 <*> infer t2+  SPair t1 t2 -> do+    t1' <- infer t1+    t2' <- infer t2+    return $ Syntax' l (SPair t1' t2') (UTyProd (t1' ^. sType) (t2' ^. sType))+   -- if t : ty, then  {t} : {ty}.   -- Note that in theory, if the @Maybe Var@ component of the @SDelay@   -- is @Just@, we should typecheck the body under a context extended@@ -354,74 +379,83 @@   -- @SDelay@ nodes are never generated from the surface syntax, only   -- dynamically at runtime when evaluating recursive let or def expressions,   -- so we don't have to worry about typechecking them here.-  SDelay _ dt -> UTyDelay <$> infer dt+  SDelay d t1 -> do+    t1' <- infer t1+    return $ Syntax' l (SDelay d t1') (UTyDelay (t1' ^. sType))+   -- We need a special case for checking the argument to 'atomic'.   -- 'atomic t' has the same type as 't', which must have a type of   -- the form 'cmd a'.  't' must also be syntactically free of   -- variables.+   TConst Atomic :$: at -> do     argTy <- fresh-    check at (UTyCmd argTy)+    at' <- check at (UTyCmd argTy)+    atomic' <- infer (Syntax l (TConst Atomic))     -- It's important that we typecheck the subterm @at@ *before* we     -- check that it is a valid argument to @atomic@: this way we can     -- ensure that we have already inferred the types of any variables     -- referenced.     validAtomic at-    return $ UTyCmd argTy+    return $ Syntax' l (SApp atomic' at') (at' ^. sType)    -- Just look up variables in the context.-  TVar x -> lookup l x+  TVar x -> Syntax' l (TVar x) <$> lookup l x   -- To infer the type of a lambda if the type of the argument is   -- provided, just infer the body under an extended context and return   -- the appropriate function type.   SLam x (Just argTy) lt -> do     let uargTy = toU argTy-    resTy <- withBinding x (Forall [] uargTy) $ infer lt-    return $ UTyFun uargTy resTy+    lt' <- withBinding (lvVar x) (Forall [] uargTy) $ infer lt+    return $ Syntax' l (SLam x (Just argTy) lt') (UTyFun uargTy (lt' ^. sType))    -- If the type of the argument is not provided, create a fresh   -- unification variable for it and proceed.   SLam x Nothing lt -> do     argTy <- fresh-    resTy <- withBinding x (Forall [] argTy) $ infer lt-    return $ UTyFun argTy resTy+    lt' <- withBinding (lvVar x) (Forall [] argTy) $ infer lt+    return $ Syntax' l (SLam x Nothing lt') (UTyFun argTy (lt' ^. sType))    -- To infer the type of an application:   SApp f x -> do     -- Infer the type of the left-hand side and make sure it has a function type.-    fTy <- infer f-    (ty1, ty2) <- decomposeFunTy fTy+    f' <- infer f+    (ty1, ty2) <- decomposeFunTy (f' ^. sType)      -- Then check that the argument has the right type.-    check x ty1 `catchError` addLocToTypeErr x-    return ty2+    x' <- check x ty1 `catchError` addLocToTypeErr x+    return $ Syntax' l (SApp f' x') ty2    -- We can infer the type of a let whether a type has been provided for   -- the variable or not.-  SLet _ x Nothing t1 t2 -> do+  SLet r x Nothing t1 t2 -> do     xTy <- fresh-    uty <- withBinding x (Forall [] xTy) $ infer t1-    xTy =:= uty+    t1' <- withBinding (lvVar x) (Forall [] xTy) $ infer t1+    let uty = t1' ^. sType+    _ <- xTy =:= uty     upty <- generalize uty-    withBinding x upty $ infer t2-  SLet _ x (Just pty) t1 t2 -> do+    t2' <- withBinding (lvVar x) upty $ infer t2+    return $ Syntax' l (SLet r x Nothing t1' t2') (t2' ^. sType)+  SLet r x (Just pty) t1 t2 -> do     let upty = toU pty     -- If an explicit polytype has been provided, skolemize it and check     -- definition and body under an extended context.     uty <- skolemize upty-    resTy <- withBinding x upty $ do-      check t1 uty `catchError` addLocToTypeErr t1-      infer t2+    (t1', t2') <- withBinding (lvVar x) upty $ do+      (,)+        <$> check t1 uty+        `catchError` addLocToTypeErr t1+        <*> infer t2     -- Make sure no skolem variables have escaped.     ask >>= mapM_ noSkolems-    return resTy+    return $ Syntax' l (SLet r x (Just pty) t1' t2') (t2' ^. sType)   SDef {} -> throwError $ DefNotTopLevel l t   SBind mx c1 c2 -> do-    ty1 <- infer c1-    a <- decomposeCmdTy ty1-    ty2 <- maybe id (`withBinding` Forall [] a) mx $ infer c2-    _ <- decomposeCmdTy ty2-    return ty2+    c1' <- infer c1+    a <- decomposeCmdTy (c1' ^. sType)+    c2' <- maybe id ((`withBinding` Forall [] a) . lvVar) mx $ infer c2+    _ <- decomposeCmdTy (c2' ^. sType)+    return $ Syntax' l (SBind mx c1' c2') (c2' ^. sType)  where   noSkolems :: UPolytype -> Infer ()   noSkolems (Forall xs upty) = do@@ -433,11 +467,12 @@             upty'         ftyvs = tyvs `S.difference` S.fromList xs     unless (S.null ftyvs) $-      throwError $ EscapedSkolem l (head (S.toList ftyvs))+      throwError $+        EscapedSkolem l (head (S.toList ftyvs)) -addLocToTypeErr :: Syntax -> TypeErr -> Infer a+addLocToTypeErr :: Syntax' ty -> TypeErr -> Infer a addLocToTypeErr s te = case te of-  Mismatch NoLoc a b -> throwError $ Mismatch (sLoc s) a b+  Mismatch NoLoc a b -> throwError $ Mismatch (s ^. sLoc) a b   _ -> throwError te  -- | Decompose a type that is supposed to be a command type.@@ -445,7 +480,7 @@ decomposeCmdTy (UTyCmd a) = return a decomposeCmdTy ty = do   a <- fresh-  ty =:= UTyCmd a+  _ <- ty =:= UTyCmd a   return a  -- | Decompose a type that is supposed to be a function type.@@ -454,7 +489,7 @@ decomposeFunTy ty = do   ty1 <- fresh   ty2 <- fresh-  ty =:= UTyFun ty1 ty2+  _ <- ty =:= UTyFun ty1 ty2   return (ty1, ty2)  -- | Infer the type of a constant.@@ -468,31 +503,36 @@   Grab -> [tyQ| cmd text |]   Harvest -> [tyQ| cmd text |]   Place -> [tyQ| text -> cmd unit |]-  Give -> [tyQ| robot -> text -> cmd unit |]-  Install -> [tyQ| robot -> text -> cmd unit |]+  Give -> [tyQ| actor -> text -> cmd unit |]+  Equip -> [tyQ| text -> cmd unit |]+  Unequip -> [tyQ| text -> cmd unit |]   Make -> [tyQ| text -> cmd unit |]   Has -> [tyQ| text -> cmd bool |]-  Installed -> [tyQ| text -> cmd bool |]+  Equipped -> [tyQ| text -> cmd bool |]   Count -> [tyQ| text -> cmd int |]-  Reprogram -> [tyQ| robot -> {cmd a} -> cmd unit |]-  Build -> [tyQ| {cmd a} -> cmd robot |]+  Reprogram -> [tyQ| actor -> {cmd a} -> cmd unit |]+  Build -> [tyQ| {cmd a} -> cmd actor |]   Drill -> [tyQ| dir -> cmd unit |]   Salvage -> [tyQ| cmd unit |]   Say -> [tyQ| text -> cmd unit |]   Listen -> [tyQ| cmd text |]   Log -> [tyQ| text -> cmd unit |]-  View -> [tyQ| robot -> cmd unit |]+  View -> [tyQ| actor -> cmd unit |]   Appear -> [tyQ| text -> cmd unit |]   Create -> [tyQ| text -> cmd unit |]   Time -> [tyQ| cmd int |]   Whereami -> [tyQ| cmd (int * int) |]+  Heading -> [tyQ| cmd dir |]   Blocked -> [tyQ| cmd bool |]   Scan -> [tyQ| dir -> cmd (unit + text) |]-  Upload -> [tyQ| robot -> cmd unit |]+  Upload -> [tyQ| actor -> cmd unit |]   Ishere -> [tyQ| text -> cmd bool |]-  Self -> [tyQ| robot |]-  Parent -> [tyQ| robot |]-  Base -> [tyQ| robot |]+  Isempty -> [tyQ| cmd bool |]+  Self -> [tyQ| actor |]+  Parent -> [tyQ| actor |]+  Base -> [tyQ| actor |]+  Meet -> [tyQ| cmd (unit + actor) |]+  MeetAll -> [tyQ| (b -> actor -> cmd b) -> b -> cmd b |]   Whoami -> [tyQ| cmd text |]   Setname -> [tyQ| text -> cmd unit |]   Random -> [tyQ| int -> cmd int |]@@ -527,24 +567,27 @@   Concat -> [tyQ| text -> text -> text |]   Chars -> [tyQ| text -> int |]   Split -> [tyQ| int -> text -> (text * text) |]+  CharAt -> [tyQ| int -> text -> int |]+  ToChar -> [tyQ| int -> text |]   AppF -> [tyQ| (a -> b) -> a -> b |]   Swap -> [tyQ| text -> cmd text |]   Atomic -> [tyQ| cmd a -> cmd a |]-  Teleport -> [tyQ| robot -> (int * int) -> cmd unit |]-  As -> [tyQ| robot -> {cmd a} -> cmd a |]-  RobotNamed -> [tyQ| text -> cmd robot |]-  RobotNumbered -> [tyQ| int -> cmd robot |]+  Teleport -> [tyQ| actor -> (int * int) -> cmd unit |]+  As -> [tyQ| actor -> {cmd a} -> cmd a |]+  RobotNamed -> [tyQ| text -> cmd actor |]+  RobotNumbered -> [tyQ| int -> cmd actor |]   Knows -> [tyQ| text -> cmd bool |]  where   cmpBinT = [tyQ| a -> a -> bool |]   arithBinT = [tyQ| int -> int -> int |] --- | @check t ty@ checks that @t@ has type @ty@.-check :: Syntax -> UType -> Infer ()+-- | @check t ty@ checks that @t@ has type @ty@, returning a+--   type-annotated AST if so.+check :: Syntax -> UType -> Infer (Syntax' UType) check t ty = do-  ty' <- infer t-  _ <- ty =:= ty'-  return ()+  Syntax' l t' ty' <- infer t+  theTy <- ty =:= ty'+  return $ Syntax' l t' theTy  -- | Ensure a term is a valid argument to @atomic@.  Valid arguments --   may not contain @def@, @let@, or lambda. Any variables which are@@ -614,37 +657,37 @@   SDelay _ s1 -> analyzeAtomic locals s1   -- Bind is similarly simple except that we have to keep track of a local variable   -- bound in the RHS.-  SBind mx s1 s2 -> (+) <$> analyzeAtomic locals s1 <*> analyzeAtomic (maybe id S.insert mx locals) s2+  SBind mx s1 s2 -> (+) <$> analyzeAtomic locals s1 <*> analyzeAtomic (maybe id (S.insert . lvVar) mx locals) s2   -- Variables are allowed if bound locally, or if they have a simple type.   TVar x     | x `S.member` locals -> return 0     | otherwise -> do-      mxTy <- asks $ Ctx.lookup x-      case mxTy of-        -- If the variable is undefined, return 0 to indicate the-        -- atomic block is valid, because we'd rather have the error-        -- caught by the real name+type checking.-        Nothing -> return 0-        Just xTy -> do-          -- Use applyBindings to make sure that we apply as much-          -- information as unification has learned at this point.  In-          -- theory, continuing to typecheck other terms elsewhere in-          -- the program could give us further information about xTy,-          -- so we might have incomplete information at this point.-          -- However, since variables referenced in an atomic block-          -- must necessarily have simple types, it's unlikely this-          -- will really make a difference.  The alternative, more-          -- "correct" way to do this would be to simply emit some-          -- constraints at this point saying that xTy must be a-          -- simple type, and check later that the constraint holds,-          -- after performing complete type inference.  However, since-          -- the current approach is much simpler, we'll stick with-          -- this until such time as we have concrete examples showing-          -- that the more correct, complex way is necessary.-          xTy' <- applyBindings xTy-          if isSimpleUPolytype xTy'-            then return 0-            else throwError (InvalidAtomic l (NonSimpleVarType x xTy') t)+        mxTy <- asks $ Ctx.lookup x+        case mxTy of+          -- If the variable is undefined, return 0 to indicate the+          -- atomic block is valid, because we'd rather have the error+          -- caught by the real name+type checking.+          Nothing -> return 0+          Just xTy -> do+            -- Use applyBindings to make sure that we apply as much+            -- information as unification has learned at this point.  In+            -- theory, continuing to typecheck other terms elsewhere in+            -- the program could give us further information about xTy,+            -- so we might have incomplete information at this point.+            -- However, since variables referenced in an atomic block+            -- must necessarily have simple types, it's unlikely this+            -- will really make a difference.  The alternative, more+            -- "correct" way to do this would be to simply emit some+            -- constraints at this point saying that xTy must be a+            -- simple type, and check later that the constraint holds,+            -- after performing complete type inference.  However, since+            -- the current approach is much simpler, we'll stick with+            -- this until such time as we have concrete examples showing+            -- that the more correct, complex way is necessary.+            xTy' <- applyBindings xTy+            if isSimpleUPolytype xTy'+              then return 0+              else throwError (InvalidAtomic l (NonSimpleVarType x xTy') t)   -- No lambda, `let` or `def` allowed!   SLam {} -> throwError (InvalidAtomic l AtomicDupingThing t)   SLet {} -> throwError (InvalidAtomic l AtomicDupingThing t)
src/Swarm/Language/Types.hs view
@@ -29,7 +29,7 @@   pattern TyText,   pattern TyDir,   pattern TyBool,-  pattern TyRobot,+  pattern TyActor,   pattern (:+:),   pattern (:*:),   pattern (:->:),@@ -46,7 +46,7 @@   pattern UTyText,   pattern UTyDir,   pattern UTyBool,-  pattern UTyRobot,+  pattern UTyActor,   pattern UTySum,   pattern UTyProd,   pattern UTyFun,@@ -67,12 +67,6 @@   TCtx,   UCtx, -  -- * Modules-  Module (..),-  TModule,-  UModule,-  trivMod,-   -- * The 'WithU' class   WithU (..), ) where@@ -111,8 +105,11 @@     BDir   | -- | Booleans.     BBool-  | -- | Robots.-    BRobot+  | -- | "Actors", i.e. anything that can do stuff. Internally, these+    --   are all just "robots", but we give them a more generic+    --   in-game name because they could represent other things like+    --   aliens, animals, seeds, ...+    BActor   deriving (Eq, Ord, Show, Data, Generic, FromJSON, ToJSON)  -- | A "structure functor" encoding the shape of type expressions.@@ -214,34 +211,6 @@ type UPolytype = Poly UType  --------------------------------------------------------------- Modules----------------------------------------------------------------- | A module generally represents the result of performing type---   inference on a top-level expression, which in particular can---   contain definitions ('Swarm.Language.Syntax.TDef').  A module---   contains the overall type of the expression, as well as the---   context giving the types of any defined variables.-data Module s t = Module {moduleTy :: s, moduleCtx :: Ctx t}-  deriving (Show, Eq, Functor, Data, Generic, FromJSON, ToJSON)---- | A 'TModule' is the final result of the type inference process on---   an expression: we get a polytype for the expression, and a---   context of polytypes for the defined variables.-type TModule = Module Polytype Polytype---- | A 'UModule' represents the type of an expression at some---   intermediate stage during the type inference process.  We get a---   'UType' (/not/ a 'UPolytype') for the expression, which may---   contain some free unification or type variables, as well as a---   context of 'UPolytype's for any defined variables.-type UModule = Module UType UPolytype---- | The trivial module for a given @s@, with the empty context.-trivMod :: s -> Module s t-trivMod t = Module t empty-------------------------------------------------------------- -- WithU ------------------------------------------------------------ @@ -312,8 +281,8 @@ pattern TyBool :: Type pattern TyBool = Fix (TyBaseF BBool) -pattern TyRobot :: Type-pattern TyRobot = Fix (TyBaseF BRobot)+pattern TyActor :: Type+pattern TyActor = Fix (TyBaseF BActor)  infixr 5 :+: @@ -360,8 +329,8 @@ pattern UTyBool :: UType pattern UTyBool = UTerm (TyBaseF BBool) -pattern UTyRobot :: UType-pattern UTyRobot = UTerm (TyBaseF BRobot)+pattern UTyActor :: UType+pattern UTyActor = UTerm (TyBaseF BActor)  pattern UTySum :: UType -> UType -> UType pattern UTySum ty1 ty2 = UTerm (TySumF ty1 ty2)
+ src/Swarm/Language/Value.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GADTs #-}++-- |+-- Module      :  Swarm.Language.Value+-- Copyright   :  Brent Yorgey+-- Maintainer  :  byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Values and environments used for interpreting the Swarm language.+module Swarm.Language.Value (+  -- * Values+  Value (..),+  stripVResult,+  prettyValue,+  valueToTerm,++  -- * Environments+  Env,+) where++import Data.Aeson (FromJSON, ToJSON)+import Data.Bool (bool)+import Data.List (foldl')+import Data.Map qualified as M+import Data.Set qualified as S+import Data.Set.Lens (setOf)+import Data.Text (Text)+import GHC.Generics (Generic)+import Swarm.Language.Context+import Swarm.Language.Pretty (prettyText)+import Swarm.Language.Syntax+import Prelude++-- | A /value/ is a term that cannot (or does not) take any more+--   evaluation steps on its own.+data Value where+  -- | The unit value.+  VUnit :: Value+  -- | An integer.+  VInt :: Integer -> Value+  -- | Literal text.+  VText :: Text -> Value+  -- | A direction.+  VDir :: Direction -> Value+  -- | A boolean.+  VBool :: Bool -> Value+  -- | A reference to a robot.+  VRobot :: Int -> Value+  -- | An injection into a sum type.  False = left, True = right.+  VInj :: Bool -> Value -> Value+  -- | A pair.+  VPair :: Value -> Value -> Value+  -- | A /closure/, representing a lambda term along with an+  --   environment containing bindings for any free variables in the+  --   body of the lambda.+  VClo :: Var -> Term -> Env -> Value+  -- | An application of a constant to some value arguments,+  --   potentially waiting for more arguments.  If a constant+  --   application is fully saturated (as defined by its 'arity'),+  --   whether it is a value or not depends on whether or not it+  --   represents a command (as defined by 'isCmd').  If a command+  --   (e.g. 'Build'), it is a value, and awaits an 'Swarm.Game.CESK.FExec' frame+  --   which will cause it to execute.  Otherwise (e.g. 'If'), it is+  --   not a value, and will immediately reduce.+  VCApp :: Const -> [Value] -> Value+  -- | A definition, which does not take effect until executed.+  --   The @Bool@ indicates whether the definition is recursive.+  VDef :: Bool -> Var -> Term -> Env -> Value+  -- | The result of a command, consisting of the result of the+  --   command as well as an environment of bindings from 'TDef'+  --   commands.+  VResult :: Value -> Env -> Value+  -- | An unevaluated bind expression, waiting to be executed, of the+  --   form /i.e./ @c1 ; c2@ or @x <- c1; c2@.  We also store an 'Env'+  --   in which to interpret the commands.+  VBind :: Maybe Var -> Term -> Term -> Env -> Value+  -- | A (non-recursive) delayed term, along with its environment. If+  --   a term would otherwise be evaluated but we don't want it to be+  --   (/e.g./ as in the case of arguments to an 'if', or a recursive+  --   binding), we can stick a 'TDelay' on it, which turns it into a+  --   value.  Delayed terms won't be evaluated until 'Force' is+  --   applied to them.+  VDelay :: Term -> Env -> Value+  -- | A reference to a memory cell in the store.+  VRef :: Int -> Value+  deriving (Eq, Show, Generic, FromJSON, ToJSON)++-- | Ensure that a value is not wrapped in 'VResult'.+stripVResult :: Value -> Value+stripVResult (VResult v _) = stripVResult v+stripVResult v = v++-- | Pretty-print a value.+prettyValue :: Value -> Text+prettyValue = prettyText . valueToTerm++-- | Inject a value back into a term.+valueToTerm :: Value -> Term+valueToTerm VUnit = TUnit+valueToTerm (VInt n) = TInt n+valueToTerm (VText s) = TText s+valueToTerm (VDir d) = TDir d+valueToTerm (VBool b) = TBool b+valueToTerm (VRobot r) = TRobot r+valueToTerm (VInj s v) = TApp (TConst (bool Inl Inr s)) (valueToTerm v)+valueToTerm (VPair v1 v2) = TPair (valueToTerm v1) (valueToTerm v2)+valueToTerm (VClo x t e) =+  M.foldrWithKey+    (\y v -> TLet False y Nothing (valueToTerm v))+    (TLam x Nothing t)+    (M.restrictKeys (unCtx e) (S.delete x (setOf freeVarsV (Syntax' NoLoc t ()))))+valueToTerm (VCApp c vs) = foldl' TApp (TConst c) (reverse (map valueToTerm vs))+valueToTerm (VDef r x t _) = TDef r x Nothing t+valueToTerm (VResult v _) = valueToTerm v+valueToTerm (VBind mx c1 c2 _) = TBind mx c1 c2+valueToTerm (VDelay t _) = TDelay SimpleDelay t+valueToTerm (VRef n) = TRef n++-- | An environment is a mapping from variable names to values.+type Env = Ctx Value
src/Swarm/TUI/Attr.hs view
@@ -15,7 +15,7 @@ -- and not those used in the world map, to avoid abusing attributes. -- For example using the robot attribute to highlight some text. ----- The few attributes that we use for drawing the logo are an exeption.+-- The few attributes that we use for drawing the logo are an exception. module Swarm.TUI.Attr (   swarmAttrMap,   worldAttributes,@@ -40,7 +40,9 @@   infoAttr,   boldAttr,   dimAttr,+  magentaAttr,   cyanAttr,+  lightCyanAttr,   yellowAttr,   blueAttr,   greenAttr,@@ -83,6 +85,8 @@          , (blueAttr, fg V.blue)          , (yellowAttr, fg V.yellow)          , (cyanAttr, fg V.cyan)+         , (lightCyanAttr, fg (V.rgbColor @Int 200 255 255))+         , (magentaAttr, fg V.magenta)          , -- Default attribute            (defAttr, V.defAttr)          ]@@ -161,12 +165,14 @@ defAttr = attrName "def"  -- | Some basic colors used in TUI.-redAttr, greenAttr, blueAttr, yellowAttr, cyanAttr :: AttrName+redAttr, greenAttr, blueAttr, yellowAttr, cyanAttr, lightCyanAttr, magentaAttr :: AttrName redAttr = attrName "red" greenAttr = attrName "green" blueAttr = attrName "blue" yellowAttr = attrName "yellow" cyanAttr = attrName "cyan"+lightCyanAttr = attrName "lightCyan"+magentaAttr = attrName "magenta"  instance ToJSON AttrName where   toJSON = toJSON . head . attrNameComponents
src/Swarm/TUI/Controller.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE QuasiQuotes #-}  -- | -- Module      :  Swarm.TUI.Controller@@ -38,7 +38,8 @@   handleInfoPanelEvent, ) where -import Brick hiding (Direction)+import Brick hiding (Direction, Location)+import Brick qualified import Brick.Focus import Brick.Widgets.Dialog import Brick.Widgets.Edit (handleEditorEvent)@@ -49,10 +50,12 @@ import Control.Lens import Control.Lens.Extras (is) import Control.Monad.Except+import Control.Monad.Extra (whenJust) import Control.Monad.State import Data.Bits import Data.Either (isRight)-import Data.Int (Int64)+import Data.Foldable (toList)+import Data.Int (Int32) import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.Map qualified as M@@ -61,48 +64,50 @@ import Data.Text qualified as T import Data.Text.IO qualified as T import Data.Time (getZonedTime)+import Data.Vector qualified as V import Graphics.Vty qualified as V import Linear import Swarm.Game.CESK (cancel, emptyStore, initMachine) import Swarm.Game.Entity hiding (empty) import Swarm.Game.Robot+import Swarm.Game.Scenario.Objective.Presentation.Model+import Swarm.Game.Scenario.Objective.Presentation.Render qualified as GR import Swarm.Game.ScenarioInfo import Swarm.Game.State import Swarm.Game.Step (gameTick)-import Swarm.Game.Value (Value (VUnit), prettyValue) import Swarm.Game.World qualified as W import Swarm.Language.Capability (Capability (CMake)) import Swarm.Language.Context+import Swarm.Language.Module import Swarm.Language.Parse (reservedWords) import Swarm.Language.Pipeline+import Swarm.Language.Pipeline.QQ (tmQ) import Swarm.Language.Pretty import Swarm.Language.Requirement qualified as R import Swarm.Language.Syntax import Swarm.Language.Typed (Typed (..)) import Swarm.Language.Types+import Swarm.Language.Value (Value (VUnit), prettyValue, stripVResult)+import Swarm.TUI.Controller.Util import Swarm.TUI.Inventory.Sorting (cycleSortDirection, cycleSortOrder) import Swarm.TUI.List import Swarm.TUI.Model+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Achievement.Persistence+import Swarm.TUI.Model.Name+import Swarm.TUI.Model.Repl+import Swarm.TUI.Model.StateUpdate+import Swarm.TUI.Model.UI import Swarm.TUI.View (generateModal) import Swarm.Util hiding ((<<.=))+import Swarm.Util.Location import Swarm.Version (NewReleaseFailure (..)) import System.Clock+import System.FilePath (splitDirectories) import Witch (into) --- | Pattern synonyms to simplify brick event handler-pattern Key :: V.Key -> BrickEvent n e-pattern Key k = VtyEvent (V.EvKey k [])--pattern CharKey, ControlKey, MetaKey :: Char -> BrickEvent n e-pattern CharKey c = VtyEvent (V.EvKey (V.KChar c) [])-pattern ControlKey c = VtyEvent (V.EvKey (V.KChar c) [V.MCtrl])-pattern MetaKey c = VtyEvent (V.EvKey (V.KChar c) [V.MMeta])--pattern EscapeKey :: BrickEvent n e-pattern EscapeKey = VtyEvent (V.EvKey V.KEsc [])--pattern FKey :: Int -> BrickEvent n e-pattern FKey c = VtyEvent (V.EvKey (V.KFun c) [])+tutorialsDirname :: FilePath+tutorialsDirname = "Tutorials"  -- | The top-level event handler for the TUI. handleEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()@@ -111,8 +116,8 @@   AppEvent (UpstreamVersion ev) -> do     let logReleaseEvent l e = runtimeState . eventLog %= logEvent l ("Release", -7) (T.pack $ show e)     case ev of-      Left e@(FailedReleaseQuery _e) -> logReleaseEvent ErrorTrace e-      Left e -> logReleaseEvent Said e+      Left e@(FailedReleaseQuery _e) -> logReleaseEvent (ErrorTrace Error) e+      Left e -> logReleaseEvent (ErrorTrace Warning) e       Right _ -> pure ()     runtimeState . upstreamRelease .= ev   e -> do@@ -128,6 +133,7 @@           MainMenu l -> handleMainMenuEvent l           NewGameMenu l -> handleNewGameMenuEvent l           MessagesMenu -> handleMainMessagesEvent+          AchievementsMenu l -> handleMainAchievementsEvent l           AboutMenu -> pressAnyKey (MainMenu (mainMenu About))  -- | The event handler for the main menu.@@ -149,7 +155,7 @@           let tutorialCollection = getTutorials ss               topMenu =                 BL.listFindBy-                  ((== "Tutorials") . scenarioItemName)+                  ((== tutorialsDirname) . T.unpack . scenarioItemName)                   (mkScenarioList cheat ss)               tutorialMenu = mkScenarioList cheat tutorialCollection               menuStack = NE.fromList [tutorialMenu, topMenu]@@ -162,20 +168,23 @@                   _ -> error "No first tutorial found!"                 _ -> error "No first tutorial found!"           startGame firstTutorial Nothing+        Achievements -> uiState . uiMenu .= AchievementsMenu (BL.list AchievementList (V.fromList listAchievements) 1)         Messages -> do           runtimeState . eventLog . notificationsCount .= 0           uiState . uiMenu .= MessagesMenu-        About -> uiState . uiMenu .= AboutMenu+        About -> do+          uiState . uiMenu .= AboutMenu+          attainAchievement $ GlobalAchievement LookedAtAboutScreen         Quit -> halt   CharKey 'q' -> halt-  ControlKey 'q' -> halt+  ControlChar 'q' -> halt   VtyEvent ev -> do     menu' <- nestEventM' menu (handleListEvent ev)     uiState . uiMenu .= MainMenu menu'   _ -> continueWithoutRedraw  getTutorials :: ScenarioCollection -> ScenarioCollection-getTutorials sc = case M.lookup "Tutorials" (scMap sc) of+getTutorials sc = case M.lookup tutorialsDirname (scMap sc) of   Just (SICollection _ c) -> c   _ -> error "No tutorials exist!" @@ -183,11 +192,26 @@ advanceMenu :: Menu -> Menu advanceMenu = _NewGameMenu . ix 0 %~ BL.listMoveDown +handleMainAchievementsEvent ::+  BL.List Name CategorizedAchievement ->+  BrickEvent Name AppEvent ->+  EventM Name AppState ()+handleMainAchievementsEvent l e = case e of+  Key V.KEsc -> returnToMainMenu+  CharKey 'q' -> returnToMainMenu+  ControlChar 'q' -> returnToMainMenu+  VtyEvent ev -> do+    l' <- nestEventM' l (handleListEvent ev)+    uiState . uiMenu .= AchievementsMenu l'+  _ -> continueWithoutRedraw+ where+  returnToMainMenu = uiState . uiMenu .= MainMenu (mainMenu Messages)+ handleMainMessagesEvent :: BrickEvent Name AppEvent -> EventM Name AppState () handleMainMessagesEvent = \case   Key V.KEsc -> returnToMainMenu   CharKey 'q' -> returnToMainMenu-  ControlKey 'q' -> returnToMainMenu+  ControlChar 'q' -> returnToMainMenu   _ -> return ()  where   returnToMainMenu = uiState . uiMenu .= MainMenu (mainMenu Messages)@@ -203,7 +227,7 @@         uiState . uiMenu .= NewGameMenu (NE.cons (mkScenarioList cheat c) scenarioStack)   Key V.KEsc -> exitNewGameMenu scenarioStack   CharKey 'q' -> exitNewGameMenu scenarioStack-  ControlKey 'q' -> halt+  ControlChar 'q' -> halt   VtyEvent ev -> do     menu' <- nestEventM' curMenu (handleListEvent ev)     uiState . uiMenu .= NewGameMenu (menu' :| rest)@@ -211,7 +235,8 @@  exitNewGameMenu :: NonEmpty (BL.List Name ScenarioItem) -> EventM Name AppState () exitNewGameMenu stk = do-  uiState . uiMenu+  uiState+    . uiMenu     .= case snd (NE.uncons stk) of       Nothing -> MainMenu (mainMenu NewGame)       Just stk' -> NewGameMenu stk'@@ -231,19 +256,22 @@       | s ^. gameState . paused -> continueWithoutRedraw       | otherwise -> runFrameUI     -- ctrl-q works everywhere-    ControlKey 'q' ->+    ControlChar 'q' ->       case s ^. gameState . winCondition of-        Won _ -> toggleModal WinModal+        WinConditions (Won _) _ -> toggleModal WinModal+        WinConditions (Unwinnable _) _ -> toggleModal LoseModal         _ -> toggleModal QuitModal     VtyEvent (V.EvResize _ _) -> invalidateCacheEntry WorldCache     Key V.KEsc       | isJust (s ^. uiState . uiError) -> uiState . uiError .= Nothing       | Just m <- s ^. uiState . uiModal -> do-        safeAutoUnpause-        uiState . uiModal .= Nothing-        -- message modal is not autopaused, so update notifications when leaving it-        when (m ^. modalType == MessagesModal) $ do-          gameState . lastSeenMessageTime .= s ^. gameState . ticks+          safeAutoUnpause+          uiState . uiModal .= Nothing+          -- message modal is not autopaused, so update notifications when leaving it+          case m ^. modalType of+            MessagesModal -> do+              gameState . lastSeenMessageTime .= s ^. gameState . ticks+            _ -> return ()     FKey 1 -> toggleModal HelpModal     FKey 2 -> toggleModal RobotsModal     FKey 3 | not (null (s ^. gameState . availableRecipes . notificationsContent)) -> do@@ -255,10 +283,11 @@     FKey 5 | not (null (s ^. gameState . messageNotifications . notificationsContent)) -> do       toggleModal MessagesModal       gameState . lastSeenMessageTime .= s ^. gameState . ticks-    ControlKey 'g' -> case s ^. uiState . uiGoal of-      Just g | g /= [] -> toggleModal (GoalModal g)-      _ -> continueWithoutRedraw-    MetaKey 'h' -> do+    ControlChar 'g' ->+      if hasAnythingToShow $ s ^. uiState . uiGoal . goalsContent+        then toggleModal GoalModal+        else continueWithoutRedraw+    MetaChar 'h' -> do       t <- liftIO $ getTime Monotonic       h <- use $ uiState . uiHideRobotsUntil       if h >= t@@ -269,54 +298,57 @@           uiState . uiHideRobotsUntil .= t + TimeSpec 2 0           invalidateCacheEntry WorldCache     -- pausing and stepping-    ControlKey 'p' | isRunning -> safeTogglePause-    ControlKey 'o' | isRunning -> do+    ControlChar 'p' | isRunning -> safeTogglePause+    ControlChar 'o' | isRunning -> do       gameState . runStatus .= ManualPause       runGameTickUI     -- speed controls-    ControlKey 'x' | isRunning -> modify $ adjustTPS (+)-    ControlKey 'z' | isRunning -> modify $ adjustTPS (-)+    ControlChar 'x' | isRunning -> modify $ adjustTPS (+)+    ControlChar 'z' | isRunning -> modify $ adjustTPS (-)     -- special keys that work on all panels-    MetaKey 'w' -> setFocus WorldPanel-    MetaKey 'e' -> setFocus RobotPanel-    MetaKey 'r' -> setFocus REPLPanel-    MetaKey 't' -> setFocus InfoPanel+    MetaChar 'w' -> setFocus WorldPanel+    MetaChar 'e' -> setFocus RobotPanel+    MetaChar 'r' -> setFocus REPLPanel+    MetaChar 't' -> setFocus InfoPanel     -- pass keys on to modal event handler if a modal is open     VtyEvent vev       | isJust (s ^. uiState . uiModal) -> handleModalEvent vev     -- toggle creative mode if in "cheat mode"-    ControlKey 'v'+    ControlChar 'v'       | s ^. uiState . uiCheatMode -> gameState . creativeMode %= not     MouseDown n _ _ mouseLoc ->       case n of-        WorldPanel -> do+        FocusablePanel WorldPanel -> do           mouseCoordsM <- Brick.zoom gameState (mouseLocToWorldCoords mouseLoc)           uiState . uiWorldCursor .= mouseCoordsM-        REPLInput -> do-          setFocus REPLPanel-          handleREPLEvent ev+        REPLInput -> handleREPLEvent ev         _ -> continueWithoutRedraw     MouseUp n _ _mouseLoc -> do       case n of         InventoryListItem pos -> uiState . uiInventory . traverse . _2 %= BL.listMoveTo pos         _ -> return ()-      setFocus $ case n of+      flip whenJust setFocus $ case n of         -- Adapt click event origin to their right panel.         -- For the REPL and the World view, using 'Brick.Widgets.Core.clickable' correctly set the origin.         -- However this does not seems to work for the robot and info panel.         -- Thus we force the destination focus here.-        InventoryList -> RobotPanel-        InventoryListItem _ -> RobotPanel-        InfoViewport -> InfoPanel-        _ -> n+        InventoryList -> Just RobotPanel+        InventoryListItem _ -> Just RobotPanel+        InfoViewport -> Just InfoPanel+        REPLInput -> Just REPLPanel+        _ -> Nothing+      case n of+        FocusablePanel x -> setFocus x+        _ -> return ()     -- dispatch any other events to the focused panel handler     _ev -> do       fring <- use $ uiState . uiFocusRing       case focusGetCurrent fring of-        Just REPLPanel -> handleREPLEvent ev-        Just WorldPanel -> handleWorldEvent ev-        Just RobotPanel -> handleRobotPanelEvent ev-        Just InfoPanel -> handleInfoPanelEvent infoScroll ev+        Just (FocusablePanel x) -> ($ ev) $ case x of+          REPLPanel -> handleREPLEvent+          WorldPanel -> handleWorldEvent+          RobotPanel -> handleRobotPanelEvent+          InfoPanel -> handleInfoPanelEvent infoScroll         _ -> continueWithoutRedraw  mouseLocToWorldCoords :: Brick.Location -> EventM Name GameState (Maybe W.Coords)@@ -332,9 +364,6 @@           my = fst mouseLoc' + snd regionStart        in pure . Just $ W.Coords (mx, my) -setFocus :: Name -> EventM Name AppState ()-setFocus name = uiState . uiFocusRing %= focusSetCurrent name- -- | Set the game to Running if it was (auto) paused otherwise to paused. -- -- Also resets the last frame time to now. If we are pausing, it@@ -360,39 +389,39 @@ toggleModal mt = do   modal <- use $ uiState . uiModal   case modal of-    Nothing -> do-      newModal <- gets $ flip generateModal mt-      ensurePause-      uiState . uiModal ?= newModal+    Nothing -> openModal mt     Just _ -> uiState . uiModal .= Nothing >> safeAutoUnpause- where-  -- Set the game to AutoPause if needed-  ensurePause = do-    pause <- use $ gameState . paused-    unless (pause || isRunningModal mt) $ do-      gameState . runStatus .= AutoPause --- | The running modals do not autopause the game.-isRunningModal :: ModalType -> Bool-isRunningModal mt = mt `elem` [RobotsModal, MessagesModal]- handleModalEvent :: V.Event -> EventM Name AppState () handleModalEvent = \case   V.EvKey V.KEnter [] -> do     mdialog <- preuse $ uiState . uiModal . _Just . modalDialog     toggleModal QuitModal-    case dialogSelection <$> mdialog of-      Just (Just QuitButton) -> quitGame-      Just (Just KeepPlayingButton) -> toggleModal KeepPlayingModal-      Just (Just (StartOverButton currentSeed siPair)) -> restartGame currentSeed siPair-      Just (Just (NextButton siPair)) -> saveScenarioInfoOnQuit >> startGame siPair Nothing+    case dialogSelection =<< mdialog of+      Just (Button QuitButton, _) -> quitGame+      Just (Button KeepPlayingButton, _) -> toggleModal KeepPlayingModal+      Just (Button StartOverButton, StartOver currentSeed siPair) -> restartGame currentSeed siPair+      Just (Button NextButton, Next siPair) -> saveScenarioInfoOnQuit >> startGame siPair Nothing       _ -> return ()   ev -> do     Brick.zoom (uiState . uiModal . _Just . modalDialog) (handleDialogEvent ev)     modal <- preuse $ uiState . uiModal . _Just . modalType     case modal of-      Just _ -> handleInfoPanelEvent modalScroll (VtyEvent ev)+      Just GoalModal -> case ev of+        V.EvKey (V.KChar '\t') [] -> uiState . uiGoal . focus %= focusNext+        _ -> do+          focused <- use $ uiState . uiGoal . focus+          case focusGetCurrent focused of+            Just (GoalWidgets w) -> case w of+              ObjectivesList -> do+                lw <- use $ uiState . uiGoal . listWidget+                newList <- refreshList lw+                uiState . uiGoal . listWidget .= newList+              GoalSummary -> handleInfoPanelEvent modalScroll (VtyEvent ev)+            _ -> handleInfoPanelEvent modalScroll (VtyEvent ev)       _ -> return ()+   where+    refreshList lw = nestEventM' lw $ handleListEventWithSeparators ev isHeader  -- | Write the @ScenarioInfo@ out to disk when exiting a game. saveScenarioInfoOnQuit :: (MonadIO m, MonadState AppState m) => m ()@@ -408,7 +437,10 @@         gs <- use $ gameState . scenarios         p <- liftIO $ normalizeScenarioPath gs p'         t <- liftIO getZonedTime-        won <- isJust <$> preuse (gameState . winCondition . _Won)+        wc <- use $ gameState . winCondition+        let won = case wc of+              WinConditions (Won _) _ -> True+              _ -> False         ts <- use $ gameState . ticks         let currentScenarioInfo :: Traversal' AppState ScenarioInfo             currentScenarioInfo = gameState . scenarios . scenarioItemByPath p . _SISingle . _2@@ -416,7 +448,14 @@         status <- preuse currentScenarioInfo         case status of           Nothing -> return ()-          Just si -> liftIO $ saveScenarioInfo p si+          Just si -> do+            let segments = splitDirectories p+            case segments of+              firstDir : _ -> do+                when (won && firstDir == tutorialsDirname) $+                  attainAchievement' t (Just p) (GlobalAchievement CompletedSingleTutorial)+              _ -> return ()+            liftIO $ saveScenarioInfo p si          -- See what scenario is currently focused in the menu.  Depending on how the         -- previous scenario ended (via quit vs. via win), it might be the same as@@ -556,12 +595,30 @@   gs' <- liftIO (Fused.runM (Fused.execState gs f))   gameState .= gs' +updateAchievements :: EventM Name AppState ()+updateAchievements = do+  -- Merge the in-game achievements with the master list in UIState+  achievementsFromGame <- use $ gameState . gameAchievements+  let wrappedGameAchievements = M.mapKeys GameplayAchievement achievementsFromGame++  oldMasterAchievementsList <- use $ uiState . uiAchievements+  uiState . uiAchievements %= M.unionWith (<>) wrappedGameAchievements++  -- Don't save to disk unless there was a change in the attainment list.+  let incrementalAchievements = wrappedGameAchievements `M.difference` oldMasterAchievementsList+  unless (null incrementalAchievements) $ do+    -- TODO: #916 This is where new achievements would be displayed in a popup+    newAchievements <- use $ uiState . uiAchievements+    liftIO $ saveAchievementsInfo $ M.elems newAchievements+ -- | Run the game for a single tick (/without/ updating the UI). --   Every robot is given a certain amount of maximum computation to --   perform a single world action (like moving, turning, grabbing, --   etc.). runGameTick :: EventM Name AppState ()-runGameTick = zoomGameState gameTick+runGameTick = do+  zoomGameState gameTick+  updateAchievements  -- | Update the UI.  This function is used after running the --   game for some number of ticks.@@ -605,7 +662,7 @@     -- result as a REPL output, with its type, and reset the replStatus.     REPLWorking (Typed (Just v) pty reqs) -> do       let finalType = stripCmd pty-      let val = Typed v finalType reqs+      let val = Typed (stripVResult v) finalType reqs       itIx <- use (gameState . replNextValueIndex)       let itName = fromString $ "it" ++ show itIx       let out = T.intercalate " " [itName, ":", prettyText finalType, "=", into (prettyValue v)]@@ -618,19 +675,29 @@     -- Otherwise, do nothing.     _ -> pure False -  -- If the focused robot's log has been updated, attempt to-  -- automatically switch to it and scroll all the way down so the new-  -- message can be seen.+  -- If the focused robot's log has been updated and the UI focus+  -- isn't currently on the inventory or info panels, attempt to+  -- automatically switch to the logger and scroll all the way down so+  -- the new message can be seen.   uiState . uiScrollToEnd .= False   logUpdated <- do-    case maybe False (view robotLogUpdated) fr of+    -- If the inventory or info panels are currently focused, it would+    -- be rude to update them right under the user's nose, so consider+    -- them "sticky".  They will be updated as soon as the player moves+    -- the focus away.+    fring <- use $ uiState . uiFocusRing+    let sticky = focusGetCurrent fring `elem` map (Just . FocusablePanel) [RobotPanel, InfoPanel]++    -- Check if the robot log was updated and we are allowed to change+    -- the inventory+info panels.+    case maybe False (view robotLogUpdated) fr && not sticky of       False -> pure False       True -> do         -- Reset the log updated flag         zoomGameState clearFocusedRobotLogUpdated -        -- Find and focus an installed "logger" device in the inventory list.-        let isLogger (InstalledEntry e) = e ^. entityName == "logger"+        -- Find and focus an equipped "logger" device in the inventory list.+        let isLogger (EquippedEntry e) = e ^. entityName == "logger"             isLogger _ = False             focusLogger = BL.listFindBy isLogger @@ -658,35 +725,89 @@         oldBotMore <- uiState . uiMoreInfoBot <<.= botMore         return $ oldTopMore /= topMore || oldBotMore /= botMore -  -- Decide whether we need to update the current goal text, and pop+  goalOrWinUpdated <- doGoalUpdates++  let redraw = g ^. needsRedraw || inventoryUpdated || replUpdated || logUpdated || infoPanelUpdated || goalOrWinUpdated+  pure redraw++-- | Either pops up the updated Goals modal+-- or pops up the Congratulations (Win) modal, or pops+-- up the Condolences (Lose) modal.+-- The Win modal will take precendence if the player+-- has met the necessary conditions to win the game.+--+-- If the player chooses to "Keep Playing" from the Win modal, the+-- updated Goals will then immediately appear.+-- This is desirable for:+-- * feedback as to the final goal the player accomplished,+-- * as a summary of all of the goals of the game+-- * shows the player more "optional" goals they can continue to pursue+doGoalUpdates :: EventM Name AppState Bool+doGoalUpdates = do+  curGoal <- use (uiState . uiGoal . goalsContent)+  isCheating <- use (uiState . uiCheatMode)+  curWinCondition <- use (gameState . winCondition)+  announcementsSeq <- use (gameState . announcementQueue)+  let announcementsList = toList announcementsSeq++  -- Decide whether we need to update the current goal text and pop   -- up a modal dialog.-  curGoal <- use (uiState . uiGoal)-  newGoal <--    preuse (gameState . winCondition . _WinConditions . _NonEmpty . _1 . objectiveGoal)+  case curWinCondition of+    NoWinCondition -> return False+    WinConditions (Unwinnable False) x -> do+      -- This clears the "flag" that the Lose dialog needs to pop up+      gameState . winCondition .= WinConditions (Unwinnable True) x+      openModal LoseModal -  let goalUpdated = curGoal /= newGoal-  when goalUpdated $ do-    uiState . uiGoal .= newGoal-    case newGoal of-      Just goal | goal /= [] -> do-        toggleModal (GoalModal goal)-      _ -> return ()+      uiState . uiMenu %= advanceMenu+      return True+    WinConditions (Won False) x -> do+      -- This clears the "flag" that the Win dialog needs to pop up+      gameState . winCondition .= WinConditions (Won True) x+      openModal WinModal -  -- Decide whether to show a pop-up modal congratulating the user on-  -- successfully completing the current challenge.-  winModalUpdated <- do-    w <- use (gameState . winCondition)-    case w of-      Won False -> do-        gameState . winCondition .= Won True-        toggleModal WinModal-        uiState . uiMenu %= advanceMenu-        return True-      _ -> return False+      uiState . uiMenu %= advanceMenu+      return True+    WinConditions _ oc -> do+      let newGoalTracking = GoalTracking announcementsList $ constructGoalMap isCheating oc+          -- The "uiGoal" field is intialized with empty members, so we know that+          -- this will be the first time showing it if it will be nonempty after previously+          -- being empty.+          isFirstGoalDisplay = hasAnythingToShow newGoalTracking && not (hasAnythingToShow curGoal)+          goalWasUpdated = isFirstGoalDisplay || not (null announcementsList) -  let redraw = g ^. needsRedraw || inventoryUpdated || replUpdated || logUpdated || infoPanelUpdated || goalUpdated || winModalUpdated-  pure redraw+      -- Decide whether to show a pop-up modal congratulating the user on+      -- successfully completing the current challenge.+      when goalWasUpdated $ do+        let hasMultiple = hasMultipleGoals newGoalTracking+            defaultFocus =+              if hasMultiple+                then ObjectivesList+                else GoalSummary +            ring =+              focusRing $+                map GoalWidgets $+                  if hasMultiple+                    then listEnums+                    else [GoalSummary]++        -- The "uiGoal" field is necessary at least to "persist" the data that is needed+        -- if the player chooses to later "recall" the goals dialog with CTRL+g.+        uiState . uiGoal+          .= GoalDisplay+            newGoalTracking+            (GR.makeListWidget newGoalTracking)+            (focusSetCurrent (GoalWidgets defaultFocus) ring)++        -- This clears the "flag" that indicate that the goals dialog needs to be+        -- automatically popped up.+        gameState . announcementQueue .= mempty++        openModal GoalModal++      return goalWasUpdated+ -- | Make sure all tiles covering the visible part of the world are --   loaded. loadVisibleRegion :: EventM Name AppState ()@@ -719,19 +840,86 @@  -- | Handle a user input event for the REPL. handleREPLEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()-handleREPLEvent = \case-  ControlKey 'c' -> do+handleREPLEvent x = do+  s <- get+  let repl = s ^. uiState . uiREPL+      controlMode = repl ^. replControlMode+      uinput = repl ^. replPromptText+  case x of+    MetaChar 'p' ->+      onlyCreative $ do+        if T.null uinput+          then uiState . uiREPL . replControlMode %= cycleEnum+          else uiState . uiError ?= "Please clear the REPL first."+    _ -> case controlMode of+      Typing -> handleREPLEventTyping x+      Piloting -> handleREPLEventPiloting x++-- | Handle a user "piloting" input event for the REPL.+handleREPLEventPiloting :: BrickEvent Name AppEvent -> EventM Name AppState ()+handleREPLEventPiloting x = case x of+  Key V.KUp -> inputCmd "move"+  Key V.KDown -> inputCmd "turn back"+  Key V.KLeft -> inputCmd "turn left"+  Key V.KRight -> inputCmd "turn right"+  ShiftKey V.KUp -> inputCmd "turn north"+  ShiftKey V.KDown -> inputCmd "turn south"+  ShiftKey V.KLeft -> inputCmd "turn west"+  ShiftKey V.KRight -> inputCmd "turn east"+  Key V.KDel -> inputCmd "selfdestruct"+  CharKey 'g' -> inputCmd "grab"+  CharKey 'h' -> inputCmd "harvest"+  CharKey 'd' -> inputCmd "drill forward"+  CharKey 's' -> inputCmd "scan forward"+  CharKey 'b' -> inputCmd "blocked"+  CharKey 'u' -> inputCmd "upload base"+  _ -> inputCmd "noop"+ where+  inputCmd cmdText = do+    uiState . uiREPL %= setCmd (cmdText <> ";")+    modify validateREPLForm+    handleREPLEventTyping $ Key V.KEnter++  setCmd nt repl =+    repl+      & replPromptText .~ nt+      & replPromptType .~ CmdPrompt []++-- | Handle a user input event for the REPL.+handleREPLEventTyping :: BrickEvent Name AppEvent -> EventM Name AppState ()+handleREPLEventTyping = \case+  ControlChar 'c' -> do     gameState . baseRobot . machine %= cancel     uiState . uiREPL . replPromptType .= CmdPrompt []+    uiState . uiREPL . replPromptText .= ""   Key V.KEnter -> do     s <- get     let topCtx = topContext s         repl = s ^. uiState . uiREPL         uinput = repl ^. replPromptText -        startBaseProgram t@(ProcessedTerm _ (Module ty _) reqs _) =-          (gameState . replStatus .~ REPLWorking (Typed Nothing ty reqs))+        -- The player typed something at the REPL and hit Enter; this+        -- function takes the resulting ProcessedTerm (if the REPL+        -- input is valid) and sets up the base robot to run it.+        startBaseProgram t@(ProcessedTerm (Module tm _) reqs reqCtx) =+          -- Set the REPL status to Working+          (gameState . replStatus .~ REPLWorking (Typed Nothing (tm ^. sType) reqs))+            -- The `reqCtx` maps names of variables defined in the+            -- term (by `def` statements) to their requirements.+            -- E.g. if we had `def m = move end`, the reqCtx would+            -- record the fact that `m` needs the `move` capability.+            -- We simply add the entire `reqCtx` to the robot's+            -- context, so we can look up requirements if we later+            -- need to requirements-check an argument to `build` or+            -- `reprogram` at runtime.  See the discussion at+            -- https://github.com/swarm-game/swarm/pull/827 for more+            -- details.+            . (gameState . baseRobot . robotContext . defReqs <>~ reqCtx)+            -- Set up the robot's CESK machine to evaluate/execute the+            -- given term, being sure to initialize the CESK machine+            -- environment and store from the top-level context.             . (gameState . baseRobot . machine .~ initMachine t (topCtx ^. defVals) (topCtx ^. defStore))+            -- Finally, be sure to activate the base robot.             . (gameState %~ execState (activateRobot 0))      if not $ s ^. gameState . replWorking@@ -749,12 +937,12 @@             Just found               | T.null uinput -> uiState %= resetREPL "" (CmdPrompt [])               | otherwise -> do-                uiState %= resetREPL found (CmdPrompt [])-                modify validateREPLForm+                  uiState %= resetREPL found (CmdPrompt [])+                  modify validateREPLForm       else continueWithoutRedraw   Key V.KUp -> modify $ adjReplHistIndex Older   Key V.KDown -> modify $ adjReplHistIndex Newer-  ControlKey 'r' -> do+  ControlChar 'r' -> do     s <- get     let uinput = s ^. uiState . uiREPL . replPromptText     case s ^. uiState . uiREPL . replPromptType of@@ -773,7 +961,7 @@       CmdPrompt {} -> continueWithoutRedraw       SearchPrompt _ ->         uiState %= resetREPL "" (CmdPrompt [])-  ControlKey 'd' -> do+  ControlChar 'd' -> do     text <- use $ uiState . uiREPL . replPromptText     if text == T.empty       then toggleModal QuitModal@@ -816,12 +1004,12 @@     -- the CmdPrompt, so when Tab is pressed again, this case then gets executed and     -- repopulates them.     | otherwise -> case candidateMatches of-      [] -> setCmd t []-      [m] -> setCmd (completeWith m) []-      -- Perform completion with the first candidate, then populate the list-      -- of all candidates with the current completion moved to the back-      -- of the queue.-      (m : ms) -> setCmd (completeWith m) (ms ++ [m])+        [] -> setCmd t []+        [m] -> setCmd (completeWith m) []+        -- Perform completion with the first candidate, then populate the list+        -- of all candidates with the current completion moved to the back+        -- of the queue.+        (m : ms) -> setCmd (completeWith m) (ms ++ [m])  where   -- checks the "parity" of the number of quotes. If odd, then there is an open quote.   hasOpenQuotes = (== 1) . (`mod` 2) . T.count "\""@@ -857,17 +1045,17 @@   case replPrompt of     CmdPrompt _       | T.null uinput ->-        let theType = s ^. gameState . replStatus . replActiveType-         in s & uiState . uiREPL . replType .~ theType+          let theType = s ^. gameState . replStatus . replActiveType+           in s & uiState . uiREPL . replType .~ theType     CmdPrompt _       | otherwise ->-        let result = processTerm' (topCtx ^. defTypes) (topCtx ^. defReqs) uinput-            theType = case result of-              Right (Just (ProcessedTerm _ (Module ty _) _ _)) -> Just ty-              _ -> Nothing-         in s-              & uiState . uiREPL . replValid .~ isRight result-              & uiState . uiREPL . replType .~ theType+          let result = processTerm' (topCtx ^. defTypes) (topCtx ^. defReqs) uinput+              theType = case result of+                Right (Just (ProcessedTerm (Module tm _) _ _)) -> Just (tm ^. sType)+                _ -> Nothing+           in s+                & uiState . uiREPL . replValid .~ isRight result+                & uiState . uiREPL . replType .~ theType     SearchPrompt _ -> s  where   uinput = s ^. uiState . uiREPL . replPromptText@@ -902,14 +1090,19 @@ -- World events ------------------------------------------------------------ -worldScrollDist :: Int64+worldScrollDist :: Int32 worldScrollDist = 8 +onlyCreative :: MonadState AppState m => m () -> m ()+onlyCreative a = do+  c <- use $ gameState . creativeMode+  when c a+ -- | Handle a user input event in the world view panel. handleWorldEvent :: BrickEvent Name AppEvent -> EventM Name AppState () -- scrolling the world view in Creative mode handleWorldEvent = \case-  Key k | k `elem` moveKeys -> onlyCreative $ scrollView (^+^ (worldScrollDist *^ keyToDir k))+  Key k | k `elem` moveKeys -> onlyCreative $ scrollView (.+^ (worldScrollDist *^ keyToDir k))   CharKey 'c' -> do     invalidateCacheEntry WorldCache     gameState . viewCenterRule .= VCRobot 0@@ -918,9 +1111,6 @@   -- Fall-through case: don't do anything.   _ -> continueWithoutRedraw  where-  onlyCreative a = do-    c <- use $ gameState . creativeMode-    when c a   moveKeys =     [ V.KUp     , V.KDown@@ -933,7 +1123,7 @@     ]  -- | Manually scroll the world view.-scrollView :: (V2 Int64 -> V2 Int64) -> EventM Name AppState ()+scrollView :: (Location -> Location) -> EventM Name AppState () scrollView update = do   -- Manually invalidate the 'WorldCache' instead of just setting   -- 'needsRedraw'.  I don't quite understand why the latter doesn't@@ -943,7 +1133,7 @@   gameState %= modifyViewCenter update  -- | Convert a directional key into a direction.-keyToDir :: V.Key -> V2 Int64+keyToDir :: V.Key -> Heading keyToDir V.KUp = north keyToDir V.KDown = south keyToDir V.KRight = east@@ -952,7 +1142,7 @@ keyToDir (V.KChar 'j') = south keyToDir (V.KChar 'k') = north keyToDir (V.KChar 'l') = east-keyToDir _ = V2 0 0+keyToDir _ = zero  -- | Adjust the ticks per second speed. adjustTPS :: (Int -> Int -> Int) -> AppState -> AppState@@ -994,17 +1184,15 @@ makeEntity :: Entity -> EventM Name AppState () makeEntity e = do   s <- get-  let mkTy = PolyUnit-      mkReq = R.singletonCap CMake-      mkProg = TApp (TConst Make) (TText (e ^. entityName))-      mkPT = ProcessedTerm mkProg (Module mkTy empty) mkReq empty+  let name = e ^. entityName+      mkPT = [tmQ| make $str:name |]       topStore =         fromMaybe emptyStore $           s ^? gameState . baseRobot . robotContext . defStore    case isActive <$> (s ^? gameState . baseRobot) of     Just False -> do-      gameState . replStatus .= REPLWorking (Typed Nothing mkTy mkReq)+      gameState . replStatus .= REPLWorking (Typed Nothing PolyUnit (R.singletonCap CMake))       gameState . baseRobot . machine .= initMachine mkPT empty topStore       gameState %= execState (activateRobot 0)     _ -> continueWithoutRedraw
+ src/Swarm/TUI/Controller/Util.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE PatternSynonyms #-}++module Swarm.TUI.Controller.Util where++import Brick hiding (Direction)+import Brick.Focus+import Control.Lens+import Control.Monad (unless)+import Graphics.Vty qualified as V+import Swarm.Game.State+import Swarm.TUI.Model+import Swarm.TUI.Model.UI+import Swarm.TUI.View.Util (generateModal)++-- | Pattern synonyms to simplify brick event handler+pattern Key :: V.Key -> BrickEvent n e+pattern Key k = VtyEvent (V.EvKey k [])++pattern CharKey, ControlChar, MetaChar :: Char -> BrickEvent n e+pattern CharKey c = VtyEvent (V.EvKey (V.KChar c) [])+pattern ControlChar c = VtyEvent (V.EvKey (V.KChar c) [V.MCtrl])+pattern MetaChar c = VtyEvent (V.EvKey (V.KChar c) [V.MMeta])++pattern ShiftKey :: V.Key -> BrickEvent n e+pattern ShiftKey k = VtyEvent (V.EvKey k [V.MShift])++pattern EscapeKey :: BrickEvent n e+pattern EscapeKey = VtyEvent (V.EvKey V.KEsc [])++pattern FKey :: Int -> BrickEvent n e+pattern FKey c = VtyEvent (V.EvKey (V.KFun c) [])++openModal :: ModalType -> EventM Name AppState ()+openModal mt = do+  newModal <- gets $ flip generateModal mt+  ensurePause+  uiState . uiModal ?= newModal+ where+  -- Set the game to AutoPause if needed+  ensurePause = do+    pause <- use $ gameState . paused+    unless (pause || isRunningModal mt) $ do+      gameState . runStatus .= AutoPause++-- | The running modals do not autopause the game.+isRunningModal :: ModalType -> Bool+isRunningModal = \case+  RobotsModal -> True+  MessagesModal -> True+  _ -> False++setFocus :: FocusablePanel -> EventM Name AppState ()+setFocus name = uiState . uiFocusRing %= focusSetCurrent (FocusablePanel name)
src/Swarm/TUI/Model.hs view
@@ -16,11 +16,13 @@   -- * Custom UI label types   -- $uilabel   AppEvent (..),+  FocusablePanel (..),   Name (..),    -- * Menus and dialogs   ModalType (..),-  ButtonSelection (..),+  Button (..),+  ButtonAction (..),   Modal (..),   modalType,   modalDialog,@@ -59,60 +61,21 @@   InventoryListEntry (..),   _Separator,   _InventoryEntry,-  _InstalledEntry,--  -- ** UI Model-  UIState,-  uiMenu,-  uiPlaying,-  uiCheatMode,-  uiFocusRing,-  uiWorldCursor,-  uiREPL,-  uiInventory,-  uiInventorySort,-  uiMoreInfoTop,-  uiMoreInfoBot,-  uiScrollToEnd,-  uiError,-  uiModal,-  uiGoal,-  lgTicksPerSecond,-  lastFrameTime,-  accumulatedTime,-  tickCount,-  frameCount,-  frameTickCount,-  lastInfoTime,-  uiShowFPS,-  uiShowZero,-  uiShowRobots,-  uiHideRobotsUntil,-  uiInventoryShouldUpdate,-  uiTPF,-  uiFPS,-  scenarioRef,-  appData,+  _EquippedEntry,    -- *** REPL Panel Model   REPLState,+  ReplControlMode (..),   replPromptType,   replPromptEditor,   replPromptText,   replValid,   replLast,   replType,+  replControlMode,   replHistory,   newREPLEditor, -  -- ** Initialization-  initFocusRing,-  defaultPrompt,-  initREPLState,-  initLgTicksPerSecond,-  initUIState,-  lastEntry,-   -- ** Updating   populateInventoryList,   infoScroll,@@ -126,19 +89,18 @@   logEvent,    -- * App state-  AppState,+  AppState (AppState),   gameState,   uiState,   runtimeState,    -- ** Initialization   AppOpts (..),-  initAppState,-  startGame,-  restartGame,-  scenarioToAppState,   Seed, +  -- *** Re-exported types used in options+  ColorMode (..),+   -- ** Utility   topContext,   focusedItem,@@ -148,59 +110,32 @@ ) where  import Brick-import Brick.Focus-import Brick.Widgets.Dialog (Dialog)-import Brick.Widgets.Edit (Editor, applyEdit, editorText, getEditContents) import Brick.Widgets.List qualified as BL-import Control.Applicative (Applicative (liftA2), (<|>)) import Control.Lens hiding (from, (<.>)) import Control.Monad.Except import Control.Monad.State-import Data.Bits (FiniteBits (finiteBitSize))-import Data.Foldable (toList) import Data.List (findIndex) import Data.List.NonEmpty (NonEmpty (..))-import Data.List.NonEmpty qualified as NE-import Data.Map (Map)-import Data.Map qualified as M-import Data.Maybe (fromMaybe, isJust)-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq+import Data.Maybe (fromMaybe) import Data.Text (Text)-import Data.Text qualified as T-import Data.Text.Zipper qualified as TZ-import Data.Time (getZonedTime) import Data.Vector qualified as V import GitHash (GitInfo)+import Graphics.Vty (ColorMode (..)) import Linear (zero) import Network.Wai.Handler.Warp (Port) import Swarm.Game.Entity as E import Swarm.Game.Robot-import Swarm.Game.Scenario (loadScenario) import Swarm.Game.ScenarioInfo (-  ScenarioCollection,-  ScenarioInfo (..),   ScenarioInfoPair,-  ScenarioItem (..),-  ScenarioStatus (..),-  normalizeScenarioPath,-  scMap,-  scenarioCollectionToList,-  scenarioItemByPath,-  scenarioPath,-  scenarioSolution,-  scenarioStatus,   _SISingle,  ) import Swarm.Game.State-import Swarm.Game.World qualified as W-import Swarm.Language.Types import Swarm.TUI.Inventory.Sorting-import Swarm.Util+import Swarm.TUI.Model.Menu+import Swarm.TUI.Model.Name+import Swarm.TUI.Model.Repl+import Swarm.TUI.Model.UI import Swarm.Version (NewReleaseFailure (NoMainUpstreamRelease))-import System.Clock-import System.FilePath (dropTrailingPathSeparator, splitPath, takeFileName)-import Witch (into)  ------------------------------------------------------------ -- Custom UI label types@@ -218,496 +153,12 @@   | UpstreamVersion (Either NewReleaseFailure String)   deriving (Show) --- | 'Name' represents names to uniquely identify various components---   of the UI, such as forms, panels, caches, extents, and lists.-data Name-  = -- | The panel containing the REPL.-    REPLPanel-  | -- | The panel containing the world view.-    WorldPanel-  | -- | The panel showing robot info and inventory on the top left.-    RobotPanel-  | -- | The info panel on the bottom left.-    InfoPanel-  | -- | The REPL input form.-    REPLInput-  | -- | The render cache for the world view.-    WorldCache-  | -- | The cached extent for the world view.-    WorldExtent-  | -- | The list of inventory items for the currently-    --   focused robot.-    InventoryList-  | -- | The inventory item position in the InventoryList.-    InventoryListItem Int-  | -- | The list of main menu choices.-    MenuList-  | -- | The list of scenario choices.-    ScenarioList-  | -- | The scrollable viewport for the info panel.-    InfoViewport-  | -- | The scrollable viewport for any modal dialog.-    ModalViewport-  deriving (Eq, Ord, Show, Read)- infoScroll :: ViewportScroll Name infoScroll = viewportScroll InfoViewport  modalScroll :: ViewportScroll Name modalScroll = viewportScroll ModalViewport ---------------------------------------------------------------- REPL History----------------------------------------------------------------- | An item in the REPL history.-data REPLHistItem-  = -- | Something entered by the user.-    REPLEntry Text-  | -- | A response printed by the system.-    REPLOutput Text-  deriving (Eq, Ord, Show, Read)---- | Useful helper function to only get user input text.-getREPLEntry :: REPLHistItem -> Maybe Text-getREPLEntry = \case-  REPLEntry t -> Just t-  _ -> Nothing---- | Useful helper function to filter out REPL output.-isREPLEntry :: REPLHistItem -> Bool-isREPLEntry = isJust . getREPLEntry---- | Get the text of REPL input/output.-replItemText :: REPLHistItem -> Text-replItemText = \case-  REPLEntry t -> t-  REPLOutput t -> t---- | History of the REPL with indices (0 is first entry) to the current---   line and to the first entry since loading saved history.---   We also (ab)use the length of the REPL as the index of current---   input line, since that number is one past the index of last entry.-data REPLHistory = REPLHistory-  { _replSeq :: Seq REPLHistItem-  , _replIndex :: Int-  , _replStart :: Int-  }--makeLensesWith (lensRules & generateSignatures .~ False) ''REPLHistory---- | Sequence of REPL inputs and outputs, oldest entry is leftmost.-replSeq :: Lens' REPLHistory (Seq REPLHistItem)---- | The current index in the REPL history (if the user is going back---   through the history using up/down keys).-replIndex :: Lens' REPLHistory Int---- | The index of the first entry since loading saved history.------ It will be set on load and reset on save (happens during exit).-replStart :: Lens' REPLHistory Int---- | Create new REPL history (i.e. from loaded history file lines).-newREPLHistory :: [REPLHistItem] -> REPLHistory-newREPLHistory xs =-  let s = Seq.fromList xs-   in REPLHistory-        { _replSeq = s-        , _replStart = length s-        , _replIndex = length s-        }---- | Point the start of REPL history after current last line. See 'replStart'.-restartREPLHistory :: REPLHistory -> REPLHistory-restartREPLHistory h = h & replStart .~ replLength h---- | Current number lines of the REPL history - (ab)used as index of input buffer.-replLength :: REPLHistory -> Int-replLength = length . _replSeq---- | Add new REPL input - the index must have been pointing one past---   the last element already, so we increment it to keep it that way.-addREPLItem :: REPLHistItem -> REPLHistory -> REPLHistory-addREPLItem t h =-  h-    & replSeq %~ (|> t)-    & replIndex .~ 1 + replLength h---- | Get the latest N items in history, starting with the oldest one.------ This is used to show previous REPL lines in UI, so we need the items--- sorted in the order they were entered and will be drawn top to bottom.-getLatestREPLHistoryItems :: Int -> REPLHistory -> [REPLHistItem]-getLatestREPLHistoryItems n h = toList latestN- where-  latestN = Seq.drop oldestIndex $ h ^. replSeq-  oldestIndex = max (h ^. replStart) $ length (h ^. replSeq) - n--data TimeDir = Newer | Older deriving (Eq, Ord, Show)--moveReplHistIndex :: TimeDir -> Text -> REPLHistory -> REPLHistory-moveReplHistIndex d lastEntered history = history & replIndex .~ newIndex- where-  historyLen = replLength history-  curText = fromMaybe lastEntered $ getCurrentItemText history-  curIndex = history ^. replIndex-  entries = history ^. replSeq-  -- split repl at index-  (olderP, newer) = Seq.splitAt curIndex entries-  -- find first different entry in direction-  notSameEntry = \case-    REPLEntry t -> t /= curText-    _ -> False-  newIndex = case d of-    Newer -> maybe historyLen (curIndex +) $ Seq.findIndexL notSameEntry newer-    Older -> fromMaybe curIndex $ Seq.findIndexR notSameEntry olderP--getCurrentItemText :: REPLHistory -> Maybe Text-getCurrentItemText history = replItemText <$> Seq.lookup (history ^. replIndex) (history ^. replSeq)--replIndexIsAtInput :: REPLHistory -> Bool-replIndexIsAtInput repl = repl ^. replIndex == replLength repl---- | Given some text,  removes the REPLEntry within REPLHistory which is equal to that.---   This is used when the user enters in search mode and want to traverse the history.---   If a command has been used many times, the history will be populated with it causing---   the effect that search command always finds the same command.-removeEntry :: Text -> REPLHistory -> REPLHistory-removeEntry foundtext hist = hist & replSeq %~ Seq.filter (/= REPLEntry foundtext)---- | Get the last REPLEntry in REPLHistory matching the given text-lastEntry :: Text -> REPLHistory -> Maybe Text-lastEntry t h =-  case Seq.viewr $ Seq.filter matchEntry $ h ^. replSeq of-    Seq.EmptyR -> Nothing-    _ Seq.:> a -> Just (replItemText a)- where-  matchesText histItem = t `T.isInfixOf` replItemText histItem-  matchEntry = liftA2 (&&) matchesText isREPLEntry----------------------------------------------------------------- REPL----------------------------------------------------------------- | This data type tells us how to interpret the text typed---   by the player at the prompt (which is stored in Editor).-data REPLPrompt-  = -- | Interpret the prompt text as a regular command.-    --   The list is for potential completions, which we can-    --   cycle through by hitting Tab repeatedly-    CmdPrompt [Text]-  | -- | Interpret the prompt text as "search this text in history"-    SearchPrompt REPLHistory--defaultPrompt :: REPLPrompt-defaultPrompt = CmdPrompt []--data REPLState = REPLState-  { _replPromptType :: REPLPrompt-  , _replPromptEditor :: Editor Text Name-  , _replValid :: Bool-  , _replLast :: Text-  , _replType :: Maybe Polytype-  , _replHistory :: REPLHistory-  }--newREPLEditor :: Text -> Editor Text Name-newREPLEditor t = applyEdit gotoEnd $ editorText REPLInput (Just 1) t- where-  ls = T.lines t-  pos = (length ls - 1, T.length (last ls))-  gotoEnd = if null ls then id else TZ.moveCursor pos--initREPLState :: REPLHistory -> REPLState-initREPLState = REPLState defaultPrompt (newREPLEditor "") True "" Nothing--makeLensesWith (lensRules & generateSignatures .~ False) ''REPLState---- | The way we interpret text typed by the player in the REPL prompt.-replPromptType :: Lens' REPLState REPLPrompt---- | The prompt where the user can type input at the REPL.-replPromptEditor :: Lens' REPLState (Editor Text Name)---- | Convinience lens to get text from editor and replace it with new---   one that has the provided text.-replPromptText :: Lens' REPLState Text-replPromptText = lens g s- where-  g r = r ^. replPromptEditor . to getEditContents . to T.concat-  s r t = r & replPromptEditor .~ newREPLEditor t---- | Whether the prompt text is a valid 'Term'.-replValid :: Lens' REPLState Bool---- | The type of the current REPL input which should be displayed to---   the user (if any).-replType :: Lens' REPLState (Maybe Polytype)---- | The last thing the user has typed which isn't part of the history.---   This is used to restore the repl form after the user visited the history.-replLast :: Lens' REPLState Text---- | History of things the user has typed at the REPL, interleaved---   with outputs the system has generated.-replHistory :: Lens' REPLState REPLHistory----------------------------------------------------------------- Menus and dialogs---------------------------------------------------------------data ModalType-  = HelpModal-  | RecipesModal-  | CommandsModal-  | MessagesModal-  | RobotsModal-  | WinModal-  | QuitModal-  | KeepPlayingModal-  | DescriptionModal Entity-  | GoalModal [Text]-  deriving (Eq, Show)--data ButtonSelection = CancelButton | KeepPlayingButton | StartOverButton Seed ScenarioInfoPair | QuitButton | NextButton ScenarioInfoPair--data Modal = Modal-  { _modalType :: ModalType-  , _modalDialog :: Dialog ButtonSelection-  }--makeLenses ''Modal--data MainMenuEntry = NewGame | Tutorial | Messages | About | Quit-  deriving (Eq, Ord, Show, Read, Bounded, Enum)--data Menu-  = NoMenu -- We started playing directly from command line, no menu to show-  | MainMenu (BL.List Name MainMenuEntry)-  | NewGameMenu (NonEmpty (BL.List Name ScenarioItem)) -- stack of scenario item lists-  | MessagesMenu-  | AboutMenu--mainMenu :: MainMenuEntry -> BL.List Name MainMenuEntry-mainMenu e = BL.list MenuList (V.fromList [minBound .. maxBound]) 1 & BL.listMoveToElement e--makePrisms ''Menu---- | Create a brick 'BL.List' of scenario items from a 'ScenarioCollection'.-mkScenarioList :: Bool -> ScenarioCollection -> BL.List Name ScenarioItem-mkScenarioList cheat = flip (BL.list ScenarioList) 1 . V.fromList . filterTest . scenarioCollectionToList- where-  filterTest = if cheat then id else filter (\case SICollection n _ -> n /= "Testing"; _ -> True)---- | Given a 'ScenarioCollection' and a 'FilePath' which is the canonical---   path to some folder or scenario, construct a 'NewGameMenu' stack---   focused on the given item, if possible.-mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu-mkNewGameMenu cheat sc path = NewGameMenu . NE.fromList <$> go (Just sc) (splitPath path) []- where-  go :: Maybe ScenarioCollection -> [FilePath] -> [BL.List Name ScenarioItem] -> Maybe [BL.List Name ScenarioItem]-  go _ [] stk = Just stk-  go Nothing _ _ = Nothing-  go (Just curSC) (thing : rest) stk = go nextSC rest (lst : stk)-   where-    hasName :: ScenarioItem -> Bool-    hasName (SISingle (_, ScenarioInfo pth _ _ _)) = takeFileName pth == thing-    hasName (SICollection nm _) = nm == into @Text (dropTrailingPathSeparator thing)--    lst = BL.listFindBy hasName (mkScenarioList cheat curSC)--    nextSC = case M.lookup (dropTrailingPathSeparator thing) (scMap curSC) of-      Just (SICollection _ c) -> Just c-      _ -> Nothing----------------------------------------------------------------- Inventory list entries----------------------------------------------------------------- | An entry in the inventory list displayed in the info panel.  We---   can either have an entity with a count in the robot's inventory,---   an entity installed on the robot, or a labelled separator.  The---   purpose of the separators is to show a clear distinction between---   the robot's /inventory/ and its /installed devices/.-data InventoryListEntry-  = Separator Text-  | InventoryEntry Count Entity-  | InstalledEntry Entity-  deriving (Eq)--makePrisms ''InventoryListEntry----------------------------------------------------------------- UI state----------------------------------------------------------------- | The main record holding the UI state.  For access to the fields,--- see the lenses below.-data UIState = UIState-  { _uiMenu :: Menu-  , _uiPlaying :: Bool-  , _uiCheatMode :: Bool-  , _uiFocusRing :: FocusRing Name-  , _uiWorldCursor :: Maybe W.Coords-  , _uiREPL :: REPLState-  , _uiInventory :: Maybe (Int, BL.List Name InventoryListEntry)-  , _uiInventorySort :: InventorySortOptions-  , _uiMoreInfoTop :: Bool-  , _uiMoreInfoBot :: Bool-  , _uiScrollToEnd :: Bool-  , _uiError :: Maybe Text-  , _uiModal :: Maybe Modal-  , _uiGoal :: Maybe [Text]-  , _uiShowFPS :: Bool-  , _uiShowZero :: Bool-  , _uiHideRobotsUntil :: TimeSpec-  , _uiInventoryShouldUpdate :: Bool-  , _uiTPF :: Double-  , _uiFPS :: Double-  , _lgTicksPerSecond :: Int-  , _tickCount :: Int-  , _frameCount :: Int-  , _frameTickCount :: Int-  , _lastFrameTime :: TimeSpec-  , _accumulatedTime :: TimeSpec-  , _lastInfoTime :: TimeSpec-  , _appData :: Map Text Text-  , _scenarioRef :: Maybe ScenarioInfoPair-  }------------------------------------------------------- Lenses for UIState--let exclude = ['_lgTicksPerSecond]- in makeLensesWith-      ( lensRules-          & generateSignatures .~ False-          & lensField . mapped . mapped %~ \fn n ->-            if n `elem` exclude then [] else fn n-      )-      ''UIState---- | The current menu state.-uiMenu :: Lens' UIState Menu---- | Are we currently playing the game?  True = we are playing, and---   should thus display a world, REPL, etc.; False = we should---   display the current menu.-uiPlaying :: Lens' UIState Bool---- | Cheat mode, i.e. are we allowed to turn creative mode on and off?-uiCheatMode :: Lens' UIState Bool---- | The focus ring is the set of UI panels we can cycle among using---   the Tab key.-uiFocusRing :: Lens' UIState (FocusRing Name)---- | The last clicked position on the world view.-uiWorldCursor :: Lens' UIState (Maybe W.Coords)---- | The state of REPL panel.-uiREPL :: Lens' UIState REPLState---- | The order and direction of sorting inventory list.-uiInventorySort :: Lens' UIState InventorySortOptions---- | The hash value of the focused robot entity (so we can tell if its---   inventory changed) along with a list of the items in the---   focused robot's inventory.-uiInventory :: Lens' UIState (Maybe (Int, BL.List Name InventoryListEntry))---- | Does the info panel contain more content past the top of the panel?-uiMoreInfoTop :: Lens' UIState Bool---- | Does the info panel contain more content past the bottom of the panel?-uiMoreInfoBot :: Lens' UIState Bool---- | A flag telling the UI to scroll the info panel to the very end---   (used when a new log message is appended).-uiScrollToEnd :: Lens' UIState Bool---- | When this is @Just@, it represents a popup box containing an---   error message that is shown on top of the rest of the UI.-uiError :: Lens' UIState (Maybe Text)---- | When this is @Just@, it represents a modal to be displayed on---   top of the UI, e.g. for the Help screen.-uiModal :: Lens' UIState (Maybe Modal)---- | Status of the scenario goal: whether there is one, and whether it---   has been displayed to the user initially.-uiGoal :: Lens' UIState (Maybe [Text])---- | A toggle to show the FPS by pressing `f`-uiShowFPS :: Lens' UIState Bool---- | A toggle to show or hide inventory items with count 0 by pressing `0`-uiShowZero :: Lens' UIState Bool---- | Hide robots on the world map.-uiHideRobotsUntil :: Lens' UIState TimeSpec---- | Whether to show or hide robots on the world map.-uiShowRobots :: Getter UIState Bool-uiShowRobots = to (\ui -> ui ^. lastFrameTime > ui ^. uiHideRobotsUntil)---- | Whether the Inventory ui panel should update-uiInventoryShouldUpdate :: Lens' UIState Bool---- | Computed ticks per milli seconds-uiTPF :: Lens' UIState Double---- | Computed frames per milli seconds-uiFPS :: Lens' UIState Double---- | The currently active Scenario description, useful for starting over.-scenarioRef :: Lens' UIState (Maybe ScenarioInfoPair)---- | The base-2 logarithm of the current game speed in ticks/second.---   Note that we cap this value to the range of +/- log2 INTMAX.-lgTicksPerSecond :: Lens' UIState Int-lgTicksPerSecond = lens _lgTicksPerSecond safeSetLgTicks- where-  maxLog = finiteBitSize (maxBound :: Int)-  maxTicks = maxLog - 2-  minTicks = 2 - maxLog-  safeSetLgTicks ui lTicks-    | lTicks < minTicks = setLgTicks ui minTicks-    | lTicks > maxTicks = setLgTicks ui maxTicks-    | otherwise = setLgTicks ui lTicks-  setLgTicks ui lTicks = ui {_lgTicksPerSecond = lTicks}---- | A counter used to track how many ticks have happened since the---   last time we updated the ticks/frame statistics.-tickCount :: Lens' UIState Int---- | A counter used to track how many frames have been rendered since the---   last time we updated the ticks/frame statistics.-frameCount :: Lens' UIState Int---- | A counter used to track how many ticks have happened in the---   current frame, so we can stop when we get to the tick cap.-frameTickCount :: Lens' UIState Int---- | The time of the last info widget update-lastInfoTime :: Lens' UIState TimeSpec---- | The time of the last 'Frame' event.-lastFrameTime :: Lens' UIState TimeSpec---- | The amount of accumulated real time.  Every time we get a 'Frame'---   event, we accumulate the amount of real time that happened since---   the last frame, then attempt to take an appropriate number of---   ticks to "catch up", based on the target tick rate.------   See https://gafferongames.com/post/fix_your_timestep/ .-accumulatedTime :: Lens' UIState TimeSpec---- | Free-form data loaded from the @data@ directory, for things like---   the logo, about page, tutorial story, etc.-appData :: Lens' UIState (Map Text Text)- -- ---------------------------------------------------------------------------- --                                Runtime state                              -- -- ----------------------------------------------------------------------------@@ -792,68 +243,13 @@  -- | Get the currently focused entity from the robot info panel (if --   any).  This is just like 'focusedItem' but forgets the---   distinction between plain inventory items and installed devices.+--   distinction between plain inventory items and equipped devices. focusedEntity :: AppState -> Maybe Entity focusedEntity =   focusedItem >=> \case     Separator _ -> Nothing     InventoryEntry _ e -> Just e-    InstalledEntry e -> Just e------------------------------------------------------- UIState initialization---- | The initial state of the focus ring.-initFocusRing :: FocusRing Name-initFocusRing = focusRing [REPLPanel, InfoPanel, RobotPanel, WorldPanel]---- | The initial tick speed.-initLgTicksPerSecond :: Int-initLgTicksPerSecond = 4 -- 2^4 = 16 ticks / second---- | Initialize the UI state.  This needs to be in the IO monad since---   it involves reading a REPL history file, getting the current---   time, and loading text files from the data directory.  The @Bool@---   parameter indicates whether we should start off by showing the---   main menu.-initUIState :: Bool -> Bool -> ExceptT Text IO UIState-initUIState showMainMenu cheatMode = liftIO $ do-  historyT <- readFileMayT =<< getSwarmHistoryPath False-  appDataMap <- readAppData-  let history = maybe [] (map REPLEntry . T.lines) historyT-  startTime <- getTime Monotonic-  return $-    UIState-      { _uiMenu = if showMainMenu then MainMenu (mainMenu NewGame) else NoMenu-      , _uiPlaying = not showMainMenu-      , _uiCheatMode = cheatMode-      , _uiFocusRing = initFocusRing-      , _uiWorldCursor = Nothing-      , _uiREPL = initREPLState $ newREPLHistory history-      , _uiInventory = Nothing-      , _uiInventorySort = defaultSortOptions-      , _uiMoreInfoTop = False-      , _uiMoreInfoBot = False-      , _uiScrollToEnd = False-      , _uiError = Nothing-      , _uiModal = Nothing-      , _uiGoal = Nothing-      , _uiShowFPS = False-      , _uiShowZero = True-      , _uiHideRobotsUntil = startTime - 1-      , _uiInventoryShouldUpdate = False-      , _uiTPF = 0-      , _uiFPS = 0-      , _lgTicksPerSecond = initLgTicksPerSecond-      , _lastFrameTime = startTime-      , _accumulatedTime = 0-      , _lastInfoTime = 0-      , _tickCount = 0-      , _frameCount = 0-      , _frameTickCount = 0-      , _appData = appDataMap-      , _scenarioRef = Nothing-      }+    EquippedEntry e -> Just e  ------------------------------------------------------------ -- Functions for updating the UI state@@ -868,23 +264,27 @@   showZero <- use uiShowZero   sortOptions <- use uiInventorySort   let mkInvEntry (n, e) = InventoryEntry n e-      mkInstEntry (_, e) = InstalledEntry e-      itemList mk label =+      mkInstEntry (_, e) = EquippedEntry e+      itemList isInventoryDisplay mk label =         (\case [] -> []; xs -> Separator label : xs)           . map mk           . sortInventory sortOptions           . filter shouldDisplay           . elems--      -- Display items if we have a positive number of them, or they-      -- aren't an installed device.  In other words we don't need to-      -- display installed devices twice unless we actually have some-      -- in our inventory in addition to being installed.-      shouldDisplay (n, e) = n > 0 || showZero && not ((r ^. installedDevices) `E.contains` e)+       where+        -- Display items if we have a positive number of them, or they+        -- aren't an equipped device.  In other words we don't need to+        -- display equipped devices twice unless we actually have some+        -- in our inventory in addition to being equipped.+        shouldDisplay (n, e) =+          n > 0+            || isInventoryDisplay+              && showZero+              && not ((r ^. equippedDevices) `E.contains` e)        items =-        (r ^. robotInventory . to (itemList mkInvEntry "Inventory"))-          ++ (r ^. installedDevices . to (itemList mkInstEntry "Installed devices"))+        (r ^. robotInventory . to (itemList True mkInvEntry "Inventory"))+          ++ (r ^. equippedDevices . to (itemList False mkInstEntry "Equipped devices"))        -- Attempt to keep the selected element steady.       sel = mList >>= BL.listSelectedElement -- Get the currently selected element+index.@@ -896,8 +296,8 @@         -- if it's not there, keep the index the same.         Just (selIdx, InventoryEntry _ e) ->           fromMaybe selIdx (findIndex ((== Just e) . preview (_InventoryEntry . _2)) items)-        Just (selIdx, InstalledEntry e) ->-          fromMaybe selIdx (findIndex ((== Just e) . preview _InstalledEntry) items)+        Just (selIdx, EquippedEntry e) ->+          fromMaybe selIdx (findIndex ((== Just e) . preview _EquippedEntry) items)         Just (selIdx, _) -> selIdx        -- Create the new list, focused at the desired index.@@ -913,72 +313,24 @@  -- | Command-line options for configuring the app. data AppOpts = AppOpts-  { -- | Explicit seed chosen by the user.-    userSeed :: Maybe Seed-  , -- | Scenario the user wants to play.-    userScenario :: Maybe FilePath-  , -- | Code to be run on base.-    scriptToRun :: Maybe FilePath-  , -- | Automatically run the solution defined in the scenario file-    autoPlay :: Bool-  , -- | Should cheat mode be enabled?-    cheatMode :: Bool-  , -- | Explicit port on which to run the web API-    userWebPort :: Maybe Port-  , -- | Information about the Git repository (not present in release).-    repoGitInfo :: Maybe GitInfo+  { userSeed :: Maybe Seed+  -- ^ Explicit seed chosen by the user.+  , userScenario :: Maybe FilePath+  -- ^ Scenario the user wants to play.+  , scriptToRun :: Maybe FilePath+  -- ^ Code to be run on base.+  , autoPlay :: Bool+  -- ^ Automatically run the solution defined in the scenario file+  , cheatMode :: Bool+  -- ^ Should cheat mode be enabled?+  , colorMode :: Maybe ColorMode+  -- ^ What colour mode should be used?+  , userWebPort :: Maybe Port+  -- ^ Explicit port on which to run the web API+  , repoGitInfo :: Maybe GitInfo+  -- ^ Information about the Git repository (not present in release).   } --- | Initialize the 'AppState'.-initAppState :: AppOpts -> ExceptT Text IO AppState-initAppState AppOpts {..} = do-  let isRunningInitialProgram = isJust scriptToRun || autoPlay-      skipMenu = isJust userScenario || isRunningInitialProgram || isJust userSeed-  gs <- initGameState-  ui <- initUIState (not skipMenu) cheatMode-  let rs = initRuntimeState-  case skipMenu of-    False -> return $ AppState gs ui rs-    True -> do-      (scenario, path) <- loadScenario (fromMaybe "classic" userScenario) (gs ^. entityMap)--      let maybeAutoplay = do-            guard autoPlay-            soln <- scenario ^. scenarioSolution-            return $ SuggestedSolution soln-      let realToRun = maybeAutoplay <|> (ScriptPath <$> scriptToRun)--      execStateT-        (startGameWithSeed userSeed (scenario, ScenarioInfo path NotStarted NotStarted NotStarted) realToRun)-        (AppState gs ui rs)---- | Load a 'Scenario' and start playing the game.-startGame :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe CodeToRun -> m ()-startGame = startGameWithSeed Nothing---- | Re-initialize the game from the stored reference to the current scenario.------ Note that "restarting" is intended only for "scenarios";--- with some scenarios, it may be possible to get stuck so that it is--- either impossible or very annoying to win, so being offered an--- option to restart is more user-friendly.------ Since scenarios are stored as a Maybe in the UI state, we handle the Nothing--- case upstream so that the Scenario passed to this function definitely exists.-restartGame :: (MonadIO m, MonadState AppState m) => Seed -> ScenarioInfoPair -> m ()-restartGame currentSeed siPair = startGameWithSeed (Just currentSeed) siPair Nothing---- | Load a 'Scenario' and start playing the game, with the---   possibility for the user to override the seed.-startGameWithSeed :: (MonadIO m, MonadState AppState m) => Maybe Seed -> ScenarioInfoPair -> Maybe CodeToRun -> m ()-startGameWithSeed userSeed siPair@(_scene, si) toRun = do-  t <- liftIO getZonedTime-  ss <- use $ gameState . scenarios-  p <- liftIO $ normalizeScenarioPath ss (si ^. scenarioPath)-  gameState . currentScenarioPath .= Just p-  gameState . scenarios . scenarioItemByPath p . _SISingle . _2 . scenarioStatus .= InProgress t 0 0-  scenarioToAppState siPair userSeed toRun- -- | Extract the scenario which would come next in the menu from the --   currently selected scenario (if any).  Can return @Nothing@ if --   either we are not in the @NewGameMenu@, or the current scenario@@ -1005,34 +357,3 @@   ctxPossiblyWithIt = case s ^. gameState . replStatus of     REPLDone (Just p) -> ctx & at "it" ?~ p     _ -> ctx---- XXX do we need to keep an old entity map around???---- | Modify the 'AppState' appropriately when starting a new scenario.-scenarioToAppState :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe Seed -> Maybe CodeToRun -> m ()-scenarioToAppState siPair@(scene, _) userSeed toRun = do-  withLensIO gameState $ scenarioToGameState scene userSeed toRun-  withLensIO uiState $ scenarioToUIState siPair- where-  withLensIO :: (MonadIO m, MonadState AppState m) => Lens' AppState x -> (x -> IO x) -> m ()-  withLensIO l a = do-    x <- use l-    x' <- liftIO $ a x-    l .= x'---- | Modify the UI state appropriately when starting a new scenario.-scenarioToUIState :: ScenarioInfoPair -> UIState -> IO UIState-scenarioToUIState siPair u =-  return $-    u-      & uiPlaying .~ True-      & uiGoal .~ Nothing-      & uiFocusRing .~ initFocusRing-      & uiInventory .~ Nothing-      & uiInventorySort .~ defaultSortOptions-      & uiShowFPS .~ False-      & uiShowZero .~ True-      & lgTicksPerSecond .~ initLgTicksPerSecond-      & uiREPL .~ initREPLState (u ^. uiREPL . replHistory)-      & uiREPL . replHistory %~ restartREPLHistory-      & scenarioRef ?~ siPair
+ src/Swarm/TUI/Model/Achievement/Attainment.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE TemplateHaskell #-}++module Swarm.TUI.Model.Achievement.Attainment where++import Control.Lens hiding (from, (<.>))+import Data.Aeson (+  Options (..),+  defaultOptions,+  genericParseJSON,+  genericToJSON,+ )+import Data.Function (on)+import Data.Time (ZonedTime, zonedTimeToUTC)+import Data.Yaml as Y+import GHC.Generics (Generic)+import Swarm.TUI.Model.Achievement.Definitions++data Attainment = Attainment+  { _achievement :: CategorizedAchievement+  , _maybeScenarioPath :: Maybe FilePath+  -- ^ from which scenario was it obtained?+  , _obtainedAt :: ZonedTime+  }+  deriving (Generic)++makeLenses ''Attainment++instance Eq Attainment where+  (==) = (==) `on` _achievement++instance Ord Attainment where+  compare = compare `on` (zonedTimeToUTC . _obtainedAt)++instance Semigroup Attainment where+  (<>) = min++instance FromJSON Attainment where+  parseJSON = genericParseJSON achievementJsonOptions++instance ToJSON Attainment where+  toJSON = genericToJSON achievementJsonOptions++achievementJsonOptions :: Options+achievementJsonOptions =+  defaultOptions+    { fieldLabelModifier = tail -- drops leading underscore+    }
+ src/Swarm/TUI/Model/Achievement/Definitions.hs view
@@ -0,0 +1,89 @@+module Swarm.TUI.Model.Achievement.Definitions where++import Data.Aeson+import Data.Text (Text)+import GHC.Generics (Generic)+import Swarm.Util++data ExpectedEffort+  = Trivial+  | Easy+  | Moderate+  | Gruelling+  deriving (Eq, Ord, Show, Bounded, Enum, Generic, FromJSON, ToJSON)++data Quotation = Quotation+  { attribution :: Text+  , content :: Text+  }+  deriving (Eq, Show, Generic, FromJSON, ToJSON)++data FlavorText+  = Freeform Text+  | FTQuotation Quotation+  deriving (Eq, Show, Generic, FromJSON, ToJSON)++data AchievementInfo = AchievementInfo+  { title :: Text+  -- ^ Guidelines:+  -- * prefer puns, pop culture references, etc.+  -- * should be a phrase in Title Case.+  -- * For achievements that are "obfuscated", this can be+  --   a vague "clue" as to what the attainment entails.+  , humorousElaboration :: Maybe FlavorText+  -- ^ Explain the reference, e.g. in the form of a full quote+  -- from a movie, or something you might find+  -- in a fortune cookie+  , attainmentProcess :: Text+  -- ^ Precisely what must be done to obtain this achievement.+  , effort :: ExpectedEffort+  , isObfuscated :: Bool+  -- ^ Hides the attainment process until after the achievement is attained.+  -- Best when the title + elaboration constitute a good clue.+  }+  deriving (Eq, Show, Generic, FromJSON, ToJSON)++data CategorizedAchievement+  = GlobalAchievement GlobalAchievement+  | GameplayAchievement GameplayAchievement+  deriving (Eq, Ord, Show, Generic)++categorizedAchievementJsonOptions :: Options+categorizedAchievementJsonOptions =+  defaultOptions+    { sumEncoding = UntaggedValue+    }++instance ToJSON CategorizedAchievement where+  toJSON = genericToJSON categorizedAchievementJsonOptions++instance FromJSON CategorizedAchievement where+  parseJSON = genericParseJSON categorizedAchievementJsonOptions++-- | Achievements that entail some aggregate of actions+-- across scenarios+data GlobalAchievement+  = CompletedSingleTutorial+  | CompletedAllTutorials+  | LookedAtAboutScreen+  deriving (Eq, Ord, Show, Bounded, Enum, Generic)++instance FromJSON GlobalAchievement+instance ToJSON GlobalAchievement++-- | Achievements obtained while playing a single scenario+data GameplayAchievement+  = CraftedBitcoin+  | RobotIntoWater+  | AttemptSelfDestructBase+  | DestroyedBase+  | LoseScenario+  deriving (Eq, Ord, Show, Bounded, Enum, Generic)++instance FromJSON GameplayAchievement+instance ToJSON GameplayAchievement++listAchievements :: [CategorizedAchievement]+listAchievements =+  map GlobalAchievement listEnums+    <> map GameplayAchievement listEnums
+ src/Swarm/TUI/Model/Achievement/Description.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.TUI.Model.Achievement.Description where++import Swarm.TUI.Model.Achievement.Definitions++describe :: CategorizedAchievement -> AchievementInfo+describe (GlobalAchievement CompletedSingleTutorial) =+  AchievementInfo+    "Welcome Freshmen"+    (Just $ Freeform "School is in session!")+    "Complete one of the tutorials."+    Easy+    False+describe (GlobalAchievement CompletedAllTutorials) =+  AchievementInfo+    "Autodidact"+    ( Just $+        FTQuotation $+          Quotation+            "Terry Pratchet"+            "I didn't go to university... But I have sympathy for those who did."+    )+    "Complete all of the tutorials."+    Moderate+    False+describe (GlobalAchievement LookedAtAboutScreen) =+  AchievementInfo+    "About time!"+    Nothing+    "View the About screen."+    Trivial+    True+describe (GameplayAchievement CraftedBitcoin) =+  -- Bitcoin is the deepest level of the recipes+  -- hierarchy.+  AchievementInfo+    "Master of Your Craft"+    Nothing+    "Make a Bitcoin"+    Moderate+    True+describe (GameplayAchievement RobotIntoWater) =+  AchievementInfo+    "Watery Grave"+    (Just $ Freeform "This little robot thinks he's a submarine.")+    "Destroy a robot by sending it into the water."+    Easy+    True+describe (GameplayAchievement AttemptSelfDestructBase) =+  AchievementInfo+    "Call of the Void"+    (Just $ Freeform "What does that big red button do?")+    "Attempt to self-destruct your base."+    Easy+    True+describe (GameplayAchievement DestroyedBase) =+  AchievementInfo+    "That Could Have Gone Better"+    (Just $ Freeform "Boom.")+    "Actually destroy your base."+    Moderate+    True+describe (GameplayAchievement LoseScenario) =+  AchievementInfo+    "Silver Lining"+    (Just $ Freeform "Here's your consolation prize.")+    "Lose at a scenario."+    Easy+    True
+ src/Swarm/TUI/Model/Achievement/Persistence.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.TUI.Model.Achievement.Persistence where++import Control.Arrow (left)+import Control.Carrier.Lift (sendIO)+import Control.Monad (forM, forM_)+import Data.Either (partitionEithers)+import Data.Yaml qualified as Y+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Failure+import Swarm.Util+import System.Directory (+  doesDirectoryExist,+  doesFileExist,+  listDirectory,+ )+import System.FilePath ((</>))++-- | Get path to swarm achievements, optionally creating necessary+--   directories.+getSwarmAchievementsPath :: Bool -> IO FilePath+getSwarmAchievementsPath createDirs = getSwarmXdgDataSubdir createDirs "achievement"++-- | Load saved info about achievements from XDG data directory.+-- Returns a tuple of warnings and attained achievements.+loadAchievementsInfo ::+  IO ([SystemFailure], [Attainment])+loadAchievementsInfo = do+  savedAchievementsPath <- getSwarmAchievementsPath False+  doesParentExist <- doesDirectoryExist savedAchievementsPath+  if doesParentExist+    then do+      contents <- listDirectory savedAchievementsPath+      eithersList <- forM contents $ \p -> do+        let fullPath = savedAchievementsPath </> p+        isFile <- doesFileExist fullPath+        if isFile+          then do+            eitherDecodedFile <- sendIO (Y.decodeFileEither fullPath)+            return $ left (AssetNotLoaded Achievement p . CanNotParse) eitherDecodedFile+          else return . Left $ AssetNotLoaded Achievement p (EntryNot File)+      return $ partitionEithers eithersList+    else return ([AssetNotLoaded Achievement "." (DoesNotExist Directory)], [])++-- | Save info about achievements to XDG data directory.+saveAchievementsInfo ::+  [Attainment] ->+  IO ()+saveAchievementsInfo attainmentList = do+  savedAchievementsPath <- getSwarmAchievementsPath True+  forM_ attainmentList $ \x -> do+    let achievementName = case _achievement x of+          GlobalAchievement y -> show y+          GameplayAchievement y -> show y+        fullPath = savedAchievementsPath </> achievementName+    Y.encodeFile fullPath x
+ src/Swarm/TUI/Model/Failure.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module      :  Swarm.Game.Failure+-- Copyright   :  Ondřej Šebek+-- Maintainer  :  ondras98@icloud.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- A data type to represent system failures.+--+-- These failures are often not fatal and serve+-- to create common infrastructure for logging.+module Swarm.TUI.Model.Failure where++import Data.Char (toLower)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Yaml (ParseException, prettyPrintParseException)++data SystemFailure+  = AssetNotLoaded Asset FilePath LoadingFailure++data Asset = Achievement | Data | History | Save+  deriving (Eq, Show)++data Entry = Directory | File+  deriving (Eq, Show)++data LoadingFailure+  = DoesNotExist Entry+  | EntryNot Entry+  | CanNotParse ParseException++tShowLow :: Show a => a -> Text+tShowLow = T.pack . map toLower . show++tShow :: Show a => a -> Text+tShow = T.pack . show++prettyLoadingFailure :: LoadingFailure -> Text+prettyLoadingFailure = \case+  DoesNotExist e -> "The " <> tShowLow e <> " is missing!"+  EntryNot e -> "The entry is not a " <> tShowLow e <> "!"+  CanNotParse p -> "Parse failure:\n" <> T.pack (indent 8 $ prettyPrintParseException p)+ where+  indent n = unlines . map (replicate n ' ' ++) . lines++prettyFailure :: SystemFailure -> Text+prettyFailure = \case+  AssetNotLoaded a fp l -> T.unwords ["Failed to acquire", tShowLow a, tShow fp] <> ": " <> prettyLoadingFailure l
+ src/Swarm/TUI/Model/Menu.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}++module Swarm.TUI.Model.Menu where++import Brick.Widgets.Dialog (Dialog)+import Brick.Widgets.List qualified as BL+import Control.Lens hiding (from, (<.>))+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE+import Data.Map qualified as M+import Data.Text (Text)+import Data.Vector qualified as V+import Swarm.Game.Entity as E+import Swarm.Game.ScenarioInfo (+  ScenarioCollection,+  ScenarioInfo (..),+  ScenarioInfoPair,+  ScenarioItem (..),+  scMap,+  scenarioCollectionToList,+ )+import Swarm.Game.State+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Name+import Swarm.Util+import System.FilePath (dropTrailingPathSeparator, splitPath, takeFileName)+import Witch (into)++------------------------------------------------------------+-- Menus and dialogs+------------------------------------------------------------++data ModalType+  = HelpModal+  | RecipesModal+  | CommandsModal+  | MessagesModal+  | RobotsModal+  | WinModal+  | LoseModal+  | QuitModal+  | KeepPlayingModal+  | DescriptionModal Entity+  | GoalModal+  deriving (Show)++data ButtonAction+  = Cancel+  | KeepPlaying+  | StartOver Seed ScenarioInfoPair+  | QuitAction+  | Next ScenarioInfoPair++data Modal = Modal+  { _modalType :: ModalType+  , _modalDialog :: Dialog ButtonAction Name+  }++makeLenses ''Modal++data MainMenuEntry+  = NewGame+  | Tutorial+  | Achievements+  | Messages+  | About+  | Quit+  deriving (Eq, Ord, Show, Read, Bounded, Enum)++data Menu+  = NoMenu -- We started playing directly from command line, no menu to show+  | MainMenu (BL.List Name MainMenuEntry)+  | NewGameMenu (NonEmpty (BL.List Name ScenarioItem)) -- stack of scenario item lists+  | AchievementsMenu (BL.List Name CategorizedAchievement)+  | MessagesMenu+  | AboutMenu++mainMenu :: MainMenuEntry -> BL.List Name MainMenuEntry+mainMenu e = BL.list MenuList (V.fromList listEnums) 1 & BL.listMoveToElement e++makePrisms ''Menu++-- | Create a brick 'BL.List' of scenario items from a 'ScenarioCollection'.+mkScenarioList :: Bool -> ScenarioCollection -> BL.List Name ScenarioItem+mkScenarioList cheat = flip (BL.list ScenarioList) 1 . V.fromList . filterTest . scenarioCollectionToList+ where+  filterTest = if cheat then id else filter (\case SICollection n _ -> n /= "Testing"; _ -> True)++-- | Given a 'ScenarioCollection' and a 'FilePath' which is the canonical+--   path to some folder or scenario, construct a 'NewGameMenu' stack+--   focused on the given item, if possible.+mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu+mkNewGameMenu cheat sc path = NewGameMenu . NE.fromList <$> go (Just sc) (splitPath path) []+ where+  go :: Maybe ScenarioCollection -> [FilePath] -> [BL.List Name ScenarioItem] -> Maybe [BL.List Name ScenarioItem]+  go _ [] stk = Just stk+  go Nothing _ _ = Nothing+  go (Just curSC) (thing : rest) stk = go nextSC rest (lst : stk)+   where+    hasName :: ScenarioItem -> Bool+    hasName (SISingle (_, ScenarioInfo pth _ _ _)) = takeFileName pth == thing+    hasName (SICollection nm _) = nm == into @Text (dropTrailingPathSeparator thing)++    lst = BL.listFindBy hasName (mkScenarioList cheat curSC)++    nextSC = case M.lookup (dropTrailingPathSeparator thing) (scMap curSC) of+      Just (SICollection _ c) -> Just c+      _ -> Nothing++------------------------------------------------------------+-- Inventory list entries+------------------------------------------------------------++-- | An entry in the inventory list displayed in the info panel.  We+--   can either have an entity with a count in the robot's inventory,+--   an entity equipped on the robot, or a labelled separator.  The+--   purpose of the separators is to show a clear distinction between+--   the robot's /inventory/ and its /equipped devices/.+data InventoryListEntry+  = Separator Text+  | InventoryEntry Count Entity+  | EquippedEntry Entity+  deriving (Eq)++makePrisms ''InventoryListEntry
+ src/Swarm/TUI/Model/Name.hs view
@@ -0,0 +1,57 @@+module Swarm.TUI.Model.Name where++data FocusablePanel+  = -- | The panel containing the REPL.+    REPLPanel+  | -- | The panel containing the world view.+    WorldPanel+  | -- | The panel showing robot info and inventory on the top left.+    RobotPanel+  | -- | The info panel on the bottom left.+    InfoPanel+  deriving (Eq, Ord, Show, Read, Bounded, Enum)++data GoalWidget+  = ObjectivesList+  | GoalSummary+  deriving (Eq, Ord, Show, Read, Bounded, Enum)++-- | Clickable buttons in modal dialogs.+data Button+  = CancelButton+  | KeepPlayingButton+  | StartOverButton+  | QuitButton+  | NextButton+  deriving (Eq, Ord, Show, Read, Bounded, Enum)++-- | 'Name' represents names to uniquely identify various components+--   of the UI, such as forms, panels, caches, extents, lists, and buttons.+data Name+  = FocusablePanel FocusablePanel+  | -- | The REPL input form.+    REPLInput+  | -- | The render cache for the world view.+    WorldCache+  | -- | The cached extent for the world view.+    WorldExtent+  | -- | The list of inventory items for the currently+    --   focused robot.+    InventoryList+  | -- | The inventory item position in the InventoryList.+    InventoryListItem Int+  | -- | The list of main menu choices.+    MenuList+  | -- | The list of achievements.+    AchievementList+  | -- | The list of goals/objectives.+    GoalWidgets GoalWidget+  | -- | The list of scenario choices.+    ScenarioList+  | -- | The scrollable viewport for the info panel.+    InfoViewport+  | -- | The scrollable viewport for any modal dialog.+    ModalViewport+  | -- | A clickable button in a modal dialog.+    Button Button+  deriving (Eq, Ord, Show, Read)
+ src/Swarm/TUI/Model/Repl.hs view
@@ -0,0 +1,266 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}++module Swarm.TUI.Model.Repl (+  -- ** REPL+  REPLHistItem (..),+  replItemText,+  isREPLEntry,+  getREPLEntry,+  REPLHistory,+  replIndex,+  replLength,+  replSeq,+  newREPLHistory,+  addREPLItem,+  restartREPLHistory,+  getLatestREPLHistoryItems,+  moveReplHistIndex,+  getCurrentItemText,+  replIndexIsAtInput,+  TimeDir (..),++  -- ** Prompt utils+  REPLPrompt (..),+  removeEntry,++  -- *** REPL Panel Model+  REPLState,+  ReplControlMode (..),+  replPromptType,+  replPromptEditor,+  replPromptText,+  replValid,+  replLast,+  replType,+  replControlMode,+  replHistory,+  newREPLEditor,++  -- ** Initialization+  initREPLState,+  defaultPrompt,+  lastEntry,+) where++import Brick.Widgets.Edit (Editor, applyEdit, editorText, getEditContents)+import Control.Applicative (Applicative (liftA2))+import Control.Lens hiding (from, (<.>))+import Data.Foldable (toList)+import Data.Maybe (fromMaybe, isJust)+import Data.Sequence (Seq)+import Data.Sequence qualified as Seq+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Zipper qualified as TZ+import Swarm.Language.Types+import Swarm.TUI.Model.Name++------------------------------------------------------------+-- REPL History+------------------------------------------------------------++-- | An item in the REPL history.+data REPLHistItem+  = -- | Something entered by the user.+    REPLEntry Text+  | -- | A response printed by the system.+    REPLOutput Text+  deriving (Eq, Ord, Show, Read)++-- | Useful helper function to only get user input text.+getREPLEntry :: REPLHistItem -> Maybe Text+getREPLEntry = \case+  REPLEntry t -> Just t+  _ -> Nothing++-- | Useful helper function to filter out REPL output.+isREPLEntry :: REPLHistItem -> Bool+isREPLEntry = isJust . getREPLEntry++-- | Get the text of REPL input/output.+replItemText :: REPLHistItem -> Text+replItemText = \case+  REPLEntry t -> t+  REPLOutput t -> t++-- | History of the REPL with indices (0 is first entry) to the current+--   line and to the first entry since loading saved history.+--   We also (ab)use the length of the REPL as the index of current+--   input line, since that number is one past the index of last entry.+data REPLHistory = REPLHistory+  { _replSeq :: Seq REPLHistItem+  , _replIndex :: Int+  , _replStart :: Int+  }++makeLensesWith (lensRules & generateSignatures .~ False) ''REPLHistory++-- | Sequence of REPL inputs and outputs, oldest entry is leftmost.+replSeq :: Lens' REPLHistory (Seq REPLHistItem)++-- | The current index in the REPL history (if the user is going back+--   through the history using up/down keys).+replIndex :: Lens' REPLHistory Int++-- | The index of the first entry since loading saved history.+--+-- It will be set on load and reset on save (happens during exit).+replStart :: Lens' REPLHistory Int++-- | Create new REPL history (i.e. from loaded history file lines).+newREPLHistory :: [REPLHistItem] -> REPLHistory+newREPLHistory xs =+  let s = Seq.fromList xs+   in REPLHistory+        { _replSeq = s+        , _replStart = length s+        , _replIndex = length s+        }++-- | Point the start of REPL history after current last line. See 'replStart'.+restartREPLHistory :: REPLHistory -> REPLHistory+restartREPLHistory h = h & replStart .~ replLength h++-- | Current number lines of the REPL history - (ab)used as index of input buffer.+replLength :: REPLHistory -> Int+replLength = length . _replSeq++-- | Add new REPL input - the index must have been pointing one past+--   the last element already, so we increment it to keep it that way.+addREPLItem :: REPLHistItem -> REPLHistory -> REPLHistory+addREPLItem t h =+  h+    & replSeq %~ (|> t)+    & replIndex .~ 1 + replLength h++-- | Get the latest N items in history, starting with the oldest one.+--+-- This is used to show previous REPL lines in UI, so we need the items+-- sorted in the order they were entered and will be drawn top to bottom.+getLatestREPLHistoryItems :: Int -> REPLHistory -> [REPLHistItem]+getLatestREPLHistoryItems n h = toList latestN+ where+  latestN = Seq.drop oldestIndex $ h ^. replSeq+  oldestIndex = max (h ^. replStart) $ length (h ^. replSeq) - n++data TimeDir = Newer | Older deriving (Eq, Ord, Show)++moveReplHistIndex :: TimeDir -> Text -> REPLHistory -> REPLHistory+moveReplHistIndex d lastEntered history = history & replIndex .~ newIndex+ where+  historyLen = replLength history+  curText = fromMaybe lastEntered $ getCurrentItemText history+  curIndex = history ^. replIndex+  entries = history ^. replSeq+  -- split repl at index+  (olderP, newer) = Seq.splitAt curIndex entries+  -- find first different entry in direction+  notSameEntry = \case+    REPLEntry t -> t /= curText+    _ -> False+  newIndex = case d of+    Newer -> maybe historyLen (curIndex +) $ Seq.findIndexL notSameEntry newer+    Older -> fromMaybe curIndex $ Seq.findIndexR notSameEntry olderP++getCurrentItemText :: REPLHistory -> Maybe Text+getCurrentItemText history = replItemText <$> Seq.lookup (history ^. replIndex) (history ^. replSeq)++replIndexIsAtInput :: REPLHistory -> Bool+replIndexIsAtInput repl = repl ^. replIndex == replLength repl++-- | Given some text,  removes the REPLEntry within REPLHistory which is equal to that.+--   This is used when the user enters in search mode and want to traverse the history.+--   If a command has been used many times, the history will be populated with it causing+--   the effect that search command always finds the same command.+removeEntry :: Text -> REPLHistory -> REPLHistory+removeEntry foundtext hist = hist & replSeq %~ Seq.filter (/= REPLEntry foundtext)++-- | Get the last REPLEntry in REPLHistory matching the given text+lastEntry :: Text -> REPLHistory -> Maybe Text+lastEntry t h =+  case Seq.viewr $ Seq.filter matchEntry $ h ^. replSeq of+    Seq.EmptyR -> Nothing+    _ Seq.:> a -> Just (replItemText a)+ where+  matchesText histItem = t `T.isInfixOf` replItemText histItem+  matchEntry = liftA2 (&&) matchesText isREPLEntry++------------------------------------------------------------+-- REPL+------------------------------------------------------------++-- | This data type tells us how to interpret the text typed+--   by the player at the prompt (which is stored in Editor).+data REPLPrompt+  = -- | Interpret the prompt text as a regular command.+    --   The list is for potential completions, which we can+    --   cycle through by hitting Tab repeatedly+    CmdPrompt [Text]+  | -- | Interpret the prompt text as "search this text in history"+    SearchPrompt REPLHistory++defaultPrompt :: REPLPrompt+defaultPrompt = CmdPrompt []++data ReplControlMode+  = Piloting+  | Typing+  deriving (Enum, Bounded, Eq)++data REPLState = REPLState+  { _replPromptType :: REPLPrompt+  , _replPromptEditor :: Editor Text Name+  , _replValid :: Bool+  , _replLast :: Text+  , _replType :: Maybe Polytype+  , _replControlMode :: ReplControlMode+  , _replHistory :: REPLHistory+  }++newREPLEditor :: Text -> Editor Text Name+newREPLEditor t = applyEdit gotoEnd $ editorText REPLInput (Just 1) t+ where+  ls = T.lines t+  pos = (length ls - 1, T.length (last ls))+  gotoEnd = if null ls then id else TZ.moveCursor pos++initREPLState :: REPLHistory -> REPLState+initREPLState = REPLState defaultPrompt (newREPLEditor "") True "" Nothing Typing++makeLensesWith (lensRules & generateSignatures .~ False) ''REPLState++-- | The way we interpret text typed by the player in the REPL prompt.+replPromptType :: Lens' REPLState REPLPrompt++-- | The prompt where the user can type input at the REPL.+replPromptEditor :: Lens' REPLState (Editor Text Name)++-- | Convinience lens to get text from editor and replace it with new+--   one that has the provided text.+replPromptText :: Lens' REPLState Text+replPromptText = lens g s+ where+  g r = r ^. replPromptEditor . to getEditContents . to T.concat+  s r t = r & replPromptEditor .~ newREPLEditor t++-- | Whether the prompt text is a valid 'Term'.+replValid :: Lens' REPLState Bool++-- | The type of the current REPL input which should be displayed to+--   the user (if any).+replType :: Lens' REPLState (Maybe Polytype)++-- | The last thing the user has typed which isn't part of the history.+--   This is used to restore the repl form after the user visited the history.+replLast :: Lens' REPLState Text++-- | Piloting or Typing mode+replControlMode :: Lens' REPLState ReplControlMode++-- | History of things the user has typed at the REPL, interleaved+--   with outputs the system has generated.+replHistory :: Lens' REPLState REPLHistory
+ src/Swarm/TUI/Model/StateUpdate.hs view
@@ -0,0 +1,158 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Swarm.TUI.Model.StateUpdate (+  initAppState,+  startGame,+  restartGame,+  attainAchievement,+  attainAchievement',+  scenarioToAppState,+) where++import Brick.AttrMap (applyAttrMappings)+import Control.Applicative ((<|>))+import Control.Lens hiding (from, (<.>))+import Control.Monad.Except+import Control.Monad.State+import Data.List qualified as List+import Data.Map qualified as M+import Data.Maybe (fromMaybe, isJust)+import Data.Text (Text)+import Data.Time (ZonedTime, getZonedTime)+import Swarm.Game.Log (ErrorLevel (..), LogSource (ErrorTrace))+import Swarm.Game.Scenario (loadScenario, scenarioAttrs)+import Swarm.Game.Scenario.Objective.Presentation.Model (emptyGoalDisplay)+import Swarm.Game.ScenarioInfo (+  ScenarioInfo (..),+  ScenarioInfoPair,+  ScenarioStatus (..),+  normalizeScenarioPath,+  scenarioItemByPath,+  scenarioPath,+  scenarioSolution,+  scenarioStatus,+  _SISingle,+ )+import Swarm.Game.State+import Swarm.TUI.Attr (swarmAttrMap)+import Swarm.TUI.Inventory.Sorting+import Swarm.TUI.Model+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Achievement.Persistence+import Swarm.TUI.Model.Failure (prettyFailure)+import Swarm.TUI.Model.Repl+import Swarm.TUI.Model.UI+import Swarm.TUI.View.CustomStyling (toAttrPair)+import System.Clock++-- | Initialize the 'AppState'.+initAppState :: AppOpts -> ExceptT Text IO AppState+initAppState AppOpts {..} = do+  let isRunningInitialProgram = isJust scriptToRun || autoPlay+      skipMenu = isJust userScenario || isRunningInitialProgram || isJust userSeed+  gs <- initGameState+  (warnings, ui) <- initUIState (not skipMenu) cheatMode+  let logWarning rs w = rs & eventLog %~ logEvent (ErrorTrace Error) ("UI Loading", -8) (prettyFailure w)+  let rs = List.foldl' logWarning initRuntimeState warnings+  case skipMenu of+    False -> return $ AppState gs ui rs+    True -> do+      (scenario, path) <- loadScenario (fromMaybe "classic" userScenario) (gs ^. entityMap)++      let maybeAutoplay = do+            guard autoPlay+            soln <- scenario ^. scenarioSolution+            return $ SuggestedSolution soln+      let realToRun = maybeAutoplay <|> (ScriptPath <$> scriptToRun)++      execStateT+        (startGameWithSeed userSeed (scenario, ScenarioInfo path NotStarted NotStarted NotStarted) realToRun)+        (AppState gs ui rs)++-- | Load a 'Scenario' and start playing the game.+startGame :: (MonadIO m, MonadState AppState m) => ScenarioInfoPair -> Maybe CodeToRun -> m ()+startGame = startGameWithSeed Nothing++-- | Re-initialize the game from the stored reference to the current scenario.+--+-- Note that "restarting" is intended only for "scenarios";+-- with some scenarios, it may be possible to get stuck so that it is+-- either impossible or very annoying to win, so being offered an+-- option to restart is more user-friendly.+--+-- Since scenarios are stored as a Maybe in the UI state, we handle the Nothing+-- case upstream so that the Scenario passed to this function definitely exists.+restartGame :: (MonadIO m, MonadState AppState m) => Seed -> ScenarioInfoPair -> m ()+restartGame currentSeed siPair = startGameWithSeed (Just currentSeed) siPair Nothing++-- | Load a 'Scenario' and start playing the game, with the+--   possibility for the user to override the seed.+startGameWithSeed :: (MonadIO m, MonadState AppState m) => Maybe Seed -> ScenarioInfoPair -> Maybe CodeToRun -> m ()+startGameWithSeed userSeed siPair@(_scene, si) toRun = do+  t <- liftIO getZonedTime+  ss <- use $ gameState . scenarios+  p <- liftIO $ normalizeScenarioPath ss (si ^. scenarioPath)+  gameState . currentScenarioPath .= Just p+  gameState . scenarios . scenarioItemByPath p . _SISingle . _2 . scenarioStatus .= InProgress t 0 0+  scenarioToAppState siPair userSeed toRun++-- TODO: #516 do we need to keep an old entity map around???++-- | Modify the 'AppState' appropriately when starting a new scenario.+scenarioToAppState ::+  (MonadIO m, MonadState AppState m) =>+  ScenarioInfoPair ->+  Maybe Seed ->+  Maybe CodeToRun ->+  m ()+scenarioToAppState siPair@(scene, _) userSeed toRun = do+  withLensIO gameState $ scenarioToGameState scene userSeed toRun+  withLensIO uiState $ scenarioToUIState siPair+ where+  withLensIO :: (MonadIO m, MonadState AppState m) => Lens' AppState x -> (x -> IO x) -> m ()+  withLensIO l a = do+    x <- use l+    x' <- liftIO $ a x+    l .= x'++attainAchievement :: (MonadIO m, MonadState AppState m) => CategorizedAchievement -> m ()+attainAchievement a = do+  currentTime <- liftIO getZonedTime+  attainAchievement' currentTime Nothing a++attainAchievement' ::+  (MonadIO m, MonadState AppState m) =>+  ZonedTime ->+  Maybe FilePath ->+  CategorizedAchievement ->+  m ()+attainAchievement' t p a = do+  (uiState . uiAchievements)+    %= M.insertWith+      (<>)+      a+      (Attainment a p t)+  newAchievements <- use $ uiState . uiAchievements+  liftIO $ saveAchievementsInfo $ M.elems newAchievements++-- | Modify the UI state appropriately when starting a new scenario.+scenarioToUIState :: ScenarioInfoPair -> UIState -> IO UIState+scenarioToUIState siPair u = do+  curTime <- getTime Monotonic+  return $+    u+      & uiPlaying .~ True+      & uiGoal .~ emptyGoalDisplay+      & uiFocusRing .~ initFocusRing+      & uiInventory .~ Nothing+      & uiInventorySort .~ defaultSortOptions+      & uiShowFPS .~ False+      & uiShowZero .~ True+      & lgTicksPerSecond .~ initLgTicksPerSecond+      & uiREPL .~ initREPLState (u ^. uiREPL . replHistory)+      & uiREPL . replHistory %~ restartREPLHistory+      & uiAttrMap .~ applyAttrMappings (map toAttrPair $ fst siPair ^. scenarioAttrs) swarmAttrMap+      & scenarioRef ?~ siPair+      & lastFrameTime .~ curTime
+ src/Swarm/TUI/Model/UI.hs view
@@ -0,0 +1,312 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}++module Swarm.TUI.Model.UI (+  UIState (..),+  GoalDisplay (..),+  uiMenu,+  uiPlaying,+  uiCheatMode,+  uiFocusRing,+  uiWorldCursor,+  uiREPL,+  uiInventory,+  uiInventorySort,+  uiMoreInfoTop,+  uiMoreInfoBot,+  uiScrollToEnd,+  uiError,+  uiModal,+  uiGoal,+  uiAchievements,+  lgTicksPerSecond,+  lastFrameTime,+  accumulatedTime,+  tickCount,+  frameCount,+  frameTickCount,+  lastInfoTime,+  uiShowFPS,+  uiShowZero,+  uiShowRobots,+  uiHideRobotsUntil,+  uiInventoryShouldUpdate,+  uiTPF,+  uiFPS,+  uiAttrMap,+  scenarioRef,+  appData,++  -- ** Initialization+  initFocusRing,+  initLgTicksPerSecond,+  initUIState,+) where++import Brick (AttrMap)+import Brick.Focus+import Brick.Widgets.List qualified as BL+import Control.Arrow ((&&&))+import Control.Lens hiding (from, (<.>))+import Control.Monad.Except+import Data.Bits (FiniteBits (finiteBitSize))+import Data.Map (Map)+import Data.Map qualified as M+import Data.Text (Text)+import Data.Text qualified as T+import Swarm.Game.Scenario.Objective.Presentation.Model+import Swarm.Game.ScenarioInfo (+  ScenarioInfoPair,+ )+import Swarm.Game.World qualified as W+import Swarm.TUI.Attr (swarmAttrMap)+import Swarm.TUI.Inventory.Sorting+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Achievement.Persistence+import Swarm.TUI.Model.Failure (SystemFailure)+import Swarm.TUI.Model.Menu+import Swarm.TUI.Model.Name+import Swarm.TUI.Model.Repl+import Swarm.Util+import System.Clock++------------------------------------------------------------+-- UI state+------------------------------------------------------------++-- | The main record holding the UI state.  For access to the fields,+-- see the lenses below.+data UIState = UIState+  { _uiMenu :: Menu+  , _uiPlaying :: Bool+  , _uiCheatMode :: Bool+  , _uiFocusRing :: FocusRing Name+  , _uiWorldCursor :: Maybe W.Coords+  , _uiREPL :: REPLState+  , _uiInventory :: Maybe (Int, BL.List Name InventoryListEntry)+  , _uiInventorySort :: InventorySortOptions+  , _uiMoreInfoTop :: Bool+  , _uiMoreInfoBot :: Bool+  , _uiScrollToEnd :: Bool+  , _uiError :: Maybe Text+  , _uiModal :: Maybe Modal+  , _uiGoal :: GoalDisplay+  , _uiAchievements :: Map CategorizedAchievement Attainment+  , _uiShowFPS :: Bool+  , _uiShowZero :: Bool+  , _uiHideRobotsUntil :: TimeSpec+  , _uiInventoryShouldUpdate :: Bool+  , _uiTPF :: Double+  , _uiFPS :: Double+  , _lgTicksPerSecond :: Int+  , _tickCount :: Int+  , _frameCount :: Int+  , _frameTickCount :: Int+  , _lastFrameTime :: TimeSpec+  , _accumulatedTime :: TimeSpec+  , _lastInfoTime :: TimeSpec+  , _appData :: Map Text Text+  , _uiAttrMap :: AttrMap+  , _scenarioRef :: Maybe ScenarioInfoPair+  }++--------------------------------------------------+-- Lenses for UIState++let exclude = ['_lgTicksPerSecond]+ in makeLensesWith+      ( lensRules+          & generateSignatures .~ False+          & lensField . mapped . mapped %~ \fn n ->+            if n `elem` exclude then [] else fn n+      )+      ''UIState++-- | The current menu state.+uiMenu :: Lens' UIState Menu++-- | Are we currently playing the game?  True = we are playing, and+--   should thus display a world, REPL, etc.; False = we should+--   display the current menu.+uiPlaying :: Lens' UIState Bool++-- | Cheat mode, i.e. are we allowed to turn creative mode on and off?+uiCheatMode :: Lens' UIState Bool++-- | The focus ring is the set of UI panels we can cycle among using+--   the Tab key.+uiFocusRing :: Lens' UIState (FocusRing Name)++-- | The last clicked position on the world view.+uiWorldCursor :: Lens' UIState (Maybe W.Coords)++-- | The state of REPL panel.+uiREPL :: Lens' UIState REPLState++-- | The order and direction of sorting inventory list.+uiInventorySort :: Lens' UIState InventorySortOptions++-- | The hash value of the focused robot entity (so we can tell if its+--   inventory changed) along with a list of the items in the+--   focused robot's inventory.+uiInventory :: Lens' UIState (Maybe (Int, BL.List Name InventoryListEntry))++-- | Does the info panel contain more content past the top of the panel?+uiMoreInfoTop :: Lens' UIState Bool++-- | Does the info panel contain more content past the bottom of the panel?+uiMoreInfoBot :: Lens' UIState Bool++-- | A flag telling the UI to scroll the info panel to the very end+--   (used when a new log message is appended).+uiScrollToEnd :: Lens' UIState Bool++-- | When this is @Just@, it represents a popup box containing an+--   error message that is shown on top of the rest of the UI.+uiError :: Lens' UIState (Maybe Text)++-- | When this is @Just@, it represents a modal to be displayed on+--   top of the UI, e.g. for the Help screen.+uiModal :: Lens' UIState (Maybe Modal)++-- | Status of the scenario goal: whether there is one, and whether it+--   has been displayed to the user initially.+uiGoal :: Lens' UIState GoalDisplay++-- | Map of achievements that were attained+uiAchievements :: Lens' UIState (Map CategorizedAchievement Attainment)++-- | A toggle to show the FPS by pressing `f`+uiShowFPS :: Lens' UIState Bool++-- | A toggle to show or hide inventory items with count 0 by pressing `0`+uiShowZero :: Lens' UIState Bool++-- | Hide robots on the world map.+uiHideRobotsUntil :: Lens' UIState TimeSpec++-- | Whether to show or hide robots on the world map.+uiShowRobots :: Getter UIState Bool+uiShowRobots = to (\ui -> ui ^. lastFrameTime > ui ^. uiHideRobotsUntil)++-- | Whether the Inventory ui panel should update+uiInventoryShouldUpdate :: Lens' UIState Bool++-- | Computed ticks per milli seconds+uiTPF :: Lens' UIState Double++-- | Computed frames per milli seconds+uiFPS :: Lens' UIState Double++-- | Attribute map+uiAttrMap :: Lens' UIState AttrMap++-- | The currently active Scenario description, useful for starting over.+scenarioRef :: Lens' UIState (Maybe ScenarioInfoPair)++-- | The base-2 logarithm of the current game speed in ticks/second.+--   Note that we cap this value to the range of +/- log2 INTMAX.+lgTicksPerSecond :: Lens' UIState Int+lgTicksPerSecond = lens _lgTicksPerSecond safeSetLgTicks+ where+  maxLog = finiteBitSize (maxBound :: Int)+  maxTicks = maxLog - 2+  minTicks = 2 - maxLog+  safeSetLgTicks ui lTicks+    | lTicks < minTicks = setLgTicks ui minTicks+    | lTicks > maxTicks = setLgTicks ui maxTicks+    | otherwise = setLgTicks ui lTicks+  setLgTicks ui lTicks = ui {_lgTicksPerSecond = lTicks}++-- | A counter used to track how many ticks have happened since the+--   last time we updated the ticks/frame statistics.+tickCount :: Lens' UIState Int++-- | A counter used to track how many frames have been rendered since the+--   last time we updated the ticks/frame statistics.+frameCount :: Lens' UIState Int++-- | A counter used to track how many ticks have happened in the+--   current frame, so we can stop when we get to the tick cap.+frameTickCount :: Lens' UIState Int++-- | The time of the last info widget update+lastInfoTime :: Lens' UIState TimeSpec++-- | The time of the last 'Frame' event.+lastFrameTime :: Lens' UIState TimeSpec++-- | The amount of accumulated real time.  Every time we get a 'Frame'+--   event, we accumulate the amount of real time that happened since+--   the last frame, then attempt to take an appropriate number of+--   ticks to "catch up", based on the target tick rate.+--+--   See https://gafferongames.com/post/fix_your_timestep/ .+accumulatedTime :: Lens' UIState TimeSpec++-- | Free-form data loaded from the @data@ directory, for things like+--   the logo, about page, tutorial story, etc.+appData :: Lens' UIState (Map Text Text)++--------------------------------------------------+-- UIState initialization++-- | The initial state of the focus ring.+initFocusRing :: FocusRing Name+initFocusRing = focusRing $ map FocusablePanel listEnums++-- | The initial tick speed.+initLgTicksPerSecond :: Int+initLgTicksPerSecond = 4 -- 2^4 = 16 ticks / second++-- | Initialize the UI state.  This needs to be in the IO monad since+--   it involves reading a REPL history file, getting the current+--   time, and loading text files from the data directory.  The @Bool@+--   parameter indicates whether we should start off by showing the+--   main menu.+initUIState :: Bool -> Bool -> ExceptT Text IO ([SystemFailure], UIState)+initUIState showMainMenu cheatMode = do+  historyT <- liftIO $ readFileMayT =<< getSwarmHistoryPath False+  appDataMap <- liftIO readAppData+  let history = maybe [] (map REPLEntry . T.lines) historyT+  startTime <- liftIO $ getTime Monotonic+  (warnings, achievements) <- liftIO loadAchievementsInfo+  let out =+        UIState+          { _uiMenu = if showMainMenu then MainMenu (mainMenu NewGame) else NoMenu+          , _uiPlaying = not showMainMenu+          , _uiCheatMode = cheatMode+          , _uiFocusRing = initFocusRing+          , _uiWorldCursor = Nothing+          , _uiREPL = initREPLState $ newREPLHistory history+          , _uiInventory = Nothing+          , _uiInventorySort = defaultSortOptions+          , _uiMoreInfoTop = False+          , _uiMoreInfoBot = False+          , _uiScrollToEnd = False+          , _uiError = Nothing+          , _uiModal = Nothing+          , _uiGoal = emptyGoalDisplay+          , _uiAchievements = M.fromList $ map (view achievement &&& id) achievements+          , _uiShowFPS = False+          , _uiShowZero = True+          , _uiHideRobotsUntil = startTime - 1+          , _uiInventoryShouldUpdate = False+          , _uiTPF = 0+          , _uiFPS = 0+          , _lgTicksPerSecond = initLgTicksPerSecond+          , _lastFrameTime = startTime+          , _accumulatedTime = 0+          , _lastInfoTime = 0+          , _tickCount = 0+          , _frameCount = 0+          , _frameTickCount = 0+          , _appData = appDataMap+          , _uiAttrMap = swarmAttrMap+          , _scenarioRef = Nothing+          }+  return (warnings, out)
src/Swarm/TUI/View.hs view
@@ -38,7 +38,7 @@   drawREPL, ) where -import Brick hiding (Direction)+import Brick hiding (Direction, Location) import Brick.Focus import Brick.Forms import Brick.Widgets.Border (hBorder, hBorderWithLabel, joinableBorder, vBorder)@@ -49,7 +49,6 @@ import Brick.Widgets.Table qualified as BT import Control.Lens hiding (Const, from) import Control.Monad (guard)-import Control.Monad.Reader (withReaderT) import Data.Array (range) import Data.Bits (shiftL, shiftR, (.&.)) import Data.Foldable qualified as F@@ -68,8 +67,8 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Time (NominalDiffTime, defaultTimeLocale, formatTime)-import Graphics.Vty qualified as V import Linear+import Linear.Affine (Point) import Network.Wai.Handler.Warp (Port) import Swarm.Game.CESK (CESK (..)) import Swarm.Game.Display@@ -77,6 +76,8 @@ import Swarm.Game.Recipe import Swarm.Game.Robot import Swarm.Game.Scenario (scenarioAuthor, scenarioDescription, scenarioName, scenarioObjectives)+import Swarm.Game.Scenario.Objective.Presentation.Model (goalsContent, hasAnythingToShow)+import Swarm.Game.Scenario.Objective.Presentation.Render qualified as GR import Swarm.Game.ScenarioInfo (   ScenarioItem (..),   ScenarioStatus (..),@@ -86,18 +87,23 @@   scenarioStatus,  ) import Swarm.Game.State-import Swarm.Game.Terrain (terrainMap) import Swarm.Game.World qualified as W+import Swarm.Language.Capability (constCaps) import Swarm.Language.Pretty (prettyText) import Swarm.Language.Syntax import Swarm.Language.Typecheck (inferConst)-import Swarm.Language.Types (Polytype) import Swarm.TUI.Attr import Swarm.TUI.Border import Swarm.TUI.Inventory.Sorting (renderSortMethod) import Swarm.TUI.Model+import Swarm.TUI.Model.Repl+import Swarm.TUI.Model.UI import Swarm.TUI.Panel+import Swarm.TUI.View.Achievement+import Swarm.TUI.View.CellDisplay+import Swarm.TUI.View.Util import Swarm.Util+import Swarm.Util.Location import Swarm.Version (NewReleaseFailure (..)) import System.Clock (TimeSpec (..)) import Text.Printf@@ -110,19 +116,20 @@ drawUI s   | s ^. uiState . uiPlaying = drawGameUI s   | otherwise = case s ^. uiState . uiMenu of-    -- We should never reach the NoMenu case if uiPlaying is false; we would have-    -- quit the app instead.  But just in case, we display the main menu anyway.-    NoMenu -> [drawMainMenuUI s (mainMenu NewGame)]-    MainMenu l -> [drawMainMenuUI s l]-    NewGameMenu stk -> [drawNewGameMenuUI stk]-    MessagesMenu -> [drawMainMessages s]-    AboutMenu -> [drawAboutMenuUI (s ^. uiState . appData . at "about")]+      -- We should never reach the NoMenu case if uiPlaying is false; we would have+      -- quit the app instead.  But just in case, we display the main menu anyway.+      NoMenu -> [drawMainMenuUI s (mainMenu NewGame)]+      MainMenu l -> [drawMainMenuUI s l]+      NewGameMenu stk -> [drawNewGameMenuUI stk]+      AchievementsMenu l -> [drawAchievementsMenuUI s l]+      MessagesMenu -> [drawMainMessages s]+      AboutMenu -> [drawAboutMenuUI (s ^. uiState . appData . at "about")]  drawMainMessages :: AppState -> Widget Name drawMainMessages s = renderDialog dial . padBottom Max . scrollList $ drawLogs ls  where   ls = reverse $ s ^. runtimeState . eventLog . notificationsContent-  dial = dialog (Just "Messages") Nothing maxModalWindowWidth+  dial = dialog (Just $ str "Messages") Nothing maxModalWindowWidth   scrollList = withVScrollBars OnRight . vBox   drawLogs = map (drawLogEntry True) @@ -131,7 +138,7 @@   vBox . catMaybes $     [ drawLogo <$> logo     , hCenter . padTopBottom 2 <$> newVersionWidget version-    , Just . centerLayer . vLimit 5 . hLimit 20 $+    , Just . centerLayer . vLimit 6 . hLimit 20 $         BL.renderList (const (hCenter . drawMainMenuEntry s)) True l     ]  where@@ -169,7 +176,8 @@       [ vBox           [ withAttr boldAttr . txt $ breadcrumbs ls           , txt " "-          , vLimit 20 . hLimit 35+          , vLimit 20+              . hLimit 35               . BL.renderList (const $ padRight Max . drawScenarioItem) True               $ l           ]@@ -244,6 +252,7 @@ drawMainMenuEntry s = \case   NewGame -> txt "New game"   Tutorial -> txt "Tutorial"+  Achievements -> txt "Achievements"   About -> txt "About"   Messages -> highlightMessages $ txt "Messages"   Quit -> txt "Quit"@@ -271,16 +280,16 @@       hBox         [ hLimitPercent 25 $             vBox-              [ vLimitPercent 50 $ panel highlightAttr fr RobotPanel plainBorder $ drawRobotPanel s+              [ vLimitPercent 50 $ panel highlightAttr fr (FocusablePanel RobotPanel) plainBorder $ drawRobotPanel s               , panel                   highlightAttr                   fr-                  InfoPanel+                  (FocusablePanel InfoPanel)                   ( plainBorder                       & topLabels . centerLabel-                      .~ (if moreTop then Just (txt " · · · ") else Nothing)+                        .~ (if moreTop then Just (txt " · · · ") else Nothing)                       & bottomLabels . centerLabel-                      .~ (if moreBot then Just (txt " · · · ") else Nothing)+                        .~ (if moreBot then Just (txt " · · · ") else Nothing)                   )                   $ drawInfoPanel s               ]@@ -288,7 +297,7 @@             [ panel                 highlightAttr                 fr-                WorldPanel+                (FocusablePanel WorldPanel)                 ( plainBorder                     & bottomLabels . rightLabel ?~ padLeftRight 1 (drawTPS s)                     & topLabels . leftLabel ?~ drawModalMenu s@@ -297,11 +306,11 @@                 )                 (drawWorld (s ^. uiState . uiShowRobots) (s ^. gameState))             , drawKeyMenu s-            , clickable REPLPanel $+            , clickable (FocusablePanel REPLPanel) $                 panel                   highlightAttr                   fr-                  REPLPanel+                  (FocusablePanel REPLPanel)                   ( plainBorder                       & topLabels . rightLabel .~ (drawType <$> (s ^. uiState . uiREPL . replType))                   )@@ -317,19 +326,41 @@   addCursorPos = case s ^. uiState . uiWorldCursor of     Nothing -> id     Just coord ->-      let worlCursorInfo = drawWorldCursorInfo (s ^. uiState . uiShowRobots) (s ^. gameState) coord-       in bottomLabels . leftLabel ?~ padLeftRight 1 worlCursorInfo+      let worldCursorInfo = drawWorldCursorInfo (s ^. gameState) coord+       in bottomLabels . leftLabel ?~ padLeftRight 1 worldCursorInfo   -- Add clock display in top right of the world view if focused robot-  -- has a clock installed+  -- has a clock equipped   addClock = topLabels . rightLabel ?~ padLeftRight 1 (drawClockDisplay $ s ^. gameState)   fr = s ^. uiState . uiFocusRing   moreTop = s ^. uiState . uiMoreInfoTop   moreBot = s ^. uiState . uiMoreInfoBot -drawWorldCursorInfo :: Bool -> GameState -> W.Coords -> Widget Name-drawWorldCursorInfo showRobots g i@(W.Coords (y, x)) =-  hBox [drawLoc showRobots g i, txt $ " at " <> from (show x) <> " " <> from (show (y * (-1)))]+drawWorldCursorInfo :: GameState -> W.Coords -> Widget Name+drawWorldCursorInfo g coords@(W.Coords (y, x)) =+  hBox $ tileMemberWidgets ++ [coordsWidget]+ where+  coordsWidget =+    txt $+      T.unwords+        [ from $ show x+        , from $ show $ y * (-1)+        ] +  tileMembers = terrain : mapMaybe merge [entity, robot]+  tileMemberWidgets =+    map (padRight $ Pad 1) $+      concat $+        reverse $+          zipWith f tileMembers ["at", "on", "with"]+   where+    f cell preposition = [renderDisplay cell, txt preposition]++  terrain = displayTerrainCell g coords+  entity = displayEntityCell g coords+  robot = displayRobotCell g coords++  merge = fmap sconcat . NE.nonEmpty . filter (not . (^. invisible))+ -- | Format the clock display to be shown in the upper right of the --   world panel. drawClockDisplay :: GameState -> Widget n@@ -339,12 +370,12 @@   pauseWidget = guard (gs ^. paused) $> txt "(PAUSED)"  -- | Check whether the currently focused robot (if any) has a clock---   device installed.-clockInstalled :: GameState -> Bool-clockInstalled gs = case focusedRobot gs of+--   device equipped.+clockEquipped :: GameState -> Bool+clockEquipped gs = case focusedRobot gs of   Nothing -> False   Just r-    | countByName "clock" (r ^. installedDevices) > 0 -> True+    | countByName "clock" (r ^. equippedDevices) > 0 -> True     | otherwise -> False  -- | Format a ticks count as a hexadecimal clock.@@ -360,16 +391,12 @@       ++ if showTicks then [".", printf "%x" (t .&. ((1 `shiftL` 4) - 1))] else []  -- | Return a possible time display, if the currently focused robot---   has a clock device installed.  The first argument is the number+--   has a clock device equipped.  The first argument is the number --   of ticks (e.g. 943 = 0x3af), and the second argument indicates --   whether the time should be shown down to single-tick resolution --   (e.g. 0:00:3a.f) or not (e.g. 0:00:3a). maybeDrawTime :: Integer -> Bool -> GameState -> Maybe (Widget n)-maybeDrawTime t showTicks gs = guard (clockInstalled gs) $> drawTime t showTicks---- | Render the type of the current REPL input to be shown to the user.-drawType :: Polytype -> Widget Name-drawType = withAttr infoAttr . padLeftRight 1 . txt . prettyText+maybeDrawTime t showTicks gs = guard (clockEquipped gs) $> drawTime t showTicks  -- | Draw info about the current number of ticks per second. drawTPS :: AppState -> Widget Name@@ -381,12 +408,12 @@    rateInfo     | s ^. uiState . uiShowFPS =-      [ txt " ("-      , str (printf "%0.1f" (s ^. uiState . uiTPF))-      , txt " tpf, "-      , str (printf "%0.1f" (s ^. uiState . uiFPS))-      , txt " fps)"-      ]+        [ txt " ("+        , str (printf "%0.1f" (s ^. uiState . uiTPF))+        , txt " tpf, "+        , str (printf "%0.1f" (s ^. uiState . uiFPS))+        , txt " fps)"+        ]     | otherwise = []    l = s ^. uiState . lgTicksPerSecond@@ -403,13 +430,9 @@   Nothing -> showFirstCursor s locs   Just _ -> Nothing --- | Width cap for modal and error message windows-maxModalWindowWidth :: Int-maxModalWindowWidth = 500- -- | Render the error dialog window with a given error message renderErrorDialog :: Text -> Widget Name-renderErrorDialog err = renderDialog (dialog (Just "Error") Nothing (maxModalWindowWidth `min` requiredWidth)) errContent+renderErrorDialog err = renderDialog (dialog (Just $ str "Error") Nothing (maxModalWindowWidth `min` requiredWidth)) errContent  where   errContent = txtWrapWith indent2 {preserveIndentation = True} err   requiredWidth = 2 + maximum (textWidth <$> T.lines err)@@ -417,110 +440,33 @@ -- | Draw the error dialog window, if it should be displayed right now. drawDialog :: AppState -> Widget Name drawDialog s = case s ^. uiState . uiModal of-  Just (Modal mt d) -> renderDialog d (maybeScroll ModalViewport $ drawModal s mt)+  Just (Modal mt d) -> renderDialog d $ case mt of+    GoalModal -> drawModal s mt+    _ -> maybeScroll ModalViewport $ drawModal s mt   Nothing -> maybe emptyWidget renderErrorDialog (s ^. uiState . uiError) --- | Make a widget scrolling if it is bigger than the available---   vertical space.  Thanks to jtdaugherty for this code.-maybeScroll :: (Ord n, Show n) => n -> Widget n -> Widget n-maybeScroll vpName contents =-  Widget Greedy Greedy $ do-    ctx <- getContext-    result <- withReaderT (availHeightL .~ 10000) (render contents)-    if V.imageHeight (result ^. imageL) <= ctx ^. availHeightL-      then return result-      else-        render $-          withVScrollBars OnRight $-            viewport vpName Vertical $-              Widget Fixed Fixed $-                return result- -- | Draw one of the various types of modal dialog. drawModal :: AppState -> ModalType -> Widget Name drawModal s = \case   HelpModal -> helpWidget (s ^. gameState . seed) (s ^. runtimeState . webPort)   RobotsModal -> robotsListWidget s   RecipesModal -> availableListWidget (s ^. gameState) RecipeList-  CommandsModal -> availableListWidget (s ^. gameState) CommandList+  CommandsModal -> commandsListWidget (s ^. gameState)   MessagesModal -> availableListWidget (s ^. gameState) MessageList   WinModal -> padBottom (Pad 1) $ hCenter $ txt "Congratulations!"+  LoseModal ->+    padBottom (Pad 1) $+      vBox $+        map+          (hCenter . txt)+          [ "Condolences!"+          , "This scenario is no longer winnable."+          ]   DescriptionModal e -> descriptionWidget s e   QuitModal -> padBottom (Pad 1) $ hCenter $ txt (quitMsg (s ^. uiState . uiMenu))-  GoalModal g -> padLeftRight 1 (displayParagraphs g)+  GoalModal -> GR.renderGoalsDisplay (s ^. uiState . uiGoal)   KeepPlayingModal -> padLeftRight 1 (displayParagraphs ["Have fun!  Hit Ctrl-Q whenever you're ready to proceed to the next challenge or return to the menu."]) -quitMsg :: Menu -> Text-quitMsg m = "Are you sure you want to " <> quitAction <> "? All progress on this scenario will be lost!"- where-  quitAction = case m of-    NoMenu -> "quit"-    _ -> "return to the menu"---- | Generate a fresh modal window of the requested type.-generateModal :: AppState -> ModalType -> Modal-generateModal s mt = Modal mt (dialog (Just title) buttons (maxModalWindowWidth `min` requiredWidth))- where-  currentScenario = s ^. uiState . scenarioRef-  currentSeed = s ^. gameState . seed-  haltingMessage = case s ^. uiState . uiMenu of-    NoMenu -> Just "Quit"-    _ -> Nothing-  descriptionWidth = 100-  helpWidth = 80-  (title, buttons, requiredWidth) =-    case mt of-      HelpModal -> (" Help ", Nothing, helpWidth)-      RobotsModal -> ("Robots", Nothing, descriptionWidth)-      RecipesModal -> ("Available Recipes", Nothing, descriptionWidth)-      CommandsModal -> ("Available Commands", Nothing, descriptionWidth)-      MessagesModal -> ("Messages", Nothing, descriptionWidth)-      WinModal ->-        let nextMsg = "Next challenge!"-            stopMsg = fromMaybe "Return to the menu" haltingMessage-            continueMsg = "Keep playing"-         in ( ""-            , Just-                ( 0-                , [ (nextMsg, NextButton scene)-                  | Just scene <- [nextScenario (s ^. uiState . uiMenu)]-                  ]-                    ++ [ (stopMsg, QuitButton)-                       , (continueMsg, KeepPlayingButton)-                       ]-                )-            , sum (map length [nextMsg, stopMsg, continueMsg]) + 32-            )-      DescriptionModal e -> (descriptionTitle e, Nothing, descriptionWidth)-      QuitModal ->-        let stopMsg = fromMaybe ("Quit to" ++ maybe "" (" " ++) (into @String <$> curMenuName s) ++ " menu") haltingMessage-            maybeStartOver = sequenceA ("Start over", StartOverButton currentSeed <$> currentScenario)-         in ( ""-            , Just-                ( 0-                , catMaybes-                    [ Just ("Keep playing", CancelButton)-                    , maybeStartOver-                    , Just (stopMsg, QuitButton)-                    ]-                )-            , T.length (quitMsg (s ^. uiState . uiMenu)) + 4-            )-      GoalModal _ ->-        let goalModalTitle = case currentScenario of-              Nothing -> "Goal"-              Just (scenario, _) -> scenario ^. scenarioName-         in (" " <> T.unpack goalModalTitle <> " ", Nothing, 80)-      KeepPlayingModal -> ("", Just (0, [("OK", CancelButton)]), 80)---- | Get the name of the current New Game menu.-curMenuName :: AppState -> Maybe Text-curMenuName s = case s ^. uiState . uiMenu of-  NewGameMenu (_ :| (parentMenu : _)) ->-    Just (parentMenu ^. BL.listSelectedElementL . to scenarioItemName)-  NewGameMenu _ -> Just "Scenarios"-  _ -> Nothing- robotsListWidget :: AppState -> Widget Name robotsListWidget s = hCenter table  where@@ -571,7 +517,7 @@      locWidget = hBox [worldCell, txt $ " " <> locStr]      where-      rloc@(V2 x y) = robot ^. robotLocation+      rloc@(Location x y) = robot ^. robotLocation       worldCell = drawLoc (s ^. uiState . uiShowRobots) g (W.locToCoords rloc)       locStr = from (show x) <> " " <> from (show y) @@ -581,9 +527,9 @@         | isActive robot -> withAttr notifAttr $ txt "busy"         | otherwise -> withAttr greenAttr $ txt "idle" -  basePos :: V2 Double-  basePos = realToFrac <$> fromMaybe (V2 0 0) (g ^? baseRobot . robotLocation)-  -- Keep the base and non sytem robot (e.g. no seed)+  basePos :: Point V2 Double+  basePos = realToFrac <$> fromMaybe origin (g ^? baseRobot . robotLocation)+  -- Keep the base and non system robot (e.g. no seed)   isRelevant robot = robot ^. robotID == 0 || not (robot ^. systemRobot)   -- Keep the robot that are less than 32 unit away from the base   isNear robot = creative || distance (realToFrac <$> robot ^. robotLocation) basePos < 32@@ -649,18 +595,16 @@     , ("Meta-t", "focus on the info panel")     ] -data NotificationList = RecipeList | CommandList | MessageList+data NotificationList = RecipeList | MessageList  availableListWidget :: GameState -> NotificationList -> Widget Name availableListWidget gs nl = padTop (Pad 1) $ vBox widgetList  where   widgetList = case nl of     RecipeList -> mkAvailableList gs availableRecipes renderRecipe-    CommandList -> mkAvailableList gs availableCommands renderCommand & (<> constWiki) . (padLeftRight 18 constHeader :)     MessageList -> messagesWidget gs   renderRecipe = padLeftRight 18 . drawRecipe Nothing (fromMaybe E.empty inv)   inv = gs ^? to focusedRobot . _Just . robotInventory-  renderCommand = padLeftRight 18 . drawConst  mkAvailableList :: GameState -> Lens' GameState (Notifications a) -> (a -> Widget Name) -> [Widget Name] mkAvailableList gs notifLens notifRender = map padRender news <> notifSep <> map padRender knowns@@ -670,28 +614,59 @@   (news, knowns) = splitAt count (gs ^. notifLens . notificationsContent)   notifSep     | count > 0 && not (null knowns) =-      [ padBottom (Pad 1) (withAttr redAttr $ hBorderWithLabel (padLeftRight 1 (txt "new↑")))-      ]+        [ padBottom (Pad 1) (withAttr redAttr $ hBorderWithLabel (padLeftRight 1 (txt "new↑")))+        ]     | otherwise = [] -constHeader :: Widget Name-constHeader = padBottom (Pad 1) $ withAttr robotAttr $ padLeft (Pad 1) $ txt "command name : type"+commandsListWidget :: GameState -> Widget Name+commandsListWidget gs =+  hCenter $+    vBox+      [ table+      , padTop (Pad 1) $ txt "For the full list of available commands see the Wiki at:"+      , txt "https://github.com/swarm-game/swarm/wiki/Commands-Cheat-Sheet"+      ]+ where+  commands = gs ^. availableCommands . notificationsContent+  table =+    BT.renderTable+      . BT.surroundingBorder False+      . BT.columnBorders False+      . BT.rowBorders False+      . BT.setDefaultColAlignment BT.AlignLeft+      . BT.alignRight 0+      . BT.table+      $ headers : commandsTable+  headers =+    withAttr robotAttr+      <$> [ txt "command name"+          , txt " : type"+          , txt "Enabled by"+          ] -constWiki :: [Widget Name]-constWiki =-  padLeftRight 13-    <$> [ padTop (Pad 2) $ txt "For the full list of available commands see the Wiki at:"-        , txt "https://github.com/swarm-game/swarm/wiki/Commands-Cheat-Sheet"-        ]+  commandsTable = mkCmdRow <$> commands+  mkCmdRow cmd =+    map+      (padTop $ Pad 1)+      [ txt $ syntax $ constInfo cmd+      , padRight (Pad 2) $ txt $ " : " <> prettyText (inferConst cmd)+      , listDevices cmd+      ] -drawConst :: Const -> Widget Name-drawConst c = hBox [padLeft (Pad $ 13 - T.length constName) (txt constName), txt constSig]- where-  constName = syntax . constInfo $ c-  constSig = " : " <> prettyText (inferConst c)+  base = gs ^? baseRobot+  entsByCap = case base of+    Just r ->+      M.map NE.toList $+        entitiesByCapability $+          (r ^. equippedDevices) `union` (r ^. robotInventory)+    Nothing -> mempty -descriptionTitle :: Entity -> String-descriptionTitle e = " " ++ from @Text (e ^. entityName) ++ " "+  listDevices cmd = vBox $ map drawLabelledEntityName providerDevices+   where+    providerDevices =+      concatMap (flip (M.findWithDefault []) entsByCap) $+        maybeToList $+          constCaps cmd  -- | Generate a pop-up widget to display the description of an entity. descriptionWidget :: AppState -> Entity -> Widget Name@@ -713,10 +688,14 @@   txt2 = txtWrapWith indent2  colorLogs :: LogEntry -> AttrName-colorLogs e = case e ^. leSaid of+colorLogs e = case e ^. leSource of   Said -> robotColor (e ^. leRobotID)   Logged -> notifAttr-  ErrorTrace -> redAttr+  ErrorTrace l -> case l of+    Debug -> dimAttr+    Warning -> yellowAttr+    Error -> redAttr+    Critical -> redAttr  where   -- color each robot message with different color of the world   robotColor rid = fgCols !! (rid `mod` fgColLen)@@ -731,10 +710,10 @@   notificationKey notifLens key name     | null (s ^. gameState . notifLens . notificationsContent) = Nothing     | otherwise =-      let highlight-            | s ^. gameState . notifLens . notificationsCount > 0 = Alert-            | otherwise = NoHighlight-       in Just (highlight, key, name)+        let highlight+              | s ^. gameState . notifLens . notificationsCount > 0 = Alert+              | otherwise = NoHighlight+         in Just (highlight, key, name)    globalKeyCmds =     catMaybes@@ -752,30 +731,41 @@ -- This excludes the F-key modals that are shown elsewhere. drawKeyMenu :: AppState -> Widget Name drawKeyMenu s =-  vLimit 1-    . hBox-    . (++ [gameModeWidget])-    . map (padLeftRight 1 . drawKeyCmd)-    . (globalKeyCmds ++)-    . map highlightKeyCmds-    . keyCmdsFor-    . focusGetCurrent-    . view (uiState . uiFocusRing)-    $ s+  vLimit 2 $+    hBox+      [ vBox+          [ mkCmdRow globalKeyCmds+          , padLeft (Pad 2) $ mkCmdRow focusedPanelCmds+          ]+      , gameModeWidget+      ]  where+  mkCmdRow = hBox . map drawPaddedCmd+  drawPaddedCmd = padLeftRight 1 . drawKeyCmd+  focusedPanelCmds =+    map highlightKeyCmds $+      keyCmdsFor $+        focusGetCurrent $+          view (uiState . uiFocusRing) s+   isReplWorking = s ^. gameState . replWorking   isPaused = s ^. gameState . paused   viewingBase = (s ^. gameState . viewCenterRule) == VCRobot 0   creative = s ^. gameState . creativeMode   cheat = s ^. uiState . uiCheatMode-  goal = case s ^. uiState . uiGoal of-    Just g | g /= [] -> True-    _ -> False+  goal = hasAnythingToShow $ s ^. uiState . uiGoal . goalsContent   showZero = s ^. uiState . uiShowZero   inventorySort = s ^. uiState . uiInventorySort+  ctrlMode = s ^. uiState . uiREPL . replControlMode +  renderControlModeSwitch :: ReplControlMode -> T.Text+  renderControlModeSwitch = \case+    Piloting -> "REPL"+    Typing -> "pilot"+   gameModeWidget =-    padLeft Max . padLeftRight 1+    padLeft Max+      . padLeftRight 1       . txt       . (<> " mode")       $ case creative of@@ -796,23 +786,24 @@     "pop out" | (s ^. uiState . uiMoreInfoBot) || (s ^. uiState . uiMoreInfoTop) -> Alert     _ -> PanelSpecific -  keyCmdsFor (Just REPLPanel) =+  keyCmdsFor (Just (FocusablePanel REPLPanel)) =     [ ("↓↑", "history")     ]       ++ [("Enter", "execute") | not isReplWorking]       ++ [("^c", "cancel") | isReplWorking]-  keyCmdsFor (Just WorldPanel) =+      ++ [("M-p", renderControlModeSwitch ctrlMode) | creative]+  keyCmdsFor (Just (FocusablePanel WorldPanel)) =     [ ("←↓↑→ / hjkl", "scroll") | creative     ]       ++ [("c", "recenter") | not viewingBase]       ++ [("f", "FPS")]-  keyCmdsFor (Just RobotPanel) =+  keyCmdsFor (Just (FocusablePanel RobotPanel)) =     [ ("Enter", "pop out")     , ("m", "make")     , ("0", (if showZero then "hide" else "show") <> " 0")     , (":/;", T.unwords ["Sort:", renderSortMethod inventorySort])     ]-  keyCmdsFor (Just InfoPanel) = []+  keyCmdsFor (Just (FocusablePanel InfoPanel)) = []   keyCmdsFor _ = []  data KeyHighlight = NoHighlight | Alert | PanelSpecific@@ -841,7 +832,7 @@     . cached WorldCache     . reportExtent WorldExtent     -- Set the clickable request after the extent to play nice with the cache-    . clickable WorldPanel+    . clickable (FocusablePanel WorldPanel)     . Widget Fixed Fixed     $ do       ctx <- getContext@@ -850,41 +841,6 @@           ixs = range (viewingRegion g (fromIntegral w, fromIntegral h))       render . vBox . map hBox . chunksOf w . map (drawLoc showRobots g) $ ixs --- | Render the 'Display' for a specific location.-drawLoc :: Bool -> GameState -> W.Coords -> Widget Name-drawLoc showRobots g = renderDisplay . displayLoc showRobots g---- | Get the 'Display' for a specific location, by combining the---   'Display's for the terrain, entity, and robots at the location.-displayLoc :: Bool -> GameState -> W.Coords -> Display-displayLoc showRobots g coords =-  sconcat $ terrain NE.:| entity <> robots- where-  terrain = terrainMap M.! toEnum (W.lookupTerrain coords (g ^. world))-  entity = maybeToList (displayForEntity <$> W.lookupEntity coords (g ^. world))-  robots =-    if showRobots-      then map (view robotDisplay) (robotsAtLocation (W.coordsToLoc coords) g)-      else []--  displayForEntity :: Entity -> Display-  displayForEntity e = (if known e then id else hidden) (e ^. entityDisplay)--  known e =-    e `hasProperty` Known-      || (e ^. entityName) `elem` (g ^. knownEntities)-      || case hidingMode g of-        HideAllEntities -> False-        HideNoEntity -> True-        HideEntityUnknownTo ro -> ro `robotKnows` e--data HideEntity = HideAllEntities | HideNoEntity | HideEntityUnknownTo Robot--hidingMode :: GameState -> HideEntity-hidingMode g-  | g ^. creativeMode = HideNoEntity-  | otherwise = maybe HideAllEntities HideEntityUnknownTo $ focusedRobot g- ------------------------------------------------------------ -- Robot inventory panel ------------------------------------------------------------@@ -894,7 +850,7 @@ drawRobotPanel :: AppState -> Widget Name drawRobotPanel s = case (s ^. gameState . to focusedRobot, s ^. uiState . uiInventory) of   (Just r, Just (_, lst)) ->-    let V2 x y = r ^. robotLocation+    let Location x y = r ^. robotLocation         drawClickableItem pos selb = clickable (InventoryListItem pos) . drawItem (lst ^. BL.listSelectedL) pos selb      in padBottom Max $           vBox@@ -931,7 +887,7 @@ drawItem _ _ _ (InventoryEntry n e) = drawLabelledEntityName e <+> showCount n  where   showCount = padLeft Max . str . show-drawItem _ _ _ (InstalledEntry e) = drawLabelledEntityName e <+> padLeft Max (str " ")+drawItem _ _ _ (EquippedEntry e) = drawLabelledEntityName e <+> padLeft Max (str " ")  -- | Draw the name of an entity, labelled with its visual --   representation as a cell in the world.@@ -959,9 +915,9 @@ explainFocusedItem :: AppState -> Widget Name explainFocusedItem s = case focusedItem s of   Just (InventoryEntry _ e) -> explainEntry s e-  Just (InstalledEntry e) ->+  Just (EquippedEntry e) ->     explainEntry s e-      -- Special case: installed logger device displays the robot's log.+      -- Special case: equipped logger device displays the robot's log.       <=> if e ^. entityName == "logger" then drawRobotLog s else emptyWidget   _ -> txt " " @@ -998,13 +954,13 @@ explainRecipes s e   | null recipes = emptyWidget   | otherwise =-    vBox-      [ padBottom (Pad 1) (hBorderWithLabel (txt "Recipes"))-      , padLeftRight 2 $-          hCenter $-            vBox $-              map (hLimit widthLimit . padBottom (Pad 1) . drawRecipe (Just e) inv) recipes-      ]+      vBox+        [ padBottom (Pad 1) (hBorderWithLabel (txt "Recipes"))+        , padLeftRight 2 $+            hCenter $+              vBox $+                map (hLimit widthLimit . padBottom (Pad 1) . drawRecipe (Just e) inv) recipes+        ]  where   recipes = recipesWith s e @@ -1059,11 +1015,11 @@   connector     | null reqs = hLimit 5 hBorder     | otherwise =-      hBox-        [ hLimit 2 hBorder-        , joinableBorder (Edges True False True True)-        , hLimit 2 hBorder-        ]+        hBox+          [ hLimit 2 hBorder+          , joinableBorder (Edges True False True True)+          , hLimit 2 hBorder+          ]   inLen = length ins + length times   outLen = length outs   times = [(fromIntegral time, timeE) | time /= 1]@@ -1127,18 +1083,13 @@ drawRobotLog s =   vBox     [ padBottom (Pad 1) (hBorderWithLabel (txt "Log"))-    , vBox . imap drawEntry $ logEntries+    , vBox . F.toList . imap drawEntry $ logEntries     ]  where-  logEntries =-    s-      & view (gameState . to focusedRobot . _Just . robotLog)-      & Seq.sort-      & F.toList-      & uniq+  logEntries = s ^. gameState . to focusedRobot . _Just . robotLog    rn = s ^? gameState . to focusedRobot . _Just . robotName-  n = length logEntries+  n = Seq.length logEntries    allMe = all ((== rn) . Just . view leRobotName) logEntries @@ -1151,7 +1102,7 @@ drawLogEntry addName e = withAttr (colorLogs e) . txtWrapWith indent2 $ if addName then name else t  where   t = e ^. leText-  name = "[" <> view leRobotName e <> "] " <> (if e ^. leSaid == Said then "said " <> quote t else t)+  name = "[" <> view leRobotName e <> "] " <> (if e ^. leSource == Said then "said " <> quote t else t)  ------------------------------------------------------------ -- REPL panel@@ -1177,7 +1128,7 @@   replE =     renderEditor       (color . vBox . map txt)-      (focusGetCurrent focus `elem` [Nothing, Just REPLPanel, Just REPLInput])+      (focusGetCurrent focus `elem` [Nothing, Just (FocusablePanel REPLPanel), Just REPLInput])       replEditor  -- | Draw the REPL.@@ -1196,12 +1147,3 @@   base = s ^. gameState . robotMap . at 0   fmt (REPLEntry e) = txt $ "> " <> e   fmt (REPLOutput t) = txt t----------------------------------------------------------------- Utility----------------------------------------------------------------- | Display a list of text-wrapped paragraphs with one blank line after---   each.-displayParagraphs :: [Text] -> Widget Name-displayParagraphs = vBox . map (padBottom (Pad 1) . txtWrap)
+ src/Swarm/TUI/View/Achievement.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.TUI.View.Achievement where++import Brick+import Brick.Widgets.Border (borderWithLabel)+import Brick.Widgets.Center (hCenter)+import Brick.Widgets.List qualified as BL+import Control.Lens ((^.))+import Data.Map (Map)+import Data.Map qualified as M+import Data.Time.Format (defaultTimeLocale, formatTime)+import Swarm.TUI.Attr+import Swarm.TUI.Model+import Swarm.TUI.Model.Achievement.Attainment+import Swarm.TUI.Model.Achievement.Definitions+import Swarm.TUI.Model.Achievement.Description+import Swarm.TUI.Model.UI+import Text.Wrap++padAllEvenly :: Int -> Widget Name -> Widget Name+padAllEvenly x w = padTopBottom x $ padLeftRight (2 * x) w++getCompletionIcon :: Bool -> Widget Name+getCompletionIcon = \case+  False -> txt " ○  "+  True -> withAttr greenAttr $ txt " ●  "++drawAchievementsMenuUI :: AppState -> BL.List Name CategorizedAchievement -> Widget Name+drawAchievementsMenuUI s l =+  vBox+    [ hCenter $ padTopBottom 1 $ str "🏆  Achievements 🏆 "+    , hCenter $+        hBox+          [ hLimitPercent 30 $+              padAll 2 $+                BL.renderList (const $ drawAchievementListItem attainedMap) True l+          , hLimitPercent 50 $+              maybe emptyWidget (singleAchievementDetails attainedMap . snd) $+                BL.listSelectedElement l+          ]+    ]+ where+  attainedMap = s ^. uiState . uiAchievements++drawAchievementListItem ::+  Map CategorizedAchievement Attainment ->+  CategorizedAchievement ->+  Widget Name+drawAchievementListItem attainedMap x =+  getCompletionIcon wasAttained <+> titleWidget+ where+  wasAttained = M.member x attainedMap+  titleWidget = txtWrap $ title details+  details = describe x++singleAchievementDetails ::+  Map CategorizedAchievement Attainment ->+  CategorizedAchievement ->+  Widget Name+singleAchievementDetails attainedMap x =+  padRight (Pad 1) $ borderWithLabel titleWidget $ padAllEvenly 1 innerContent+ where+  wasAttained = M.member x attainedMap++  renderFlavorTextWidget :: FlavorText -> Widget Name+  renderFlavorTextWidget (Freeform t) = txtWrap t+  renderFlavorTextWidget (FTQuotation (Quotation author quoteContent)) =+    vBox+      [ txtWrap quoteContent+      , padLeft Max $+          padRight (Pad 2) $+            txtWrapWith (defaultWrapSettings {fillStrategy = FillIndent 2}) $+              "--" <> author+      ]++  innerContent =+    vBox+      [ maybe emptyWidget (padAllEvenly 2 . renderFlavorTextWidget) $ humorousElaboration details+      , txtWrap $+          if wasAttained || not (isObfuscated details)+            then attainmentProcess details+            else "???"+      , case M.lookup x attainedMap of+          Nothing -> emptyWidget+          Just attainment ->+            padTop (Pad 1) $+              hBox+                [ txt "Obtained: "+                , withAttr cyanAttr $+                    str $+                      formatTime defaultTimeLocale "%l:%M%P on %b %e, %Y" $+                        attainment ^. obtainedAt+                ]+      , padTop (Pad 1) $+          hBox+            [ txt "Effort: "+            , withAttr boldAttr $ str $ show $ effort details+            ]+      ]++  titleWidget = padLeftRight 1 $ txt $ title details+  details = describe x
+ src/Swarm/TUI/View/CellDisplay.hs view
@@ -0,0 +1,59 @@+module Swarm.TUI.View.CellDisplay where++import Brick+import Control.Lens+import Data.List.NonEmpty qualified as NE+import Data.Map qualified as M+import Data.Maybe (maybeToList)+import Data.Semigroup (sconcat)+import Swarm.Game.Display+import Swarm.Game.Entity as E+import Swarm.Game.Robot+import Swarm.Game.State+import Swarm.Game.Terrain (terrainMap)+import Swarm.Game.World qualified as W+import Swarm.TUI.Model.Name++-- | Render the 'Display' for a specific location.+drawLoc :: Bool -> GameState -> W.Coords -> Widget Name+drawLoc showRobots g = renderDisplay . displayLoc showRobots g++displayTerrainCell :: GameState -> W.Coords -> Display+displayTerrainCell g coords = terrainMap M.! toEnum (W.lookupTerrain coords (g ^. world))++displayEntityCell, displayRobotCell :: GameState -> W.Coords -> [Display]+displayRobotCell g coords = map (view robotDisplay) (robotsAtLocation (W.coordsToLoc coords) g)+displayEntityCell g coords = maybeToList (displayForEntity <$> W.lookupEntity coords (g ^. world))+ where+  displayForEntity :: Entity -> Display+  displayForEntity e = (if known e then id else hidden) (e ^. entityDisplay)++  known e =+    e+      `hasProperty` Known+      || (e ^. entityName)+      `elem` (g ^. knownEntities)+      || case hidingMode g of+        HideAllEntities -> False+        HideNoEntity -> True+        HideEntityUnknownTo ro -> ro `robotKnows` e++-- | Get the 'Display' for a specific location, by combining the+--   'Display's for the terrain, entity, and robots at the location.+displayLoc :: Bool -> GameState -> W.Coords -> Display+displayLoc showRobots g coords =+  sconcat $ terrain NE.:| entity <> robots+ where+  terrain = displayTerrainCell g coords+  entity = displayEntityCell g coords+  robots =+    if showRobots+      then displayRobotCell g coords+      else []++data HideEntity = HideAllEntities | HideNoEntity | HideEntityUnknownTo Robot++hidingMode :: GameState -> HideEntity+hidingMode g+  | g ^. creativeMode = HideNoEntity+  | otherwise = maybe HideAllEntities HideEntityUnknownTo $ focusedRobot g
+ src/Swarm/TUI/View/CustomStyling.hs view
@@ -0,0 +1,37 @@+module Swarm.TUI.View.CustomStyling where++import Brick (AttrName, attrName)+import Data.Set (toList)+import Data.Text qualified as T+import Graphics.Vty.Attributes+import Numeric (readHex)+import Swarm.Game.Scenario.Style+import Swarm.TUI.Attr (worldPrefix)++toStyle :: StyleFlag -> Style+toStyle = \case+  Standout -> standout+  Italic -> italic+  Strikethrough -> strikethrough+  Underline -> underline+  ReverseVideo -> reverseVideo+  Blink -> blink+  Dim -> dim+  Bold -> bold++toAttrColor :: HexColor -> Color+toAttrColor (HexColor colorText) =+  case nums of+    [r, g, b] -> RGBColor r g b+    _ -> RGBColor 255 255 255+ where+  chunks = T.chunksOf 2 $ T.dropWhile (== '#') colorText+  nums = map (fst . head . readHex . T.unpack) chunks++toAttrPair :: CustomAttr -> (AttrName, Attr)+toAttrPair ca =+  (worldPrefix <> attrName (name ca), addStyle $ addFg $ addBg currentAttr)+ where+  addFg = maybe id (flip withForeColor . toAttrColor) $ fg ca+  addBg = maybe id (flip withBackColor . toAttrColor) $ bg ca+  addStyle = maybe id (flip withStyle . sum . map toStyle . toList) $ style ca
+ src/Swarm/TUI/View/Util.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE OverloadedStrings #-}++module Swarm.TUI.View.Util where++import Brick hiding (Direction)+import Brick.Widgets.Dialog+import Brick.Widgets.List qualified as BL+import Control.Lens hiding (Const, from)+import Control.Monad.Reader (withReaderT)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Maybe (catMaybes, fromMaybe)+import Data.Text (Text)+import Data.Text qualified as T+import Graphics.Vty qualified as V+import Swarm.Game.Entity as E+import Swarm.Game.Scenario (scenarioName)+import Swarm.Game.ScenarioInfo (scenarioItemName)+import Swarm.Game.State+import Swarm.Language.Pretty (prettyText)+import Swarm.Language.Types (Polytype)+import Swarm.TUI.Attr+import Swarm.TUI.Model+import Swarm.TUI.Model.UI+import Witch (from, into)++-- | Generate a fresh modal window of the requested type.+generateModal :: AppState -> ModalType -> Modal+generateModal s mt = Modal mt (dialog (Just $ str title) buttons (maxModalWindowWidth `min` requiredWidth))+ where+  currentScenario = s ^. uiState . scenarioRef+  currentSeed = s ^. gameState . seed+  haltingMessage = case s ^. uiState . uiMenu of+    NoMenu -> Just "Quit"+    _ -> Nothing+  descriptionWidth = 100+  helpWidth = 80+  (title, buttons, requiredWidth) =+    case mt of+      HelpModal -> (" Help ", Nothing, helpWidth)+      RobotsModal -> ("Robots", Nothing, descriptionWidth)+      RecipesModal -> ("Available Recipes", Nothing, descriptionWidth)+      CommandsModal -> ("Available Commands", Nothing, descriptionWidth)+      MessagesModal -> ("Messages", Nothing, descriptionWidth)+      WinModal ->+        let nextMsg = "Next challenge!"+            stopMsg = fromMaybe "Return to the menu" haltingMessage+            continueMsg = "Keep playing"+         in ( ""+            , Just+                ( Button NextButton+                , [ (nextMsg, Button NextButton, Next scene)+                  | Just scene <- [nextScenario (s ^. uiState . uiMenu)]+                  ]+                    ++ [ (stopMsg, Button QuitButton, QuitAction)+                       , (continueMsg, Button KeepPlayingButton, KeepPlaying)+                       ]+                )+            , sum (map length [nextMsg, stopMsg, continueMsg]) + 32+            )+      LoseModal ->+        let stopMsg = fromMaybe "Return to the menu" haltingMessage+            continueMsg = "Keep playing"+            maybeStartOver = do+              cs <- currentScenario+              return ("Start over", Button StartOverButton, StartOver currentSeed cs)+         in ( ""+            , Just+                ( Button QuitButton+                , catMaybes+                    [ Just (stopMsg, Button QuitButton, QuitAction)+                    , maybeStartOver+                    , Just (continueMsg, Button KeepPlayingButton, KeepPlaying)+                    ]+                )+            , sum (map length [stopMsg, continueMsg]) + 32+            )+      DescriptionModal e -> (descriptionTitle e, Nothing, descriptionWidth)+      QuitModal ->+        let stopMsg = fromMaybe ("Quit to" ++ maybe "" (" " ++) (into @String <$> curMenuName s) ++ " menu") haltingMessage+            maybeStartOver = do+              cs <- currentScenario+              return ("Start over", Button StartOverButton, StartOver currentSeed cs)+         in ( ""+            , Just+                ( Button CancelButton+                , catMaybes+                    [ Just ("Keep playing", Button CancelButton, Cancel)+                    , maybeStartOver+                    , Just (stopMsg, Button QuitButton, QuitAction)+                    ]+                )+            , T.length (quitMsg (s ^. uiState . uiMenu)) + 4+            )+      GoalModal ->+        let goalModalTitle = case currentScenario of+              Nothing -> "Goal"+              Just (scenario, _) -> scenario ^. scenarioName+         in (" " <> T.unpack goalModalTitle <> " ", Nothing, descriptionWidth)+      KeepPlayingModal -> ("", Just (Button CancelButton, [("OK", Button CancelButton, Cancel)]), 80)++-- | Render the type of the current REPL input to be shown to the user.+drawType :: Polytype -> Widget Name+drawType = withAttr infoAttr . padLeftRight 1 . txt . prettyText++descriptionTitle :: Entity -> String+descriptionTitle e = " " ++ from @Text (e ^. entityName) ++ " "++-- | Width cap for modal and error message windows+maxModalWindowWidth :: Int+maxModalWindowWidth = 500++-- | Get the name of the current New Game menu.+curMenuName :: AppState -> Maybe Text+curMenuName s = case s ^. uiState . uiMenu of+  NewGameMenu (_ :| (parentMenu : _)) ->+    Just (parentMenu ^. BL.listSelectedElementL . to scenarioItemName)+  NewGameMenu _ -> Just "Scenarios"+  _ -> Nothing++quitMsg :: Menu -> Text+quitMsg m = "Are you sure you want to " <> quitAction <> "? All progress on this scenario will be lost!"+ where+  quitAction = case m of+    NoMenu -> "quit"+    _ -> "return to the menu"++-- | Display a list of text-wrapped paragraphs with one blank line after+--   each.+displayParagraphs :: [Text] -> Widget Name+displayParagraphs = vBox . map (padBottom (Pad 1) . txtWrap)++withEllipsis :: Text -> Widget Name+withEllipsis t =+  Widget Greedy Fixed $ do+    ctx <- getContext+    let w = ctx ^. availWidthL+        ellipsis = T.replicate 3 $ T.singleton '.'+        tLength = T.length t+        newText =+          if tLength > w+            then T.take (w - T.length ellipsis) t <> ellipsis+            else t+    render $ txt newText++-- | Make a widget scrolling if it is bigger than the available+--   vertical space.  Thanks to jtdaugherty for this code.+maybeScroll :: (Ord n, Show n) => n -> Widget n -> Widget n+maybeScroll vpName contents =+  Widget Greedy Greedy $ do+    ctx <- getContext+    result <- withReaderT (availHeightL .~ 10000) (render contents)+    if V.imageHeight (result ^. imageL) <= ctx ^. availHeightL+      then return result+      else+        render $+          withVScrollBars OnRight $+            viewport vpName Vertical $+              Widget Fixed Fixed $+                return result
src/Swarm/Util.hs view
@@ -17,17 +17,23 @@   maxOn,   maximum0,   cycleEnum,+  listEnums,   uniq,   getElemsInArea,   manhattan,+  binTuples,    -- * Directory utilities   readFileMay,   readFileMayT,-  getSwarmDataPath,+  getSwarmXdgDataSubdir,+  getSwarmXdgDataFile,   getSwarmSavePath,   getSwarmHistoryPath,   readAppData,+  getDataDirSafe,+  getDataFileNameSafe,+  dataNotFound,    -- * Text utilities   isIdentChar,@@ -37,6 +43,7 @@   reflow,   quote,   squote,+  bquote,   commaList,   indefinite,   indefiniteQ,@@ -63,9 +70,6 @@    -- * Utilities for NP-hard approximation   smallHittingSet,-  getDataDirSafe,-  getDataFileNameSafe,-  dataNotFound, ) where  import Control.Algebra (Has)@@ -76,13 +80,13 @@ import Control.Lens (ASetter', Lens', LensLike, LensLike', Over, lens, (<>~)) import Control.Lens.Lens ((&)) import Control.Monad (forM, unless, when)-import Data.Aeson (FromJSONKey, ToJSONKey) import Data.Bifunctor (first) import Data.Char (isAlphaNum) import Data.Either.Validation-import Data.Int (Int64)+import Data.Int (Int32) import Data.List (maximumBy, partition) import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE import Data.Map (Map) import Data.Map qualified as M import Data.Maybe (fromMaybe, mapMaybe)@@ -96,10 +100,10 @@ import Data.Yaml import Language.Haskell.TH import Language.Haskell.TH.Syntax (lift)-import Linear (V2 (V2)) import NLP.Minimorph.English qualified as MM import NLP.Minimorph.Util ((<+>)) import Paths_swarm (getDataDir)+import Swarm.Util.Location import System.Clock (TimeSpec) import System.Directory (   XdgDirectory (XdgData),@@ -116,7 +120,7 @@  -- $setup -- >>> import qualified Data.Map as M--- >>> import Linear.V2+-- >>> import Swarm.Util.Location  infixr 1 ? infix 4 %%=, <+=, <%=, <<.=, <>=@@ -148,6 +152,9 @@   | e == maxBound = minBound   | otherwise = succ e +listEnums :: (Enum e, Bounded e) => [e]+listEnums = [minBound .. maxBound]+ -- | Drop repeated elements that are adjacent to each other. -- -- >>> uniq []@@ -164,21 +171,21 @@   (x : xs) -> x : uniq (dropWhile (== x) xs)  -- | Manhattan distance between world locations.-manhattan :: V2 Int64 -> V2 Int64 -> Int64-manhattan (V2 x1 y1) (V2 x2 y2) = abs (x1 - x2) + abs (y1 - y2)+manhattan :: Location -> Location -> Int32+manhattan (Location x1 y1) (Location x2 y2) = abs (x1 - x2) + abs (y1 - y2)  -- | Get elements that are in manhattan distance from location. ----- >>> v2s i = [(v, manhattan (V2 0 0) v) | x <- [-i..i], y <- [-i..i], let v = V2 x y]+-- >>> v2s i = [(p, manhattan origin p) | x <- [-i..i], y <- [-i..i], let p = Location x y] -- >>> v2s 0--- [(V2 0 0,0)]--- >>> map (\i -> length (getElemsInArea (V2 0 0) i (M.fromList $ v2s i))) [0..8]+-- [(P (V2 0 0),0)]+-- >>> map (\i -> length (getElemsInArea origin i (M.fromList $ v2s i))) [0..8] -- [1,5,13,25,41,61,85,113,145] -- -- The last test is the sequence "Centered square numbers": -- https://oeis.org/A001844-getElemsInArea :: V2 Int64 -> Int64 -> Map (V2 Int64) e -> [e]-getElemsInArea o@(V2 x y) d m = M.elems sm'+getElemsInArea :: Location -> Int32 -> Map Location e -> [e]+getElemsInArea o@(Location x y) d m = M.elems sm'  where   -- to be more efficient we basically split on first coordinate   -- (which is logarithmic) and then we have to linearly filter@@ -194,12 +201,22 @@   --          ▼▼▼▼   sm =     m-      & M.split (V2 (x - d) (y - 1)) -- A+      & M.split (Location (x - d) (y - 1)) -- A       & snd -- A<-      & M.split (V2 (x + d) (y + 1)) -- B+      & M.split (Location (x + d) (y + 1)) -- B       & fst -- B>   sm' = M.filterWithKey (const . (<= d) . manhattan o) sm +-- | Place the second element of the tuples into bins by+-- the value of the first element.+binTuples ::+  (Foldable t, Ord a) =>+  t (a, b) ->+  Map a (NE.NonEmpty b)+binTuples = foldr f mempty+ where+  f = uncurry (M.insertWith (<>)) . fmap pure+ ------------------------------------------------------------ -- Directory stuff @@ -215,23 +232,33 @@ catchIO :: IO a -> IO (Maybe a) catchIO act = (Just <$> act) `catchIOError` (\_ -> return Nothing) +-- | Get subdirectory from swarm data directory.+--+-- This will first look in Cabal generated path and then+-- try a `data` directory in 'XdgData' path.+--+-- The idea is that when installing with Cabal/Stack the first+-- is preferred, but when the players install a binary they+-- need to extract the `data` archive to the XDG directory. getDataDirSafe :: FilePath -> IO (Maybe FilePath) getDataDirSafe p = do-  d <- mySubdir <$> getDataDir+  d <- (`appDir` p) <$> getDataDir   de <- doesDirectoryExist d   if de     then return $ Just d     else do-      xd <- mySubdir . (</> "data") <$> getSwarmDataPath False+      xd <- (`appDir` p) <$> getSwarmXdgDataSubdir False "data"       xde <- doesDirectoryExist xd       return $ if xde then Just xd else Nothing  where-  mySubdir d = d `appDir` p   appDir r = \case     "" -> r     "." -> r     d -> r </> d +-- | Get file from swarm data directory.+--+-- See the note in 'getDataDirSafe'. getDataFileNameSafe :: FilePath -> IO (Maybe FilePath) getDataFileNameSafe name = do   dir <- getDataDirSafe "."@@ -242,38 +269,41 @@       fe <- doesFileExist fp       return $ if fe then Just fp else Nothing +-- | Get a nice message suggesting to download `data` directory to 'XdgData'. dataNotFound :: FilePath -> IO Text dataNotFound f = do-  d <- getSwarmDataPath False+  d <- getSwarmXdgDataSubdir False ""   let squotes = squote . T.pack   return $     T.unlines       [ "Could not find the data: " <> squotes f-      , "Try downloading the Swarm 'data' directory to: " <> squotes d+      , "Try downloading the Swarm 'data' directory to: " <> squotes (d </> "data")       ]  -- | Get path to swarm data, optionally creating necessary---   directories.-getSwarmDataPath :: Bool -> IO FilePath-getSwarmDataPath createDirs = do-  swarmData <- getXdgDirectory XdgData "swarm"+--   directories. This could fail if user has bad permissions+--   on his own $HOME or $XDG_DATA_HOME which is unlikely.+getSwarmXdgDataSubdir :: Bool -> FilePath -> IO FilePath+getSwarmXdgDataSubdir createDirs subDir = do+  swarmData <- (</> subDir) <$> getXdgDirectory XdgData "swarm"   when createDirs (createDirectoryIfMissing True swarmData)   pure swarmData +getSwarmXdgDataFile :: Bool -> FilePath -> IO FilePath+getSwarmXdgDataFile createDirs filepath = do+  let (subDir, file) = splitFileName filepath+  d <- getSwarmXdgDataSubdir createDirs subDir+  return $ d </> file+ -- | Get path to swarm saves, optionally creating necessary --   directories. getSwarmSavePath :: Bool -> IO FilePath-getSwarmSavePath createDirs = do-  swarmSave <- getXdgDirectory XdgData ("swarm" </> "saves")-  when createDirs (createDirectoryIfMissing True swarmSave)-  pure swarmSave+getSwarmSavePath createDirs = getSwarmXdgDataSubdir createDirs "saves"  -- | Get path to swarm history, optionally creating necessary---   directories. This could fail if user has bad permissions---   on his own $HOME or $XDG_DATA_HOME which is unlikely.+--   directories. getSwarmHistoryPath :: Bool -> IO FilePath-getSwarmHistoryPath createDirs =-  (</> "history") <$> getSwarmDataPath createDirs+getSwarmHistoryPath createDirs = getSwarmXdgDataFile createDirs "history"  -- | Read all the .txt files in the data/ directory. readAppData :: IO (Map Text Text)@@ -346,9 +376,9 @@ singularSubjectVerb :: Text -> Text -> Text singularSubjectVerb sub verb   | verb == "be" = case toUpper sub of-    "I" -> "I am"-    "YOU" -> sub <+> "are"-    _ -> sub <+> "is"+      "I" -> "I am"+      "YOU" -> sub <+> "are"+      _ -> sub <+> "is"   | otherwise = sub <+> (if is3rdPerson then verb3rd else verb)  where   is3rdPerson = toUpper sub `notElem` ["I", "YOU"]@@ -379,6 +409,10 @@ quote :: Text -> Text quote t = T.concat ["\"", t, "\""] +-- | Surround some text in backticks.+bquote :: Text -> Text+bquote t = T.concat ["`", t, "`"]+ -- | Make a list of things with commas and the word "and". commaList :: [Text] -> Text commaList [] = ""@@ -388,12 +422,6 @@  ------------------------------------------------------------ -- Some orphan instances--deriving instance ToJSON (V2 Int64)-deriving instance FromJSON (V2 Int64)--deriving instance FromJSONKey (V2 Int64)-deriving instance ToJSONKey (V2 Int64)  deriving instance FromJSON TimeSpec deriving instance ToJSON TimeSpec
+ src/Swarm/Util/Location.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE PatternSynonyms #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- Orphan JSON instances for Location and Heading++-- |+-- Module      :  Swarm.Util.Location+-- Copyright   :  Brent Yorgey+-- Maintainer  :  byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Locations and headings.+module Swarm.Util.Location (Location, pattern Location, Heading, origin, Affine (..)) where++import Data.Aeson (FromJSONKey, ToJSONKey)+import Data.Int (Int32)+import Data.Yaml (FromJSON (parseJSON), ToJSON (toJSON))+import Linear (V2 (..))+import Linear.Affine (Affine (..), Point (..), origin)++-- | A Location is a pair of (x,y) coordinates, both up to 32 bits.+--   The positive x-axis points east and the positive y-axis points+--   north.  These are the coordinates that are shown to players.+--+--   See also the 'Coords' type defined in "Swarm.Game.World", which+--   use a (row, column) format instead, which is more convenient for+--   internal use.  The "Swarm.Game.World" module also defines+--   conversions between 'Location' and 'Coords'.+type Location = Point V2 Int32++-- | A convenient way to pattern-match on 'Location' values.+pattern Location :: Int32 -> Int32 -> Location+pattern Location x y = P (V2 x y)++{-# COMPLETE Location #-}++-- | A @Heading@ is a 2D vector, with 32-bit coordinates.+--+--   'Location' and 'Heading' are both represented using types from+--   the @linear@ package, so they can be manipulated using a large+--   number of operators from that package.  For example:+--+--   * Two headings can be added with '^+^'.+--   * The difference between two 'Location's is a 'Heading' (via '.-.').+--   * A 'Location' plus a 'Heading' is another 'Location' (via '.^+').+type Heading = V2 Int32++deriving instance ToJSON (V2 Int32)+deriving instance FromJSON (V2 Int32)++deriving instance FromJSONKey (V2 Int32)+deriving instance ToJSONKey (V2 Int32)++instance FromJSON Location where+  parseJSON = fmap P . parseJSON++instance ToJSON Location where+  toJSON (P v) = toJSON v
src/Swarm/Web.hs view
@@ -25,41 +25,87 @@ import Control.Lens ((^.)) import Control.Monad (void) import Control.Monad.IO.Class (liftIO)+import Data.Foldable (toList) import Data.IORef (IORef, readIORef) import Data.IntMap qualified as IM import Data.Maybe (fromMaybe)+import Data.Text qualified as T import Network.Wai qualified import Network.Wai.Handler.Warp qualified as Warp import Servant import Swarm.Game.Robot+import Swarm.Game.Scenario.Objective+import Swarm.Game.Scenario.Objective.Graph+import Swarm.Game.Scenario.Objective.Presentation.Model+import Swarm.Game.Scenario.Objective.WinCheck import Swarm.Game.State+import Swarm.TUI.Model+import Swarm.TUI.Model.UI import System.Timeout (timeout)  type SwarmApi =   "robots" :> Get '[JSON] [Robot]     :<|> "robot" :> Capture "id" Int :> Get '[JSON] (Maybe Robot)+    :<|> "goals" :> "prereqs" :> Get '[JSON] [PrereqSatisfaction]+    :<|> "goals" :> "active" :> Get '[JSON] [Objective]+    :<|> "goals" :> "graph" :> Get '[JSON] (Maybe GraphInfo)+    :<|> "goals" :> "uigoal" :> Get '[JSON] GoalTracking+    :<|> "goals" :> Get '[JSON] WinCondition+    :<|> "repl" :> "history" :> "full" :> Get '[JSON] [T.Text] -mkApp :: IORef GameState -> Servant.Server SwarmApi-mkApp gsRef =+mkApp :: IORef AppState -> Servant.Server SwarmApi+mkApp appStateRef =   robotsHandler     :<|> robotHandler+    :<|> prereqsHandler+    :<|> activeGoalsHandler+    :<|> goalsGraphHandler+    :<|> uiGoalHandler+    :<|> goalsHandler+    :<|> replHandler  where   robotsHandler = do-    g <- liftIO (readIORef gsRef)-    pure $ IM.elems $ g ^. robotMap+    appState <- liftIO (readIORef appStateRef)+    pure $ IM.elems $ appState ^. gameState . robotMap   robotHandler rid = do-    g <- liftIO (readIORef gsRef)-    pure $ IM.lookup rid (g ^. robotMap)+    appState <- liftIO (readIORef appStateRef)+    pure $ IM.lookup rid (appState ^. gameState . robotMap)+  prereqsHandler = do+    appState <- liftIO (readIORef appStateRef)+    case appState ^. gameState . winCondition of+      WinConditions _winState oc -> return $ getSatisfaction oc+      _ -> return []+  activeGoalsHandler = do+    appState <- liftIO (readIORef appStateRef)+    case appState ^. gameState . winCondition of+      WinConditions _winState oc -> return $ getActiveObjectives oc+      _ -> return []+  goalsGraphHandler = do+    appState <- liftIO (readIORef appStateRef)+    return $ case appState ^. gameState . winCondition of+      WinConditions _winState oc -> Just $ makeGraphInfo oc+      _ -> Nothing+  uiGoalHandler = do+    appState <- liftIO (readIORef appStateRef)+    return $ appState ^. uiState . uiGoal . goalsContent+  goalsHandler = do+    appState <- liftIO (readIORef appStateRef)+    return $ appState ^. gameState . winCondition+  replHandler = do+    appState <- liftIO (readIORef appStateRef)+    let replHistorySeq = appState ^. uiState . uiREPL . replHistory . replSeq+        items = [x | REPLEntry x <- toList replHistorySeq]+    pure items -webMain :: Maybe (MVar (Either String ())) -> Warp.Port -> IORef GameState -> IO ()-webMain baton port gsRef = catch (Warp.runSettings settings app) handleErr+webMain :: Maybe (MVar (Either String ())) -> Warp.Port -> IORef AppState -> IO ()+webMain baton port appStateRef = catch (Warp.runSettings settings app) handleErr  where   settings = Warp.setPort port $ onReady Warp.defaultSettings   onReady = case baton of     Just mv -> Warp.setBeforeMainLoop $ putMVar mv (Right ())     Nothing -> id   app :: Network.Wai.Application-  app = Servant.serve (Proxy @SwarmApi) (mkApp gsRef)+  app = Servant.serve (Proxy @SwarmApi) (mkApp appStateRef)   handleErr :: IOException -> IO ()   handleErr e = case baton of     Just mv -> putMVar mv (Left $ displayException e)@@ -74,13 +120,13 @@ --   startup doesn't work.  Otherwise, ignore the failure.  In any --   case, return a @Maybe Port@ value representing whether a web --   server is actually running, and if so, what port it is on.-startWebThread :: Maybe Warp.Port -> IORef GameState -> IO (Either String Warp.Port)+startWebThread :: Maybe Warp.Port -> IORef AppState -> IO (Either String Warp.Port) -- User explicitly provided port '0': don't run the web server startWebThread (Just 0) _ = pure $ Left "The web port has been turned off."-startWebThread portM gsRef = do+startWebThread portM appStateRef = do   baton <- newEmptyMVar   let port = fromMaybe defaultPort portM-  void $ forkIO $ webMain (Just baton) port gsRef+  void $ forkIO $ webMain (Just baton) port appStateRef   res <- timeout 500_000 (takeMVar baton)   case (portM, res) of     -- User requested explicit port but server didn't start: fail
swarm.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               swarm-version:            0.2.0.0+version:            0.3.0.0 synopsis:           2D resource gathering game with programmable robots  description:        Swarm is a 2D programming and resource gathering@@ -24,7 +24,7 @@                     editors/emacs/*.el                     editors/vscode/syntaxes/*.json data-dir:           data/-data-files:         *.yaml, scenarios/**/*.yaml, scenarios/**/*.txt, scenarios/**/*.sw, *.txt+data-files:         *.yaml, scenarios/**/*.yaml, scenarios/**/*.txt, scenarios/**/*.sw, *.txt, test/language-snippets/**/*.sw  source-repository head     type:     git@@ -81,55 +81,88 @@  library     import:           stan-config, common, ghc2021-extensions-    exposed-modules:  Swarm.Language.Context-                      Swarm.Language.Types-                      Swarm.Language.Syntax-                      Swarm.Language.Capability-                      Swarm.Language.Requirement-                      Swarm.Language.Parse-                      Swarm.Language.Parse.QQ-                      Swarm.Language.Pretty-                      Swarm.Language.Typecheck-                      Swarm.Language.Typed-                      Swarm.Language.Elaborate-                      Swarm.Language.LSP-                      Swarm.Language.Pipeline-                      Swarm.Language.Pipeline.QQ+    exposed-modules:  Data.BoolExpr+                      Data.BoolExpr.Simplify+                      Swarm.App+                      Swarm.DocGen                       Swarm.Game.CESK-                      Swarm.Game.Scenario-                      Swarm.Game.ScenarioInfo                       Swarm.Game.Display                       Swarm.Game.Entity                       Swarm.Game.Exception+                      Swarm.Game.Log                       Swarm.Game.Recipe                       Swarm.Game.Robot+                      Swarm.Game.Scenario+                      Swarm.Game.Scenario.Cell+                      Swarm.Game.Scenario.Objective.Logic+                      Swarm.Game.Scenario.Objective.Graph+                      Swarm.Game.Scenario.Objective.Presentation.Model+                      Swarm.Game.Scenario.Objective.Presentation.Render+                      Swarm.Game.Scenario.Objective.Validation+                      Swarm.Game.Scenario.Objective.WinCheck+                      Swarm.Game.Scenario.Objective+                      Swarm.Game.Scenario.RobotLookup+                      Swarm.Game.Scenario.Style+                      Swarm.Game.Scenario.WorldDescription+                      Swarm.Game.ScenarioInfo                       Swarm.Game.State                       Swarm.Game.Step                       Swarm.Game.Terrain-                      Swarm.Game.Value                       Swarm.Game.World                       Swarm.Game.WorldGen+                      Swarm.Language.Context+                      Swarm.Language.Capability+                      Swarm.Language.Elaborate+                      Swarm.Language.LSP+                      Swarm.Language.LSP.Hover+                      Swarm.Language.LSP.VarUsage+                      Swarm.Language.Module+                      Swarm.Language.Parse+                      Swarm.Language.Parse.QQ+                      Swarm.Language.Pipeline+                      Swarm.Language.Pipeline.QQ+                      Swarm.Language.Pretty+                      Swarm.Language.Requirement+                      Swarm.Language.Syntax+                      Swarm.Language.Typecheck+                      Swarm.Language.Typed+                      Swarm.Language.Types+                      Swarm.Language.Value                       Swarm.TUI.Attr                       Swarm.TUI.Border+                      Swarm.TUI.Controller+                      Swarm.TUI.Controller.Util+                      Swarm.TUI.View.CustomStyling+                      Swarm.TUI.Inventory.Sorting                       Swarm.TUI.List-                      Swarm.TUI.Panel                       Swarm.TUI.Model+                      Swarm.TUI.Model.Achievement.Attainment+                      Swarm.TUI.Model.Achievement.Definitions+                      Swarm.TUI.Model.Achievement.Description+                      Swarm.TUI.Model.Achievement.Persistence+                      Swarm.TUI.Model.Failure+                      Swarm.TUI.Model.Menu+                      Swarm.TUI.Model.Name+                      Swarm.TUI.Model.Repl+                      Swarm.TUI.Model.StateUpdate+                      Swarm.TUI.Model.UI+                      Swarm.TUI.Panel                       Swarm.TUI.View-                      Swarm.TUI.Controller-                      Swarm.TUI.Inventory.Sorting-                      Swarm.App-                      Swarm.Version-                      Swarm.Web+                      Swarm.TUI.View.Achievement+                      Swarm.TUI.View.CellDisplay+                      Swarm.TUI.View.Util                       Swarm.Util-                      Swarm.DocGen+                      Swarm.Util.Location                       Swarm.Util.Yaml+                      Swarm.Version+                      Swarm.Web     other-modules:    Paths_swarm     autogen-modules:  Paths_swarm      build-depends:    base                          >= 4.14 && < 4.18,                       aeson                         >= 2 && < 2.2,                       array                         >= 0.5.4 && < 0.6,-                      brick                         >= 1.0 && < 1.4,+                      brick                         >= 1.5 && < 1.7,                       bytestring                    >= 0.10 && < 0.12,                       clock                         >= 0.8.2 && < 0.9,                       containers                    >= 0.6.2 && < 0.7,@@ -147,9 +180,9 @@                       http-client-tls               >= 0.3 && < 0.4,                       http-types                    >= 0.12 && < 0.13,                       lens                          >= 4.19 && < 5.3,-                      linear                        >= 1.21.6 && < 1.22,-                      lsp                           >= 1.2 && < 1.7,-                      megaparsec                    >= 9.0 && < 9.3,+                      linear                        >= 1.21.6 && < 1.23,+                      lsp                           >= 1.6 && < 1.7,+                      megaparsec                    >= 9.0 && < 9.4,                       minimorph                     >= 0.3 && < 0.4,                       mtl                           >= 2.2.2 && < 2.3,                       murmur3                       >= 1.0.4 && < 1.1,@@ -166,6 +199,7 @@                       tagged                        >= 0.8 && < 0.9,                       template-haskell              >= 2.16 && < 2.20,                       text                          >= 1.2.4 && < 2.1,+                      text-rope                     >= 0.2 && < 0.3,                       text-zipper                   >= 0.10 && < 0.13,                       time                          >= 1.9 && < 1.14,                       unification-fd                >= 0.11  && < 0.12,@@ -208,6 +242,8 @@                       TestNotification                       TestLanguagePipeline                       TestPretty+                      TestBoolExpr+                      TestLSP                       TestUtil      build-depends:    tasty                         >= 0.10 && < 1.5,
test/integration/Main.hs view
@@ -5,16 +5,18 @@ -- | Swarm integration tests module Main where -import Control.Lens (Ixed (ix), to, use, view, (&), (.~), (<&>), (^.), (^?!))-import Control.Monad (filterM, forM_, unless, void, when)+import Control.Lens (Ixed (ix), to, use, view, (&), (.~), (<&>), (<>~), (^.), (^..), (^?!))+import Control.Monad (filterM, forM_, unless, when) import Control.Monad.State (StateT (runStateT), gets) import Control.Monad.Trans.Except (runExceptT) import Data.Char (isSpace) import Data.Containers.ListUtils (nubOrd) import Data.Foldable (Foldable (toList), find) import Data.IntSet qualified as IS+import Data.List (partition) import Data.Map qualified as M import Data.Maybe (isJust)+import Data.Sequence (Seq) import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T@@ -23,11 +25,12 @@ import Swarm.DocGen qualified as DocGen import Swarm.Game.CESK (emptyStore, initMachine) import Swarm.Game.Entity (EntityMap, loadEntities)-import Swarm.Game.Robot (leText, machine, robotLog, waitingUntil)+import Swarm.Game.Robot (LogEntry, defReqs, leText, machine, robotContext, robotLog, waitingUntil) import Swarm.Game.Scenario (Scenario) import Swarm.Game.State (   GameState,-  WinCondition (Won),+  WinCondition (WinConditions),+  WinStatus (Won),   activeRobots,   baseRobot,   initGameStateForScenario,@@ -40,21 +43,25 @@  ) import Swarm.Game.Step (gameTick) import Swarm.Language.Context qualified as Ctx-import Swarm.Language.Pipeline (processTerm)+import Swarm.Language.Pipeline (ProcessedTerm (..), processTerm) import Swarm.Util.Yaml (decodeFileEitherE) import System.Directory (doesDirectoryExist, doesFileExist, listDirectory) import System.Environment (getEnvironment)+import System.FilePath (splitDirectories) import System.FilePath.Posix (takeExtension, (</>)) import System.Timeout (timeout) import Test.Tasty (TestTree, defaultMain, testGroup)-import Test.Tasty.ExpectedFailure (expectFailBecause) import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, testCase) import Witch (into) +isUnparseableTest :: (FilePath, String) -> Bool+isUnparseableTest (fp, _) = "_Validation" `elem` splitDirectories fp+ main :: IO () main = do   examplePaths <- acquire "example" "sw"   scenarioPaths <- acquire "data/scenarios" "yaml"+  let (unparseableScenarios, parseableScenarios) = partition isUnparseableTest scenarioPaths   scenarioPrograms <- acquire "data/scenarios" "sw"   ci <- any (("CI" ==) . fst) <$> getEnvironment   entities <- loadEntities@@ -66,7 +73,8 @@           "Tests"           [ exampleTests examplePaths           , exampleTests scenarioPrograms-          , scenarioTests em scenarioPaths+          , scenarioParseTests em parseableScenarios+          , scenarioParseInvalidTests em unparseableScenarios           , testScenarioSolution ci em           , testEditorFiles           ]@@ -81,19 +89,34 @@  where   value = processTerm $ into @Text fileContent -scenarioTests :: EntityMap -> [(FilePath, String)] -> TestTree-scenarioTests em inputs = testGroup "Test scenarios" (map (scenarioTest em) inputs)+scenarioParseTests :: EntityMap -> [(FilePath, String)] -> TestTree+scenarioParseTests em inputs =+  testGroup+    "Test scenarios parse"+    (map (scenarioTest Parsed em) inputs) -scenarioTest :: EntityMap -> (FilePath, String) -> TestTree-scenarioTest em (path, _) =-  testCase ("parse scenario " ++ show path) (void $ getScenario em path)+scenarioParseInvalidTests :: EntityMap -> [(FilePath, String)] -> TestTree+scenarioParseInvalidTests em inputs =+  testGroup+    "Test invalid scenarios fail to parse"+    (map (scenarioTest Failed em) inputs) -getScenario :: EntityMap -> FilePath -> IO Scenario-getScenario em p = do+data ParseResult = Parsed | Failed++scenarioTest :: ParseResult -> EntityMap -> (FilePath, String) -> TestTree+scenarioTest expRes em (path, _) =+  testCase ("parse scenario " ++ show path) (getScenario expRes em path)++getScenario :: ParseResult -> EntityMap -> FilePath -> IO ()+getScenario expRes em p = do   res <- decodeFileEitherE em p :: IO (Either ParseException Scenario)-  case res of-    Left err -> assertFailure (prettyPrintParseException err)-    Right s -> return s+  case expRes of+    Parsed -> case res of+      Left err -> assertFailure (prettyPrintParseException err)+      Right _s -> return ()+    Failed -> case res of+      Left _err -> return ()+      Right _s -> assertFailure "Unexpectedly parsed invalid scenario!"  acquire :: FilePath -> String -> IO [(FilePath, String)] acquire dir ext = do@@ -124,6 +147,8 @@   sec :: Int   sec = 10 ^ (6 :: Int) +data ShouldCheckBadErrors = CheckForBadErrors | AllowBadErrors deriving (Eq, Show)+ testScenarioSolution :: Bool -> EntityMap -> TestTree testScenarioSolution _ci _em =   testGroup@@ -137,10 +162,10 @@         , testSolution Default "Tutorials/place"         , testSolution Default "Tutorials/types"         , testSolution Default "Tutorials/type-errors"-        , testSolution Default "Tutorials/bind"-        , testSolution Default "Tutorials/install"+        , testSolution Default "Tutorials/equip"         , testSolution Default "Tutorials/build"-        , testSolution' Default "Tutorials/crash" $ \g -> do+        , testSolution Default "Tutorials/bind2"+        , testSolution' Default "Tutorials/crash" CheckForBadErrors $ \g -> do             let rs = toList $ g ^. robotMap             let hints = any (T.isInfixOf "you will win" . view leText) . toList . view robotLog             let win = isJust $ find hints rs@@ -149,7 +174,7 @@         , testSolution Default "Tutorials/def"         , testSolution Default "Tutorials/lambda"         , testSolution Default "Tutorials/require"-        , testSolution Default "Tutorials/requireinv"+        , testSolution (Sec 3) "Tutorials/requireinv"         , testSolution Default "Tutorials/conditionals"         , testSolution (Sec 5) "Tutorials/farming"         ]@@ -158,7 +183,10 @@         [ testSolution Default "Challenges/chess_horse"         , testSolution Default "Challenges/teleport"         , testSolution (Sec 5) "Challenges/2048"+        , testSolution (Sec 3) "Challenges/word-search"+        , testSolution (Sec 3) "Challenges/ice-cream"         , testSolution (Sec 10) "Challenges/hanoi"+        , testSolution Default "Challenges/friend"         , testGroup             "Mazes"             [ testSolution Default "Challenges/Mazes/easy_cave_maze"@@ -166,19 +194,22 @@             , testSolution Default "Challenges/Mazes/invisible_maze"             , testSolution Default "Challenges/Mazes/loopy_maze"             ]+        , testGroup+            "Ranching"+            [ testSolution (Sec 30) "Challenges/Ranching/gated-paddock"+            ]         ]     , testGroup         "Regression tests"-        [ expectFailBecause "Awaiting fix (#394)" $-            testSolution Default "Testing/394-build-drill"+        [ testSolution Default "Testing/394-build-drill"         , testSolution Default "Testing/373-drill"         , testSolution Default "Testing/428-drowning-destroy"-        , testSolution' Default "Testing/475-wait-one" $ \g -> do+        , testSolution' Default "Testing/475-wait-one" CheckForBadErrors $ \g -> do             let t = g ^. ticks                 r1Waits = g ^?! robotMap . ix 1 . to waitingUntil                 active = IS.member 1 $ g ^. activeRobots                 waiting = elem 1 . concat . M.elems $ g ^. waitingRobots-            assertBool "The game should only take one tick" $ t == 1+            assertBool "The game should only take two ticks" $ t == 2             assertBool "Robot 1 should have waiting machine" $ isJust r1Waits             assertBool "Robot 1 should be still active" active             assertBool "Robot 1 should not be in waiting set" $ not waiting@@ -186,13 +217,18 @@         , testSolution Default "Testing/504-teleport-self"         , testSolution Default "Testing/508-capability-subset"         , testGroup+            "Possession criteria (#858)"+            [ testSolution Default "Testing/858-inventory/858-possession-objective"+            , testSolution Default "Testing/858-inventory/858-counting-objective"+            , testSolution Default "Testing/858-inventory/858-nonpossession-objective"+            ]+        , testGroup             "Require (#201)"             [ testSolution Default "Testing/201-require/201-require-device"             , testSolution Default "Testing/201-require/201-require-device-creative"             , testSolution Default "Testing/201-require/201-require-device-creative1"             , testSolution Default "Testing/201-require/201-require-entities"-            , expectFailBecause "Awaiting fix (#540)" $-                testSolution Default "Testing/201-require/201-require-entities-def"+            , testSolution Default "Testing/201-require/201-require-entities-def"             , testSolution Default "Testing/201-require/533-reprogram-simple"             , testSolution Default "Testing/201-require/533-reprogram"             ]@@ -206,6 +242,21 @@         , testSolution Default "Testing/699-movement-fail/699-move-liquid"         , testSolution Default "Testing/699-movement-fail/699-teleport-blocked"         , testSolution Default "Testing/710-multi-robot"+        , testSolution Default "Testing/920-meet"+        , testSolution Default "Testing/955-heading"+        , testSolution' Default "Testing/397-wrong-missing" CheckForBadErrors $ \g -> do+            let msgs =+                  (g ^. messageQueue . to seqToTexts)+                    <> (g ^.. robotMap . traverse . robotLog . to seqToTexts . traverse)++            assertBool "Should be some messages" (not (null msgs))+            assertBool "Error messages should not mention treads" $+              not (any ("treads" `T.isInfixOf`) msgs)+            assertBool "Error message should mention GPS receiver" $+              any ("GPS receiver" `T.isInfixOf`) msgs+        , testSolution Default "Testing/961-custom-capabilities"+        , testSolution Default "Testing/956-GPS"+        , testSolution Default "Testing/958-isempty"         ]     ]  where@@ -213,30 +264,38 @@   -- expectFailIf b = if b then expectFailBecause else (\_ x -> x)    testSolution :: Time -> FilePath -> TestTree-  testSolution s p = testSolution' s p (const $ pure ())+  testSolution s p = testSolution' s p CheckForBadErrors (const $ pure ()) -  testSolution' :: Time -> FilePath -> (GameState -> Assertion) -> TestTree-  testSolution' s p verify = testCase p $ do-    Right gs <- runExceptT $ initGameStateForScenario p Nothing Nothing-    case gs ^. winSolution of-      Nothing -> assertFailure "No solution to test!"-      Just sol -> do-        let gs' = gs & baseRobot . machine .~ initMachine sol Ctx.empty emptyStore-        m <- timeout (time s) (snd <$> runStateT playUntilWin gs')-        case m of-          Nothing -> assertFailure "Timed out - this likely means that the solution did not work."-          Just g -> do-            -- When debugging, try logging all robot messages.-            -- printAllLogs-            noBadErrors g-            verify g+  testSolution' :: Time -> FilePath -> ShouldCheckBadErrors -> (GameState -> Assertion) -> TestTree+  testSolution' s p shouldCheckBadErrors verify = testCase p $ do+    out <- runExceptT $ initGameStateForScenario p Nothing Nothing+    case out of+      Left x -> assertFailure $ unwords ["Failure in initGameStateForScenario:", T.unpack x]+      Right gs -> case gs ^. winSolution of+        Nothing -> assertFailure "No solution to test!"+        Just sol@(ProcessedTerm _ _ reqCtx) -> do+          let gs' =+                gs+                  -- See #827 for an explanation of why it's important to add to+                  -- the robotContext defReqs here (and also why this will,+                  -- hopefully, eventually, go away).+                  & baseRobot . robotContext . defReqs <>~ reqCtx+                  & baseRobot . machine .~ initMachine sol Ctx.empty emptyStore+          m <- timeout (time s) (snd <$> runStateT playUntilWin gs')+          case m of+            Nothing -> assertFailure "Timed out - this likely means that the solution did not work."+            Just g -> do+              -- When debugging, try logging all robot messages.+              -- printAllLogs+              when (shouldCheckBadErrors == CheckForBadErrors) $ noBadErrors g+              verify g    playUntilWin :: StateT GameState IO ()   playUntilWin = do     w <- use winCondition     b <- gets badErrorsInLogs     when (null b) $ case w of-      Won _ -> return ()+      WinConditions (Won _) _ -> return ()       _ -> gameTick >> playUntilWin  noBadErrors :: GameState -> Assertion@@ -251,8 +310,10 @@     (g ^. robotMap)     <> filter isBad (seqToTexts $ g ^. messageQueue)  where-  seqToTexts = map (view leText) . toList   isBad m = "Fatal error:" `T.isInfixOf` m || "swarm/issues" `T.isInfixOf` m++seqToTexts :: Seq LogEntry -> [Text]+seqToTexts = map (view leText) . toList  printAllLogs :: GameState -> IO () printAllLogs g =
test/unit/Main.hs view
@@ -20,8 +20,10 @@   testProperty,   (==>),  )+import TestBoolExpr (testBoolExpr) import TestEval (testEval) import TestInventory (testInventory)+import TestLSP (testLSP) import TestLanguagePipeline (testLanguagePipeline) import TestModel (testModel) import TestNotification (testNotification)@@ -41,11 +43,13 @@     "Tests"     [ testLanguagePipeline     , testPrettyConst+    , testBoolExpr     , testEval g     , testModel     , testInventory     , testNotification g     , testMisc+    , testLSP     ]  testMisc :: TestTree
+ test/unit/TestBoolExpr.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Boolean expression unit tests+module TestBoolExpr where++import Data.BoolExpr qualified as BE+import Data.BoolExpr.Simplify qualified as Simplify+import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.Set qualified as Set+import Swarm.Game.Scenario.Objective.Logic+import Swarm.Game.Scenario.Objective.WinCheck qualified as WC+import Test.Tasty+import Test.Tasty.HUnit++testBoolExpr :: TestTree+testBoolExpr =+  testGroup+    "Boolean evaluation"+    [ testGroup+        "Expression simplification"+        [ testGroup+            "Return true if the expression can be simplified to False"+            [ testGroup+                "Effect of constant literals"+                [ testCase+                    "False input via single literal"+                    $ expectTrue BE.BFalse+                , testCase+                    "True input via composed literals"+                    $ expectFalse BE.BTrue+                , testCase+                    "False input via composed literals"+                    $ expectTrue+                    $ BE.BOr BE.BFalse BE.BFalse+                , testCase+                    "True input via composed literals"+                    $ expectFalse+                    $ BE.BOr BE.BFalse BE.BTrue+                , testCase+                    "Constant OR'd with False"+                    $ expectFalse+                    $ BE.BOr BE.BFalse (BE.BConst (BE.Positive "foo"))+                , testCase+                    "Constant OR'd with True"+                    $ expectFalse+                    $ BE.BOr (BE.BConst (BE.Positive "foo")) BE.BTrue+                , testCase+                    "Constant AND'd with False"+                    $ expectTrue+                    $ BE.BAnd BE.BFalse (BE.BConst (BE.Positive "foo"))+                , testCase+                    "Constant AND'd with True"+                    $ expectFalse+                    $ BE.BAnd (BE.BConst (BE.Positive "foo")) BE.BTrue+                , testCase+                    "Nested Constants AND'd with False within OR"+                    $ expectTrue+                    $ BE.BOr+                      (BE.BAnd BE.BFalse (BE.BConst (BE.Positive "foo")))+                      (BE.BAnd (BE.BConst (BE.Positive "bar")) BE.BFalse)+                , testCase+                    "Deeply nested Constants AND'd with False within OR with multiple negations"+                    $ expectTrue+                    $ BE.BOr+                      (BE.BAnd (BE.BNot BE.BTrue) (BE.BNot (BE.BNot (BE.BNot (BE.BConst (BE.Positive "foo"))))))+                      (BE.BAnd (BE.BConst (BE.Positive "bar")) (BE.BNot (BE.BNot BE.BFalse)))+                ]+            , testGroup+                "Effect of contradicting named constants"+                [ testCase+                    "via NOT operator"+                    $ expectTrue+                    $ BE.BAnd (BE.BNot (BE.BConst (BE.Positive "foo"))) (BE.BConst (BE.Positive "foo"))+                , testCase+                    "via signedness"+                    $ expectTrue+                    $ BE.BAnd (BE.BConst (BE.Positive "foo")) (BE.BConst (BE.Negative "foo"))+                ]+            ]+        ]+    , testGroup+        "Prerequisite expressions"+        [ testCase+            "A negated goal is completed"+            $ assertBool "Should have returned true"+            $ WC.isUnwinnablePrereq (Set.singleton "b") demoPrereqs+        , testCase+            "A non-negated goal is completed"+            $ assertBool "Should have returned false"+            $ not+            $ WC.isUnwinnablePrereq (Set.singleton "c") demoPrereqs+        ]+    ]+ where+  expectTrue, expectFalse :: BE.BoolExpr String -> Assertion+  expectTrue = assertBool "Should have returned true" . Simplify.cannotBeTrue+  expectFalse = assertBool "Should have returned false" . not . Simplify.cannotBeTrue++  demoPrereqs :: Prerequisite ObjectiveLabel+  demoPrereqs =+    And $+      Id "a"+        :| [Not (Id "b"), Id "c"]
test/unit/TestEval.hs view
@@ -5,10 +5,11 @@ module TestEval where  import Control.Lens ((^.), _3)+import Data.Char (ord) import Data.Text (Text) import Data.Text qualified as T import Swarm.Game.State-import Swarm.Game.Value+import Swarm.Language.Value import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck@@ -238,6 +239,27 @@                  in T.concat ["(", t1, ",", t2, ") == split (chars ", t1, ") (", t1, " ++ ", t2, ")"]                       `evaluatesToP` VBool True             )+        , testProperty+            "charAt"+            $ \i (NonEmpty s) ->+              let i' = i `mod` length s+               in T.concat ["charAt ", from @String (show i'), " ", tquote s]+                    `evaluatesToP` VInt (fromIntegral (ord (s !! i')))+        , testCase+            "toChar 97"+            ("toChar 97 == \"a\"" `evaluatesTo` VBool True)+        , testProperty+            "chars/toChar"+            ( \(NonNegative (i :: Integer)) ->+                T.concat ["chars (toChar ", from @String (show i), ")"]+                  `evaluatesToP` VInt 1+            )+        , testProperty+            "charAt/toChar"+            ( \(NonNegative i) ->+                T.concat ["charAt 0 (toChar ", from @String (show i), ")"]+                  `evaluatesToP` VInt i+            )         ]     ]  where@@ -251,7 +273,7 @@       Left err ->         p err           @? "Expected predicate did not hold on error message "-          ++ from @Text @String err+            ++ from @Text @String err    evaluatesTo :: Text -> Value -> Assertion   evaluatesTo tm val = do
+ test/unit/TestLSP.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}++-- | LSP unit tests+module TestLSP (testLSP) where++import Data.Text (Text)+import Data.Text.IO qualified as TIO+import Swarm.Language.LSP.VarUsage qualified as VU+import Swarm.Language.Parse (readTerm')+import Swarm.Language.Syntax qualified as S+import System.FilePath ((</>))+import Test.Tasty+import Test.Tasty.HUnit++baseTestPath :: FilePath+baseTestPath = "data/test/language-snippets/warnings/unused-vars"++data UnusedVar = UnusedVar S.Var VU.BindingType+  deriving (Eq, Show)++simplifyWarning :: VU.VarUsage -> UnusedVar+simplifyWarning (VU.VarUsage (S.LV _ v) scope) = UnusedVar v scope++testLSP :: TestTree+testLSP =+  testGroup+    "Unused variable warnings"+    [ testCase "outer lambda" $+        checkFile "multiple-lambda-first-unused.sw" $+          pure $+            UnusedVar "x" VU.Lambda+    , testCase "inner lambda" $+        checkFile "multiple-lambda-second-unused.sw" $+          pure $+            UnusedVar "y" VU.Lambda+    , testCase "shadowed variable name" $+        checkFile "shadowed-variable-lambda-unused.sw" $+          pure $+            UnusedVar "x" VU.Lambda+    , testCase "outer let" $+        checkFile "multiple-let-first-unused.sw" $+          pure $+            UnusedVar "x" VU.Let+    , testCase "outer let" $+        checkFile "multiple-let-second-unused.sw" $+          pure $+            UnusedVar "y" VU.Let+    , testCase "multiple unused let" $+        checkFile+          "multiple-let-all-unused.sw"+          [UnusedVar "x" VU.Let, UnusedVar "y" VU.Let]+    , testCase "shadowed let without usage" $+        checkFile+          "shadowed-variable-let-unused.sw"+          [UnusedVar "x" VU.Let]+    , testCase "shadowed let with intermediate usage" $+        checkFile+          "shadowed-variable-let-intermediate-use.sw"+          []+    , testCase "recursive let" $+        checkFile+          "recursive-let.sw"+          [UnusedVar "fac" VU.Let]+    , testCase "single unused bind" $+        checkFile+          "single-bind-unused.sw"+          [UnusedVar "x" VU.Bind]+    , testCase "single used bind" $+        checkFile+          "single-bind-used.sw"+          []+    ]+ where+  checkFile :: FilePath -> [UnusedVar] -> IO ()+  checkFile filename expectedWarnings = do+    content <- TIO.readFile fullPath+    let actualWarnings = getWarnings content+    assertEqual "failed" expectedWarnings actualWarnings+   where+    fullPath = baseTestPath </> filename++  getWarnings :: Text -> [UnusedVar]+  getWarnings content =+    case readTerm' content of+      Right (Just term) -> map simplifyWarning problems+       where+        VU.Usage _ problems = VU.getUsage mempty term+      _ -> []
test/unit/TestLanguagePipeline.hs view
@@ -1,15 +1,24 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}  -- | Swarm unit tests module TestLanguagePipeline where +import Control.Arrow ((&&&))+import Control.Lens (toListOf)+import Control.Lens.Plated (universe) import Data.Aeson (eitherDecode, encode) import Data.Either import Data.Maybe import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T-import Swarm.Language.Pipeline (processTerm)+import Swarm.Language.Module (Module (..))+import Swarm.Language.Parse.QQ (tyQ)+import Swarm.Language.Pipeline (ProcessedTerm (..), processTerm)+import Swarm.Language.Pipeline.QQ (tmQ)+import Swarm.Language.Syntax import Swarm.Language.Typecheck (isSimpleUType) import Swarm.Language.Types import Test.Tasty@@ -245,11 +254,38 @@             "void - valid type signature"             (valid "def f : void -> a = \\x. undefined end")         ]+    , testGroup+        "type annotations"+        [ testCase+            "annotate 1 + 1"+            ( assertEqual+                "type annotations"+                (toListOf traverse (getSyntax [tmQ| 1 + 1 |]))+                [[tyQ| int -> int -> int|], [tyQ|int|], [tyQ|int -> int|], [tyQ|int|], [tyQ|int|]]+            )+        , testCase+            "get all annotated variable types"+            ( let s =+                    getSyntax+                      [tmQ| def f : (int -> int) -> int -> text = \g. \x. format (g x) end |]++                  isVar (TVar {}) = True+                  isVar _ = False+                  getVars = map (_sTerm &&& _sType) . filter (isVar . _sTerm) . universe+               in assertEqual+                    "variable types"+                    (getVars s)+                    ( [ (TVar "g", [tyQ| int -> int |])+                      , (TVar "x", [tyQ| int |])+                      ]+                    )+            )+        ]     ]  where   valid = flip process "" -  roundTrip txt = assertEqual "rountrip" term (decodeThrow $ encode term)+  roundTrip txt = assertEqual "roundtrip" term (decodeThrow $ encode term)    where     decodeThrow v = case eitherDecode v of       Left e -> error $ "Decoding of " <> from (T.decodeUtf8 (from v)) <> " failed with: " <> from e@@ -264,3 +300,6 @@     Right _       | expect == "" -> pure ()       | otherwise -> error "Unexpected success"++  getSyntax :: ProcessedTerm -> Syntax' Polytype+  getSyntax (ProcessedTerm (Module s _) _ _) = s
test/unit/TestNotification.hs view
@@ -34,7 +34,7 @@         assertEqual "There should be two messages in queue" [0, 1] (view leTime <$> gs' ^. messageNotifications . notificationsContent)         assertNew (gs' & lastSeenMessageTime .~ 0) 1 "message" messageNotifications     , testCase "new message after log" $ do-        gs' <- goodPlay "create \"logger\"; install self \"logger\"; log \"Hello world!\""+        gs' <- goodPlay "create \"logger\"; equip \"logger\"; log \"Hello world!\""         let r = gs' ^?! robotMap . ix (-1)         assertBool "There should be one log entry in robots log" (length (r ^. robotLog) == 1)         assertEqual "The hypothetical robot should be in focus" (Just (r ^. robotID)) (view robotID <$> focusedRobot gs')
test/unit/TestPretty.hs view
@@ -36,17 +36,22 @@     , testCase         "operators #8 - double unary negation"         ( equalPretty "-(-1)" $-            TApp (TConst Neg) $ TApp (TConst Neg) (TInt 1)+            TApp (TConst Neg) $+              TApp (TConst Neg) (TInt 1)         )     , testCase         "operators #8 - unary negation with strongly fixing binary operator"         ( equalPretty "-1 ^ (-2)" $-            TApp (TConst Neg) $ mkOp' Exp (TInt 1) $ TApp (TConst Neg) (TInt 2)+            TApp (TConst Neg) $+              mkOp' Exp (TInt 1) $+                TApp (TConst Neg) (TInt 2)         )     , testCase         "operators #8 - unary negation with weakly fixing binary operator"         ( equalPretty "-(1 + -2)" $-            TApp (TConst Neg) $ mkOp' Add (TInt 1) $ TApp (TConst Neg) (TInt 2)+            TApp (TConst Neg) $+              mkOp' Add (TInt 1) $+                TApp (TConst Neg) (TInt 2)         )     , testCase         "operators #8 - simple infix operator"
test/unit/TestUtil.hs view
@@ -14,9 +14,9 @@ import Swarm.Game.Robot import Swarm.Game.State import Swarm.Game.Step (gameTick, hypotheticalRobot, stepCESK)-import Swarm.Game.Value import Swarm.Language.Context import Swarm.Language.Pipeline (ProcessedTerm (..), processTerm)+import Swarm.Language.Value  eval :: GameState -> Text -> IO (GameState, Robot, Either Text (Value, Int)) eval g = either (return . (g,hypotheticalRobot undefined 0,) . Left) (evalPT g) . processTerm1