Offload subrecords of GameState to other modules (#1667)

This is a continuation of #1652.

Most of the sub-records are bundled into `Swarm.Game.State.Substate`, but we create a `Swarm.Game.State.Robot` module just for robots.

We introduce a `zoomRobots` function so that applicable functions can operate directly on `Robots` state instead of `GameState`.

## Size comparison

### Before

| File | Lines |
| --- | --- |
| `State.hs` | 1569 |

### After

| File | Lines |
| --- | --- |
| `State.hs` | 812 |
| `Substate.hs` | 497 |
| `Robot.hs` | 395 |
| `Config.hs` | 21 |
## For follow-up PR:
- [ ]  Remove exports of `_viewCenter` and `_focusedRobotID` from `Swarm.Game.State.Robot`
This commit is contained in:
Karl Ostmo 2023-11-30 13:57:39 -08:00 committed by GitHub
parent b244a4223c
commit 3094abd565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1121 additions and 911 deletions

View File

@ -32,6 +32,7 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition.Log
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Game.Universe
import Swarm.Game.World.Modify
import Text.AhoCorasick

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Config data required by initializers of the
-- 'Swarm.Game.State.GameState' record and its subrecords.
module Swarm.Game.State.Config where
import Swarm.Game.Entity (Entity, EntityMap)
import Swarm.Game.Recipe (Recipe)
import Swarm.Game.ResourceLoading (NameGenerator)
import Swarm.Game.World.Typecheck (WorldMap)
-- | Record to pass information needed to create an initial
-- 'GameState' record when starting a scenario.
data GameStateConfig = GameStateConfig
{ initNameParts :: NameGenerator
, initEntities :: EntityMap
, initRecipes :: [Recipe Entity]
, initWorldMap :: WorldMap
}

View File

@ -0,0 +1,394 @@
{-# LANGUAGE TemplateHaskell #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Robot-specific subrecords and utilities used by 'Swarm.Game.State.GameState'
module Swarm.Game.State.Robot (
-- * Types
ViewCenterRule (..),
Robots,
-- * Robot naming
RobotNaming,
nameGenerator,
gensym,
robotNaming,
-- * Initialization
initRobots,
-- * Accessors
_viewCenter,
_focusedRobotID,
robotMap,
robotsByLocation,
robotsWatching,
activeRobots,
waitingRobots,
viewCenterRule,
viewCenter,
focusedRobotID,
-- * Utilities
wakeWatchingRobots,
sleepUntil,
sleepForever,
wakeUpRobotsDoneSleeping,
deleteRobot,
removeRobotFromLocationMap,
activateRobot,
addRobot,
addRobotToLocation,
addTRobot,
setRobotList,
) where
import Control.Arrow (Arrow ((&&&)))
import Control.Effect.Lens
import Control.Effect.State (State)
import Control.Effect.Throw (Has)
import Control.Lens hiding (Const, use, uses, view, (%=), (+=), (.=), (<+=), (<<.=))
import Control.Monad (forM_)
import Data.Aeson (FromJSON, ToJSON)
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.NonEmpty qualified as NE
import Data.Map (Map)
import Data.Map qualified as M
import Data.Maybe (mapMaybe)
import Data.Set qualified as S
import Data.Tuple (swap)
import GHC.Generics (Generic)
import Swarm.Game.CESK (CESK (Waiting), TickNumber (..), addTicks)
import Swarm.Game.Location
import Swarm.Game.ResourceLoading (NameGenerator)
import Swarm.Game.Robot
import Swarm.Game.State.Config
import Swarm.Game.Universe as U
import Swarm.Util (binTuples, surfaceEmpty, (<+=), (<<.=))
import Swarm.Util.Lens (makeLensesExcluding)
-- | The 'ViewCenterRule' specifies how to determine the center of the
-- world viewport.
data ViewCenterRule
= -- | The view should be centered on an absolute position.
VCLocation (Cosmic Location)
| -- | The view should be centered on a certain robot.
VCRobot RID
deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON)
makePrisms ''ViewCenterRule
data RobotNaming = RobotNaming
{ _nameGenerator :: NameGenerator
, _gensym :: Int
}
makeLensesExcluding ['_nameGenerator] ''RobotNaming
--- | Read-only list of words, for use in building random robot names.
nameGenerator :: Getter RobotNaming NameGenerator
nameGenerator = to _nameGenerator
-- | A counter used to generate globally unique IDs.
gensym :: Lens' RobotNaming Int
data Robots = Robots
{ _robotMap :: IntMap Robot
, -- A set of robots to consider for the next game tick. It is guaranteed to
-- be a subset of the keys of 'robotMap'. It may contain waiting or idle
-- robots. But robots that are present in 'robotMap' and not in 'activeRobots'
-- are guaranteed to be either waiting or idle.
_activeRobots :: IntSet
, -- A set of probably waiting robots, indexed by probable wake-up time. It
-- may contain robots that are in fact active or idle, as well as robots
-- that do not exist anymore. Its only guarantee is that once a robot name
-- with its wake up time is inserted in it, it will remain there until the
-- wake-up time is reached, at which point it is removed via
-- 'wakeUpRobotsDoneSleeping'.
-- Waiting robots for a given time are a list because it is cheaper to
-- prepend to a list than insert into a 'Set'.
_waitingRobots :: Map TickNumber [RID]
, _robotsByLocation :: Map SubworldName (Map Location IntSet)
, -- This member exists as an optimization so
-- that we do not have to iterate over all "waiting" robots,
-- since there may be many.
_robotsWatching :: Map (Cosmic Location) (S.Set RID)
, _robotNaming :: RobotNaming
, _viewCenterRule :: ViewCenterRule
, _viewCenter :: Cosmic Location
, _focusedRobotID :: RID
}
-- We want to access active and waiting robots via lenses inside
-- this module but to expose it as a Getter to protect invariants.
makeLensesFor
[ ("_activeRobots", "internalActiveRobots")
, ("_waitingRobots", "internalWaitingRobots")
]
''Robots
makeLensesExcluding ['_viewCenter, '_viewCenterRule, '_focusedRobotID, '_activeRobots, '_waitingRobots] ''Robots
-- | All the robots that currently exist in the game, indexed by ID.
robotMap :: Lens' Robots (IntMap Robot)
-- | The names of the robots that are currently not sleeping.
activeRobots :: Getter Robots IntSet
activeRobots = internalActiveRobots
-- | The names of the robots that are currently sleeping, indexed by wake up
-- time. Note that this may not include all sleeping robots, particularly
-- those that are only taking a short nap (e.g. @wait 1@).
waitingRobots :: Getter Robots (Map TickNumber [RID])
waitingRobots = internalWaitingRobots
-- | The names of all robots that currently exist in the game, indexed by
-- location (which we need both for /e.g./ the @salvage@ command as
-- well as for actually drawing the world). Unfortunately there is
-- no good way to automatically keep this up to date, since we don't
-- just want to completely rebuild it every time the 'robotMap'
-- changes. Instead, we just make sure to update it every time the
-- 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' Robots (Map SubworldName (Map Location IntSet))
-- | Get a list of all the robots that are \"watching\" by location.
robotsWatching :: Lens' Robots (Map (Cosmic Location) (S.Set RID))
-- | State and data for assigning identifiers to robots
robotNaming :: Lens' Robots RobotNaming
-- | The current center of the world view. Note that this cannot be
-- modified directly, since it is calculated automatically from the
-- 'viewCenterRule'. To modify the view center, either set the
-- 'viewCenterRule', or use 'modifyViewCenter'.
viewCenter :: Getter Robots (Cosmic Location)
viewCenter = to _viewCenter
-- | The current robot in focus.
--
-- It is only a 'Getter' because this value should be updated only when
-- the 'viewCenterRule' is specified to be a robot.
--
-- Technically it's the last robot ID specified by 'viewCenterRule',
-- but that robot may not be alive anymore - to be safe use 'focusedRobot'.
focusedRobotID :: Getter Robots RID
focusedRobotID = to _focusedRobotID
-- * Utilities
initRobots :: GameStateConfig -> Robots
initRobots gsc =
Robots
{ _robotMap = IM.empty
, _activeRobots = IS.empty
, _waitingRobots = M.empty
, _robotsByLocation = M.empty
, _robotsWatching = mempty
, _robotNaming =
RobotNaming
{ _nameGenerator = initNameParts gsc
, _gensym = 0
}
, _viewCenterRule = VCRobot 0
, _viewCenter = defaultCosmicLocation
, _focusedRobotID = 0
}
-- | The current rule for determining the center of the world view.
-- It updates also, 'viewCenter' and 'focusedRobot' to keep
-- everything synchronized.
viewCenterRule :: Lens' Robots ViewCenterRule
viewCenterRule = lens getter setter
where
getter :: Robots -> ViewCenterRule
getter = _viewCenterRule
-- The setter takes care of updating 'viewCenter' and 'focusedRobot'
-- So none of these fields get out of sync.
setter :: Robots -> ViewCenterRule -> Robots
setter g rule =
case rule of
VCLocation loc -> g {_viewCenterRule = rule, _viewCenter = loc}
VCRobot rid ->
let robotcenter = g ^? robotMap . ix rid . robotLocation
in -- retrieve the loc of the robot if it exists, Nothing otherwise.
-- sometimes, lenses are amazing...
case robotcenter of
Nothing -> g
Just loc -> g {_viewCenterRule = rule, _viewCenter = loc, _focusedRobotID = rid}
-- | Add a concrete instance of a robot template to the game state:
-- First, generate a unique ID number for it. Then, add it to the
-- main robot map, the active robot set, and to to the index of
-- robots by location. Return the updated robot.
addTRobot :: (Has (State Robots) sig m) => TRobot -> m Robot
addTRobot r = do
rid <- robotNaming . gensym <+= 1
let r' = instantiateRobot rid r
addRobot r'
return r'
-- | Add a robot to the game state, adding it to the main robot map,
-- the active robot set, and to to the index of robots by
-- location.
addRobot :: (Has (State Robots) sig m) => Robot -> m ()
addRobot r = do
robotMap %= IM.insert rid r
addRobotToLocation rid $ r ^. robotLocation
internalActiveRobots %= IS.insert rid
where
rid = r ^. robotID
-- | Helper function for updating the "robotsByLocation" bookkeeping
addRobotToLocation :: (Has (State Robots) sig m) => RID -> Cosmic Location -> m ()
addRobotToLocation rid rLoc =
robotsByLocation
%= M.insertWith
(M.unionWith IS.union)
(rLoc ^. subworld)
(M.singleton (rLoc ^. planar) (IS.singleton rid))
-- | Takes a robot out of the 'activeRobots' set and puts it in the 'waitingRobots'
-- queue.
sleepUntil :: (Has (State Robots) sig m) => RID -> TickNumber -> m ()
sleepUntil rid time = do
internalActiveRobots %= IS.delete rid
internalWaitingRobots . at time . non [] %= (rid :)
-- | Takes a robot out of the 'activeRobots' set.
sleepForever :: (Has (State Robots) sig m) => RID -> m ()
sleepForever rid = internalActiveRobots %= IS.delete rid
-- | Adds a robot to the 'activeRobots' set.
activateRobot :: (Has (State Robots) sig m) => RID -> m ()
activateRobot rid = internalActiveRobots %= IS.insert rid
-- | Removes robots whose wake up time matches the current game ticks count
-- from the 'waitingRobots' queue and put them back in the 'activeRobots' set
-- if they still exist in the keys of 'robotMap'.
wakeUpRobotsDoneSleeping :: (Has (State Robots) sig m) => TickNumber -> m ()
wakeUpRobotsDoneSleeping time = do
mrids <- internalWaitingRobots . at time <<.= Nothing
case mrids of
Nothing -> return ()
Just rids -> do
robots <- use robotMap
let aliveRids = filter (`IM.member` robots) rids
internalActiveRobots %= IS.union (IS.fromList aliveRids)
-- These robots' wake times may have been moved "forward"
-- by 'wakeWatchingRobots'.
clearWatchingRobots rids
-- | Clear the "watch" state of all of the
-- awakened robots
clearWatchingRobots ::
(Has (State Robots) sig m) =>
[RID] ->
m ()
clearWatchingRobots rids = do
robotsWatching %= M.map (`S.difference` S.fromList rids)
-- | Iterates through all of the currently @wait@-ing robots,
-- and moves forward the wake time of the ones that are @watch@-ing this location.
--
-- NOTE: Clearing 'TickNumber' map entries from 'internalWaitingRobots'
-- upon wakeup is handled by 'wakeUpRobotsDoneSleeping'
wakeWatchingRobots :: (Has (State Robots) sig m) => TickNumber -> Cosmic Location -> m ()
wakeWatchingRobots currentTick loc = do
waitingMap <- use waitingRobots
rMap <- use robotMap
watchingMap <- use robotsWatching
-- The bookkeeping updates to robot waiting
-- states are prepared in 4 steps...
let -- Step 1: Identify the robots that are watching this location.
botsWatchingThisLoc :: [Robot]
botsWatchingThisLoc =
mapMaybe (`IM.lookup` rMap) $
S.toList $
M.findWithDefault mempty loc watchingMap
-- Step 2: Get the target wake time for each of these robots
wakeTimes :: [(RID, TickNumber)]
wakeTimes = mapMaybe (sequenceA . (view robotID &&& waitingUntil)) botsWatchingThisLoc
wakeTimesToPurge :: Map TickNumber (S.Set RID)
wakeTimesToPurge = M.fromListWith (<>) $ map (fmap S.singleton . swap) wakeTimes
-- Step 3: Take these robots out of their time-indexed slot in "waitingRobots".
-- To preserve performance, this should be done without iterating over the
-- entire "waitingRobots" map.
filteredWaiting = foldr f waitingMap $ M.toList wakeTimesToPurge
where
-- Note: some of the map values may become empty lists.
-- But we shall not worry about cleaning those up here;
-- they will be "garbage collected" as a matter of course
-- when their tick comes up in "wakeUpRobotsDoneSleeping".
f (k, botsToRemove) = M.adjust (filter (`S.notMember` botsToRemove)) k
-- Step 4: Re-add the watching bots to be awakened at the next tick:
wakeableBotIds = map fst wakeTimes
newWakeTime = addTicks 1 currentTick
newInsertions = M.singleton newWakeTime wakeableBotIds
-- NOTE: There are two "sources of truth" for the waiting state of robots:
-- 1. In the GameState via "internalWaitingRobots"
-- 2. In each robot, via the CESK machine state
-- 1. Update the game state
internalWaitingRobots .= M.unionWith (<>) filteredWaiting newInsertions
-- 2. Update the machine of each robot
forM_ wakeableBotIds $ \rid ->
robotMap . at rid . _Just . machine %= \case
Waiting _ c -> Waiting newWakeTime c
x -> x
deleteRobot :: (Has (State Robots) sig m) => RID -> m ()
deleteRobot rn = do
internalActiveRobots %= IS.delete rn
mrobot <- robotMap . at rn <<.= Nothing
mrobot `forM_` \robot -> do
-- Delete the robot from the index of robots by location.
removeRobotFromLocationMap (robot ^. robotLocation) rn
-- | Makes sure empty sets don't hang around in the
-- 'robotsByLocation' map. We don't want a key with an
-- empty set at every location any robot has ever
-- visited!
removeRobotFromLocationMap ::
(Has (State Robots) sig m) =>
Cosmic Location ->
RID ->
m ()
removeRobotFromLocationMap (Cosmic oldSubworld oldPlanar) rid =
robotsByLocation %= M.update (tidyDelete rid) oldSubworld
where
deleteOne x = surfaceEmpty IS.null . IS.delete x
tidyDelete robID =
surfaceEmpty M.null . M.update (deleteOne robID) oldPlanar
setRobotList :: [Robot] -> Robots -> Robots
setRobotList robotList rState =
rState
& robotMap .~ IM.fromList (map (view robotID &&& id) robotList)
& robotsByLocation .~ M.map (groupRobotsByPlanarLocation . NE.toList) (groupRobotsBySubworld robotList)
& internalActiveRobots .~ setOf (traverse . robotID) robotList
where
groupRobotsBySubworld =
binTuples . map (view (robotLocation . subworld) &&& id)
groupRobotsByPlanarLocation rs =
M.fromListWith
IS.union
(map (view (robotLocation . planar) &&& (IS.singleton . view robotID)) rs)

View File

@ -0,0 +1,496 @@
{-# LANGUAGE TemplateHaskell #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Subrecord definitions that belong to 'Swarm.Game.State.GameState'
module Swarm.Game.State.Substate (
GameStateConfig (..),
REPLStatus (..),
WinStatus (..),
WinCondition (..),
ObjectiveCompletion (..),
_NoWinCondition,
_WinConditions,
Announcement (..),
RunStatus (..),
Seed,
Step (..),
SingleStep (..),
-- ** GameState fields
-- *** Randomness state
Randomness,
initRandomness,
seed,
randGen,
-- *** Temporal state
TemporalState,
initTemporalState,
gameStep,
runStatus,
ticks,
robotStepsPerTick,
paused,
-- *** Recipes
Recipes,
initRecipeMaps,
recipesOut,
recipesIn,
recipesCat,
-- *** Messages
Messages,
initMessages,
messageQueue,
lastSeenMessageTime,
announcementQueue,
-- *** Controls
GameControls,
initGameControls,
initiallyRunCode,
replStatus,
replNextValueIndex,
inputHandler,
-- *** Discovery
Discovery,
initDiscovery,
allDiscoveredEntities,
availableRecipes,
availableCommands,
knownEntities,
gameAchievements,
structureRecognition,
tagMembers,
-- *** Landscape
Landscape,
initLandscape,
worldNavigation,
multiWorld,
worldScrollable,
entityMap,
-- ** Notifications
Notifications (..),
notificationsCount,
notificationsContent,
-- ** Utilities
defaultRobotStepsPerTick,
replActiveType,
replWorking,
toggleRunStatus,
) where
import Control.Lens hiding (Const, use, uses, view, (%=), (+=), (.=), (<+=), (<<.=))
import Data.Aeson (FromJSON, ToJSON)
import Data.IntMap (IntMap)
import Data.List.NonEmpty (NonEmpty)
import Data.Map (Map)
import Data.Map qualified as M
import Data.Sequence (Seq)
import Data.Set qualified as S
import Data.Text (Text)
import GHC.Generics (Generic)
import Servant.Docs (ToSample)
import Servant.Docs qualified as SD
import Swarm.Game.Achievement.Attainment
import Swarm.Game.Achievement.Definitions
import Swarm.Game.CESK (TickNumber (..))
import Swarm.Game.Entity
import Swarm.Game.Location
import Swarm.Game.Recipe (
Recipe,
catRecipeMap,
inRecipeMap,
outRecipeMap,
)
import Swarm.Game.Robot
import Swarm.Game.Scenario.Objective
import Swarm.Game.Scenario.Topography.Navigation.Portal (Navigation (..))
import Swarm.Game.Scenario.Topography.Structure.Recognition
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (emptyFoundStructures)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (RecognizerAutomatons (..))
import Swarm.Game.State.Config
import Swarm.Game.Universe as U
import Swarm.Game.World qualified as W
import Swarm.Game.World.Gen (Seed)
import Swarm.Language.Pipeline (ProcessedTerm)
import Swarm.Language.Syntax (Const)
import Swarm.Language.Typed (Typed (Typed))
import Swarm.Language.Types (Polytype)
import Swarm.Language.Value (Value)
import Swarm.Log
import Swarm.Util.Lens (makeLensesNoSigs)
import System.Random (StdGen, mkStdGen)
-- * Subsidiary data types
-- | A data type to represent the current status of the REPL.
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 'Swarm.Language.Value.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
-- entered. The @Maybe Value@ starts out as 'Nothing' and gets
-- filled in with a result once the command completes.
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
| -- | 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
instance ToSample WinCondition where
toSamples _ =
SD.samples
[ NoWinCondition
-- TODO: #1552 add simple objective sample
]
-- | A data type to keep track of the pause mode.
data RunStatus
= -- | The game is running.
Running
| -- | The user paused the game, and it should stay pause after visiting the help.
ManualPause
| -- | The game got paused while visiting the help,
-- and it should unpause after returning back to the game.
AutoPause
deriving (Eq, Show, Generic, FromJSON, ToJSON)
-- | Switch (auto or manually) paused game to running and running to manually paused.
--
-- Note that this function is not safe to use in the app directly, because the UI
-- also tracks time between ticks---use 'Swarm.TUI.Controller.safeTogglePause' instead.
toggleRunStatus :: RunStatus -> RunStatus
toggleRunStatus s = if s == Running then ManualPause else Running
-- | A data type to keep track of some kind of log or sequence, with
-- an index to remember which ones are "new" and which ones have
-- "already been seen".
data Notifications a = Notifications
{ _notificationsCount :: Int
, _notificationsContent :: [a]
}
deriving (Eq, Show, Generic, FromJSON, ToJSON)
instance Semigroup (Notifications a) where
Notifications count1 xs1 <> Notifications count2 xs2 = Notifications (count1 + count2) (xs1 <> xs2)
instance Monoid (Notifications a) where
mempty = Notifications 0 []
makeLenses ''Notifications
data Recipes = Recipes
{ _recipesOut :: IntMap [Recipe Entity]
, _recipesIn :: IntMap [Recipe Entity]
, _recipesCat :: IntMap [Recipe Entity]
}
makeLensesNoSigs ''Recipes
-- | All recipes the game knows about, indexed by outputs.
recipesOut :: Lens' Recipes (IntMap [Recipe Entity])
-- | All recipes the game knows about, indexed by inputs.
recipesIn :: Lens' Recipes (IntMap [Recipe Entity])
-- | All recipes the game knows about, indexed by requirement/catalyst.
recipesCat :: Lens' Recipes (IntMap [Recipe Entity])
data Messages = Messages
{ _messageQueue :: Seq LogEntry
, _lastSeenMessageTime :: TickNumber
, _announcementQueue :: Seq Announcement
}
makeLensesNoSigs ''Messages
-- | A queue of global messages.
--
-- Note that we put the newest entry to the right.
messageQueue :: Lens' Messages (Seq LogEntry)
-- | Last time message queue has been viewed (used for notification).
lastSeenMessageTime :: Lens' Messages TickNumber
-- | A queue of global announcements.
-- 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' Messages (Seq Announcement)
-- | Type for remembering which robots will be run next in a robot step mode.
--
-- Once some robots have run, we need to store 'RID' to know which ones should go next.
-- At 'SBefore' no robots were run yet, so it is safe to transition to and from 'WorldTick'.
--
-- @
-- tick
-- ┌────────────────────────────────────┐
-- │ │
-- │ step │
-- │ ┌────┐ │
-- ▼ ▼ │ │
-- ┌───────┐ step ┌───────┴───┐ step ┌────┴─────┐
-- │SBefore├──────►│SSingle RID├──────►│SAfter RID│
-- └──┬────┘ └───────────┘ └────┬─────┘
-- │ ▲ player ▲ │
-- ▼ │ switch └───────────────────┘
-- ┌────┴────┐ view RID > oldRID
-- │WorldTick│
-- └─────────┘
-- @
data SingleStep
= -- | Run the robots from the beginning until the focused robot (noninclusive).
SBefore
| -- | Run a single step of the focused robot.
SSingle RID
| -- | Run robots after the (previously) focused robot and finish the tick.
SAfter RID
-- | Game step mode - we use the single step mode when debugging robot 'CESK' machine.
data Step = WorldTick | RobotStep SingleStep
data TemporalState = TemporalState
{ _gameStep :: Step
, _runStatus :: RunStatus
, _ticks :: TickNumber
, _robotStepsPerTick :: Int
}
makeLensesNoSigs ''TemporalState
-- | How to step the game: 'WorldTick' or 'RobotStep' for debugging the 'CESK' machine.
gameStep :: Lens' TemporalState Step
-- | The current 'RunStatus'.
runStatus :: Lens' TemporalState RunStatus
-- | Whether the game is currently paused.
paused :: Getter TemporalState Bool
paused = to (\s -> s ^. runStatus /= Running)
-- | The number of ticks elapsed since the game started.
ticks :: Lens' TemporalState TickNumber
-- | The maximum number of CESK machine steps a robot may take during
-- a single tick.
robotStepsPerTick :: Lens' TemporalState Int
data GameControls = GameControls
{ _replStatus :: REPLStatus
, _replNextValueIndex :: Integer
, _inputHandler :: Maybe (Text, Value)
, _initiallyRunCode :: Maybe ProcessedTerm
}
makeLensesNoSigs ''GameControls
-- | The current status of the REPL.
replStatus :: Lens' GameControls REPLStatus
-- | The index of the next @it{index}@ value
replNextValueIndex :: Lens' GameControls Integer
-- | The currently installed input handler and hint text.
inputHandler :: Lens' GameControls (Maybe (Text, Value))
-- | Code that is run upon scenario start, before any
-- REPL interaction.
initiallyRunCode :: Lens' GameControls (Maybe ProcessedTerm)
data Discovery = Discovery
{ _allDiscoveredEntities :: Inventory
, _availableRecipes :: Notifications (Recipe Entity)
, _availableCommands :: Notifications Const
, _knownEntities :: S.Set EntityName
, _gameAchievements :: Map GameplayAchievement Attainment
, _structureRecognition :: StructureRecognizer
, _tagMembers :: Map Text (NonEmpty EntityName)
}
makeLensesNoSigs ''Discovery
-- | The list of entities that have been discovered.
allDiscoveredEntities :: Lens' Discovery Inventory
-- | The list of available recipes.
availableRecipes :: Lens' Discovery (Notifications (Recipe Entity))
-- | The list of available commands.
availableCommands :: Lens' Discovery (Notifications Const)
-- | The names of entities that should be considered \"known\", that is,
-- robots know what they are without having to scan them.
knownEntities :: Lens' Discovery (S.Set EntityName)
-- | Map of in-game achievements that were obtained
gameAchievements :: Lens' Discovery (Map GameplayAchievement Attainment)
-- | Recognizer for robot-constructed structures
structureRecognition :: Lens' Discovery StructureRecognizer
-- | Map from tags to entities that possess that tag
tagMembers :: Lens' Discovery (Map Text (NonEmpty EntityName))
data Landscape = Landscape
{ _worldNavigation :: Navigation (M.Map SubworldName) Location
, _multiWorld :: W.MultiWorld Int Entity
, _entityMap :: EntityMap
, _worldScrollable :: Bool
}
makeLensesNoSigs ''Landscape
-- | Includes a 'Map' of named locations and an
-- "edge list" (graph) that maps portal entrances to exits
worldNavigation :: Lens' Landscape (Navigation (M.Map SubworldName) Location)
-- | The current state of the world (terrain and entities only; robots
-- are stored in the 'robotMap'). 'Int' is used instead of
-- 'TerrainType' because we need to be able to store terrain values in
-- unboxed tile arrays.
multiWorld :: Lens' Landscape (W.MultiWorld Int Entity)
-- | The catalog of all entities that the game knows about.
entityMap :: Lens' Landscape EntityMap
-- | Whether the world map is supposed to be scrollable or not.
worldScrollable :: Lens' Landscape Bool
data Randomness = Randomness
{ _seed :: Seed
, _randGen :: StdGen
}
makeLensesNoSigs ''Randomness
-- | The initial seed that was used for the random number generator,
-- and world generation.
seed :: Lens' Randomness Seed
-- | Pseudorandom generator initialized at start.
randGen :: Lens' Randomness StdGen
-- * Utilities
-- | Whether the repl is currently working.
replWorking :: Getter GameControls Bool
replWorking = to (\s -> matchesWorking $ s ^. replStatus)
where
matchesWorking (REPLDone _) = False
matchesWorking (REPLWorking _) = True
-- | Either the type of the command being executed, or of the last command
replActiveType :: Getter REPLStatus (Maybe Polytype)
replActiveType = to getter
where
getter (REPLDone (Just (Typed _ typ _))) = Just typ
getter (REPLWorking (Typed _ typ _)) = Just typ
getter _ = Nothing
-- | By default, robots may make a maximum of 100 CESK machine steps
-- during one game tick.
defaultRobotStepsPerTick :: Int
defaultRobotStepsPerTick = 100
-- * Record initialization
initTemporalState :: TemporalState
initTemporalState =
TemporalState
{ _gameStep = WorldTick
, _runStatus = Running
, _ticks = TickNumber 0
, _robotStepsPerTick = defaultRobotStepsPerTick
}
initGameControls :: GameControls
initGameControls =
GameControls
{ _replStatus = REPLDone Nothing
, _replNextValueIndex = 0
, _inputHandler = Nothing
, _initiallyRunCode = Nothing
}
initMessages :: Messages
initMessages =
Messages
{ _messageQueue = Empty
, _lastSeenMessageTime = TickNumber (-1)
, _announcementQueue = mempty
}
initLandscape :: GameStateConfig -> Landscape
initLandscape gsc =
Landscape
{ _worldNavigation = Navigation mempty mempty
, _multiWorld = mempty
, _entityMap = initEntities gsc
, _worldScrollable = True
}
initDiscovery :: Discovery
initDiscovery =
Discovery
{ _availableRecipes = mempty
, _availableCommands = mempty
, _allDiscoveredEntities = empty
, _knownEntities = mempty
, -- This does not need to be initialized with anything,
-- since the master list of achievements is stored in UIState
_gameAchievements = mempty
, _structureRecognition = StructureRecognizer (RecognizerAutomatons mempty mempty) emptyFoundStructures []
, _tagMembers = mempty
}
initRandomness :: Randomness
initRandomness =
Randomness
{ _seed = 0
, _randGen = mkStdGen 0
}
initRecipeMaps :: GameStateConfig -> Recipes
initRecipeMaps gsc =
Recipes
{ _recipesOut = outRecipeMap (initRecipes gsc)
, _recipesIn = inRecipeMap (initRecipes gsc)
, _recipesCat = catRecipeMap (initRecipes gsc)
}

View File

@ -51,6 +51,8 @@ import Swarm.Game.Robot
import Swarm.Game.Scenario.Objective qualified as OB
import Swarm.Game.Scenario.Objective.WinCheck qualified as WC
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.Const
import Swarm.Game.Step.RobotStepState
import Swarm.Game.Step.Util
@ -77,7 +79,8 @@ import Prelude hiding (Applicative (..), lookup)
-- the tick. Use the return value to check whether a full tick happened.
gameTick :: (Has (State GameState) sig m, Has (Lift IO) sig m, Has Effect.Time sig m) => m Bool
gameTick = do
wakeUpRobotsDoneSleeping
time <- use $ temporal . ticks
zoomRobots $ wakeUpRobotsDoneSleeping time
active <- use $ robotInfo . activeRobots
focusedRob <- use $ robotInfo . focusedRobotID
@ -135,17 +138,18 @@ finishGameTick =
insertBackRobot :: Has (State GameState) sig m => RID -> Robot -> m ()
insertBackRobot rn rob = do
time <- use $ temporal . ticks
if rob ^. selfDestruct
then deleteRobot rn
else do
robotInfo . robotMap %= IM.insert rn rob
case waitingUntil rob of
Just wakeUpTime
-- if w=2 t=1 then we do not needlessly put robot to waiting queue
| wakeUpTime <= addTicks 2 time -> return ()
| otherwise -> sleepUntil rn wakeUpTime
Nothing ->
unless (isActive rob) (sleepForever rn)
zoomRobots $
if rob ^. selfDestruct
then deleteRobot rn
else do
robotMap %= IM.insert rn rob
case waitingUntil rob of
Just wakeUpTime
-- if w=2 t=1 then we do not needlessly put robot to waiting queue
| wakeUpTime <= addTicks 2 time -> return ()
| otherwise -> sleepUntil rn wakeUpTime
Nothing ->
unless (isActive rob) (sleepForever rn)
-- Run a set of robots - this is used to run robots before/after the focused one.
runRobotIDs :: (Has (State GameState) sig m, Has (Lift IO) sig m, Has Effect.Time sig m) => IS.IntSet -> m ()
@ -380,7 +384,7 @@ evaluateCESK ::
evaluateCESK cesk = do
createdAt <- getNow
let r = hypotheticalRobot cesk createdAt
addRobot r -- Add the special robot to the robot map, so it can look itself up if needed
zoomRobots $ addRobot r -- Add the special robot to the robot map, so it can look itself up if needed
evalState r . runCESK $ cesk
runCESK ::

View File

@ -31,6 +31,8 @@ import Swarm.Game.Entity qualified as E
import Swarm.Game.Location
import Swarm.Game.Robot
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.RobotStepState
import Swarm.Game.Step.Util
import Swarm.Game.Step.Util.Inspect
@ -93,26 +95,27 @@ addCombustionBot inputEntity combustibility ts loc = do
return $ maybe [] (pure . (1,)) maybeE
combustionDurationRand <- uniform durationRange
let combustionProg = combustionProgram combustionDurationRand combustibility
void $
addTRobot $
mkRobot
()
Nothing
"fire"
(Markdown.fromText $ T.unwords ["A burning", (inputEntity ^. entityName) <> "."])
(Just loc)
zero
( defaultEntityDisplay '*'
& displayAttr .~ AWorld "fire"
& displayPriority .~ 0
)
(initMachine combustionProg empty emptyStore)
[]
botInventory
True
False
mempty
ts
void
. zoomRobots
. addTRobot
$ mkRobot
()
Nothing
"fire"
(Markdown.fromText $ T.unwords ["A burning", (inputEntity ^. entityName) <> "."])
(Just loc)
zero
( defaultEntityDisplay '*'
& displayAttr .~ AWorld "fire"
& displayPriority .~ 0
)
(initMachine combustionProg empty emptyStore)
[]
botInventory
True
False
mempty
ts
return combustionDurationRand
where
Combustibility _ durationRange maybeCombustionProduct = combustibility
@ -191,7 +194,7 @@ igniteNeighbor creationTime sourceDuration loc = do
. min (fromIntegral sourceDuration)
. negate
$ log ignitionDelayRand / rate
addIgnitionBot ignitionDelay e creationTime loc
zoomRobots $ addIgnitionBot ignitionDelay e creationTime loc
where
neighborCombustibility = (e ^. entityCombustion) ? defaultCombustibility
rate = E.ignition neighborCombustibility
@ -201,7 +204,7 @@ igniteNeighbor creationTime sourceDuration loc = do
-- Its sole purpose is to delay the 'Swarm.Language.Syntax.Ignite' command for a neighbor
-- that has been a priori determined that it shall be ignited.
addIgnitionBot ::
Has (State GameState) sig m =>
Has (State Robots) sig m =>
Integer ->
Entity ->
TimeSpec ->

View File

@ -64,6 +64,8 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition (automatons, foundSt
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (foundByName)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.Arithmetic
import Swarm.Game.Step.Combustion qualified as Combustion
import Swarm.Game.Step.Path.Finding
@ -716,9 +718,10 @@ execConst runChildProg c vs s k = do
forM_ maybeRidLoc $ \(rid, loc') ->
robotInfo . robotMap . at rid . _Just . robotLog %= addLatestClosest loc'
robotsAround <-
if isPrivileged
then use $ robotInfo . robotMap . to IM.elems
else gets $ robotsInArea loc hearingDistance
zoomRobots $
if isPrivileged
then use $ robotMap . to IM.elems
else gets $ robotsInArea loc hearingDistance
mapM_ addToRobotLog robotsAround
return $ mkReturn ()
_ -> badConst
@ -820,9 +823,9 @@ execConst runChildProg c vs s k = do
-- able to halt privileged ones.
omni <- isPrivilegedBot
case omni || not (target ^. systemRobot) of
True -> do
True -> zoomRobots $ do
-- Cancel its CESK machine, and put it to sleep.
robotInfo . robotMap . at targetID . _Just . machine %= cancel
robotMap . at targetID . _Just . machine %= cancel
sleepForever targetID
return $ mkReturn ()
False -> throwError $ cmdExn c ["You are not authorized to halt that robot."]
@ -853,14 +856,15 @@ execConst runChildProg c vs s k = do
let neighbor =
find ((/= rid) . (^. robotID)) -- pick one other than ourself
. sortOn ((manhattan `on` view planar) loc . (^. robotLocation)) -- prefer closer
$ robotsInArea loc 1 g -- all robots within Manhattan distance 1
$ robotsInArea loc 1
$ g ^. robotInfo -- all robots within Manhattan distance 1
return $ mkReturn neighbor
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
let neighborIDs = filter (/= rid) . map (^. robotID) $ robotsInArea loc 1 $ g ^. robotInfo
return $ Out b s (FMeetAll f neighborIDs : k)
_ -> badConst
Whoami -> case vs of
@ -982,15 +986,16 @@ execConst runChildProg c vs s k = do
-- the childRobot inherits the parent robot's environment
-- and context which collectively mean all the variables
-- declared in the parent robot
robotInfo . robotMap . at childRobotID . _Just . machine .= In cmd e s [FExec]
robotInfo . robotMap . at childRobotID . _Just . robotContext .= r ^. robotContext
zoomRobots $ do
robotMap . at childRobotID . _Just . machine .= In cmd e s [FExec]
robotMap . at childRobotID . _Just . robotContext .= r ^. robotContext
-- Provision the target robot with any required devices and
-- inventory that are lacking.
provisionChild childRobotID (fromList . S.toList $ toEquip) toGive
-- Finally, re-activate the reprogrammed target robot.
activateRobot childRobotID
zoomRobots $ activateRobot childRobotID
return $ mkReturn ()
_ -> badConst
@ -1032,7 +1037,7 @@ execConst runChildProg c vs s k = do
-- Construct the new robot and add it to the world.
parentCtx <- use robotContext
newRobot <-
addTRobot . (trobotContext .~ parentCtx) $
zoomRobots . addTRobot . (trobotContext .~ parentCtx) $
mkRobot
()
(Just pid)
@ -1102,13 +1107,14 @@ execConst runChildProg c vs s k = do
giveItem item = TApp (TApp (TConst Give) (TRobot ourID)) (TText item)
-- Reprogram and activate the salvaged robot
robotInfo
. robotMap
. at (target ^. robotID)
. traverse
. machine
.= In giveInventory empty emptyStore [FExec]
activateRobot (target ^. robotID)
zoomRobots $ do
robotMap
. at (target ^. robotID)
. traverse
. machine
.= In giveInventory empty emptyStore [FExec]
activateRobot $ target ^. robotID
-- Now wait the right amount of time for it to finish.
time <- use $ temporal . ticks

View File

@ -29,6 +29,8 @@ import Swarm.Game.ResourceLoading (NameGenerator (..))
import Swarm.Game.Robot
import Swarm.Game.Scenario.Topography.Structure.Recognition.Tracking qualified as SRT
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.Path.Cache
import Swarm.Game.Step.Path.Type
import Swarm.Game.Step.Path.Walkability
@ -68,7 +70,8 @@ updateEntityAt cLoc@(Cosmic subworldName loc) upd = do
W.updateM @Int (W.locToCoords loc) upd
forM_ (WM.getModification =<< someChange) $ \modType -> do
wakeWatchingRobots cLoc
currentTick <- use $ temporal . ticks
zoomRobots $ wakeWatchingRobots currentTick cLoc
SRT.entityModified modType cLoc
pcr <- use $ pathCaching . pathCachingRobots

View File

@ -41,6 +41,8 @@ import Swarm.Game.Recipe
import Swarm.Game.Robot
import Swarm.Game.Scenario.Topography.Navigation.Portal (Navigation (..), destination, reorientation)
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.RobotStepState
import Swarm.Game.Step.Util
import Swarm.Game.Universe
@ -111,8 +113,9 @@ updateRobotLocation oldLoc newLoc
| otherwise = do
newlocWithPortal <- applyPortal newLoc
rid <- use robotID
removeRobotFromLocationMap oldLoc rid
addRobotToLocation rid newlocWithPortal
zoomRobots $ do
removeRobotFromLocationMap oldLoc rid
addRobotToLocation rid newlocWithPortal
modify (unsafeSetRobotLocation newlocWithPortal)
flagRedraw
where
@ -142,9 +145,10 @@ onTarget rid act = do
Nothing -> return ()
Just tgt -> do
tgt' <- execState @Robot tgt act
if tgt' ^. selfDestruct
then deleteRobot rid
else robotInfo . robotMap . ix rid .= tgt'
zoomRobots $
if tgt' ^. selfDestruct
then deleteRobot rid
else robotMap . ix rid .= tgt'
grantAchievement ::
(Has (State GameState) sig m, Has (Lift IO) sig m) =>
@ -258,8 +262,9 @@ provisionChild ::
m ()
provisionChild childID toEquip toGive = do
-- Equip and give devices to child
robotInfo . robotMap . ix childID . equippedDevices %= E.union toEquip
robotInfo . robotMap . ix childID . robotInventory %= E.union toGive
zoomRobots $ do
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
@ -368,26 +373,27 @@ addSeedBot ::
TimeSpec ->
m ()
addSeedBot e (minT, maxT) loc ts =
void $
addTRobot $
mkRobot
()
Nothing
"seed"
(Markdown.fromText $ T.unwords ["A growing", e ^. entityName, "seed."])
(Just loc)
zero
( defaultEntityDisplay '.'
& displayAttr .~ (e ^. entityDisplay . displayAttr)
& displayPriority .~ 0
)
(initMachine (seedProgram minT (maxT - minT) (e ^. entityName)) empty emptyStore)
[]
[(1, e)]
True
False
mempty
ts
void
. zoomRobots
. addTRobot
$ mkRobot
()
Nothing
"seed"
(Markdown.fromText $ T.unwords ["A growing", e ^. entityName, "seed."])
(Just loc)
zero
( defaultEntityDisplay '.'
& displayAttr .~ (e ^. entityDisplay . displayAttr)
& displayPriority .~ 0
)
(initMachine (seedProgram minT (maxT - minT) (e ^. entityName)) empty emptyStore)
[]
[(1, e)]
True
False
mempty
ts
-- | A system program for a "seed robot", to regrow a growable entity
-- after it is harvested.

View File

@ -13,6 +13,7 @@ import Data.Text (Text)
import Swarm.Game.Location
import Swarm.Game.Robot
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.Universe
import Swarm.Language.Direction
import Swarm.Util (listEnums)

View File

@ -30,6 +30,7 @@ import Swarm.Game.Scenario.Topography.Cell
import Swarm.Game.Scenario.Topography.Center
import Swarm.Game.Scenario.Topography.EntityFacade (EntityFacade (..), mkFacade)
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Game.Terrain (getTerrainWord)
import Swarm.Game.Universe
import Swarm.Game.World qualified as W

View File

@ -82,6 +82,8 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition (automatons)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (definitions)
import Swarm.Game.ScenarioInfo
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step (finishGameTick, gameTick)
import Swarm.Language.Capability (Capability (CDebug, CGod, CMake), constCaps)
import Swarm.Language.Context
@ -869,7 +871,7 @@ updateUI = do
False -> pure False
True -> do
-- Reset the log updated flag
zoomGameState clearFocusedRobotLogUpdated
zoomGameState $ zoomRobots clearFocusedRobotLogUpdated
-- Find and focus an equipped "logger" device in the inventory list.
let isLogger (EquippedEntry e) = e ^. entityName == "logger"
@ -1058,7 +1060,7 @@ runInputHandler kc = do
let topCtx = topContext s
handlerCESK = Out (VKey kc) (topCtx ^. defStore) [FApp handler, FExec]
gameState . baseRobot . machine .= handlerCESK
gameState %= execState (activateRobot 0)
gameState %= execState (zoomRobots $ activateRobot 0)
-- | Handle a user "piloting" input event for the REPL.
handleREPLEventPiloting :: BrickEvent Name AppEvent -> EventM Name AppState ()
@ -1136,7 +1138,7 @@ runBaseTerm topCtx =
-- 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))
. (gameState %~ execState (zoomRobots $ activateRobot 0))
-- | Handle a user input event for the REPL.
handleREPLEventTyping :: BrickEvent Name AppEvent -> EventM Name AppState ()
@ -1474,7 +1476,7 @@ makeEntity e = do
Just False -> do
gameState . gameControls . replStatus .= REPLWorking (Typed Nothing PolyUnit (R.singletonCap CMake))
gameState . baseRobot . machine .= initMachine mkPT empty topStore
gameState %= execState (activateRobot 0)
gameState %= execState (zoomRobots $ activateRobot 0)
_ -> continueWithoutRedraw
-- | Display a modal window with the description of an entity.

View File

@ -12,6 +12,8 @@ import Control.Monad.IO.Class (liftIO)
import Data.Map qualified as M
import Graphics.Vty qualified as V
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Universe
import Swarm.Game.World qualified as W
import Swarm.TUI.Model

View File

@ -18,6 +18,7 @@ import Data.Yaml qualified as Y
import Graphics.Vty qualified as V
import Swarm.Game.Scenario.Topography.EntityFacade
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Game.Universe
import Swarm.Game.World qualified as W
import Swarm.TUI.Controller.Util

View File

@ -20,7 +20,8 @@ import Data.Functor.Identity (runIdentity)
import Data.Text qualified as T
import Swarm.Game.Failure (SystemFailure)
import Swarm.Game.Scenario.Status (ParameterizableLaunchParams (..), ScenarioInfoPair, getLaunchParams, scenarioStatus)
import Swarm.Game.State (Seed, ValidatedLaunchParams, getRunCodePath, parseCodeFile)
import Swarm.Game.State (ValidatedLaunchParams, getRunCodePath, parseCodeFile)
import Swarm.Game.State.Substate (Seed)
import Swarm.Language.Pretty (prettyText)
import Swarm.TUI.Launch.Model
import Swarm.TUI.Model.Name

View File

@ -145,6 +145,7 @@ import Swarm.Game.Robot
import Swarm.Game.Scenario.Status
import Swarm.Game.ScenarioInfo (ScenarioCollection, loadScenarios, _SISingle)
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Game.World.Load (loadWorlds)
import Swarm.Game.World.Typecheck (WorldMap)
import Swarm.Log

View File

@ -29,7 +29,7 @@ import Swarm.Game.ScenarioInfo (
scMap,
scenarioCollectionToList,
)
import Swarm.Game.State
import Swarm.Game.State.Substate (Seed)
import Swarm.TUI.Model.Name
import Swarm.Util
import System.FilePath (dropTrailingPathSeparator, splitPath, takeFileName)

View File

@ -60,6 +60,7 @@ import Swarm.Game.ScenarioInfo (
_SISingle,
)
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Language.Pretty (prettyText)
import Swarm.Log (LogSource (SystemLog), Severity (..))
import Swarm.TUI.Editor.Model qualified as EM

View File

@ -100,6 +100,8 @@ import Swarm.Game.ScenarioInfo (
scenarioItemName,
)
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Universe
import Swarm.Game.World qualified as W
import Swarm.Language.Capability (Capability (..), constCaps)
@ -1436,7 +1438,11 @@ drawREPL s =
(Just False, _) -> renderREPLPrompt (s ^. uiState . uiFocusRing) theRepl
_running -> padRight Max $ txt "..."
theRepl = s ^. uiState . uiREPL
-- NOTE: there exists a lens named 'baseRobot' that uses "unsafe"
-- indexing that may be an alternative to this:
base = s ^. gameState . robotInfo . robotMap . at 0
fmt (REPLEntry e) = txt $ "> " <> e
fmt (REPLOutput t) = txt t
fmt (REPLError t) = txtWrapWith indent2 {preserveIndentation = True} t

View File

@ -36,6 +36,8 @@ import Swarm.Game.Scenario.Topography.EntityFacade
import Swarm.Game.Scenario.Topography.Structure.Recognition (foundStructures)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (foundByLocation)
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Terrain
import Swarm.Game.Universe
import Swarm.Game.World qualified as W

View File

@ -27,6 +27,7 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute (getEntit
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (foundByName)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Structure
import Swarm.TUI.View.Attribute.Attr

View File

@ -20,6 +20,7 @@ import Swarm.Game.Location
import Swarm.Game.Scenario (scenarioName)
import Swarm.Game.ScenarioInfo (scenarioItemName)
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.Game.Terrain
import Swarm.Language.Pretty (prettyTextLine)
import Swarm.Language.Syntax (Syntax)

View File

@ -71,6 +71,8 @@ import Swarm.Game.Scenario.Topography.Structure.Recognition
import Swarm.Game.Scenario.Topography.Structure.Recognition.Log
import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step.Path.Type
import Swarm.Language.Module
import Swarm.Language.Pipeline

View File

@ -163,6 +163,9 @@ library
Swarm.Game.Scenario.Topography.WorldPalette
Swarm.Game.ScenarioInfo
Swarm.Game.State
Swarm.Game.State.Config
Swarm.Game.State.Robot
Swarm.Game.State.Substate
Swarm.Game.Step
Swarm.Game.Step.Arithmetic
Swarm.Game.Step.Combustion

View File

@ -15,7 +15,9 @@ import Swarm.Game.CESK (emptyStore, initMachine)
import Swarm.Game.Display (defaultRobotDisplay)
import Swarm.Game.Location
import Swarm.Game.Robot (TRobot, mkRobot)
import Swarm.Game.State (GameState, addTRobot, creativeMode, landscape, multiWorld)
import Swarm.Game.State (GameState, creativeMode, landscape, zoomRobots)
import Swarm.Game.State.Robot (addTRobot)
import Swarm.Game.State.Substate (multiWorld)
import Swarm.Game.Step (gameTick)
import Swarm.Game.Terrain (TerrainType (DirtT))
import Swarm.Game.Universe (Cosmic (..), SubworldName (DefaultRootSubworld))
@ -120,7 +122,7 @@ mkGameState robotMaker numRobots = do
let robots = [robotMaker (Location (fromIntegral x) 0) | x <- [0 .. numRobots - 1]]
Right initAppState <- runExceptT classicGame0
execStateT
(mapM addTRobot robots)
(zoomRobots $ mapM addTRobot robots)
( (initAppState ^. gameState)
& creativeMode .~ True
& landscape . multiWorld .~ M.singleton DefaultRootSubworld (newWorld (WF $ const (fromEnum DirtT, ENothing)))

View File

@ -37,24 +37,28 @@ import Swarm.Game.Robot (activityCounts, commandsHistogram, defReqs, equippedDev
import Swarm.Game.Scenario (Scenario)
import Swarm.Game.State (
GameState,
WinCondition (WinConditions),
WinStatus (Won),
activeRobots,
baseRobot,
discovery,
gameAchievements,
messageInfo,
messageQueue,
notificationsContent,
pathCaching,
robotInfo,
robotMap,
temporal,
ticks,
waitingRobots,
winCondition,
winSolution,
)
import Swarm.Game.State.Robot (
activeRobots,
robotMap,
waitingRobots,
)
import Swarm.Game.State.Substate (
WinCondition (WinConditions),
WinStatus (Won),
gameAchievements,
messageQueue,
notificationsContent,
ticks,
)
import Swarm.Game.Step (gameTick)
import Swarm.Game.Step.Path.Type
import Swarm.Game.World.Typecheck (WorldMap)

View File

@ -12,6 +12,8 @@ import Data.Text qualified as T
import Swarm.Game.CESK (TickNumber (..))
import Swarm.Game.Robot
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Log
import Test.Tasty
import Test.Tasty.HUnit

View File

@ -18,6 +18,8 @@ import Swarm.Game.CESK
import Swarm.Game.Exception
import Swarm.Game.Robot
import Swarm.Game.State
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step (gameTick, hypotheticalRobot, stepCESK)
import Swarm.Language.Context
import Swarm.Language.Pipeline (ProcessedTerm (..), processTerm)
@ -60,7 +62,7 @@ play g = either (return . (,g) . Left) playPT . processTerm1
hid = hr ^. robotID
gs =
g
& execState (addRobot hr)
& execState (zoomRobots $ addRobot hr)
& robotInfo . viewCenterRule .~ VCRobot hid
& creativeMode .~ True