mirror of
https://github.com/swarm-game/swarm.git
synced 2024-09-11 14:46:33 +03:00
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:
parent
b244a4223c
commit
3094abd565
@ -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
20
src/Swarm/Game/State/Config.hs
Normal file
20
src/Swarm/Game/State/Config.hs
Normal 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
|
||||
}
|
394
src/Swarm/Game/State/Robot.hs
Normal file
394
src/Swarm/Game/State/Robot.hs
Normal 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)
|
496
src/Swarm/Game/State/Substate.hs
Normal file
496
src/Swarm/Game/State/Substate.hs
Normal 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)
|
||||
}
|
@ -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 ::
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user