Skip to content

Commit c40b150

Browse files
Add available commands help (#458)
This change adds a new help panel with the list of recently acquired commands. The panels currently shows `command name :: command type signature`, for each newly available command. Fixes #436
1 parent 9b6eaef commit c40b150

File tree

6 files changed

+203
-118
lines changed

6 files changed

+203
-118
lines changed

src/Swarm/Game/State.hs

+38-9
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module Swarm.Game.State (
3737
robotsByLocation,
3838
activeRobots,
3939
availableRecipes,
40-
availableRecipesNewCount,
40+
availableCommands,
4141
allDiscoveredEntities,
4242
gensym,
4343
randGen,
@@ -57,6 +57,11 @@ module Swarm.Game.State (
5757
focusedRobotID,
5858
ticks,
5959

60+
-- ** Notifications
61+
Notifications (..),
62+
notificationsCount,
63+
notificationsContent,
64+
6065
-- ** GameState initialization
6166
initGameState,
6267
scenarioToGameState,
@@ -82,7 +87,7 @@ module Swarm.Game.State (
8287

8388
import Control.Applicative ((<|>))
8489
import Control.Arrow (Arrow ((&&&)))
85-
import Control.Lens hiding (use, uses, view, (%=), (+=), (.=), (<+=), (<<.=))
90+
import Control.Lens hiding (Const, use, uses, view, (%=), (+=), (.=), (<+=), (<<.=))
8691
import Control.Monad.Except
8792
import Data.Array (Array, listArray)
8893
import Data.Int (Int64)
@@ -95,6 +100,7 @@ import Data.List (partition)
95100
import Data.Map (Map)
96101
import qualified Data.Map as M
97102
import Data.Maybe (fromMaybe)
103+
import qualified Data.Set as S
98104
import Data.Text (Text)
99105
import qualified Data.Text as T (lines)
100106
import qualified Data.Text.IO as T (readFile)
@@ -115,10 +121,11 @@ import Swarm.Game.Scenario
115121
import Swarm.Game.Value
116122
import qualified Swarm.Game.World as W
117123
import Swarm.Game.WorldGen (Seed)
124+
import Swarm.Language.Capability (constCaps)
118125
import qualified Swarm.Language.Context as Ctx
119126
import Swarm.Language.Pipeline (ProcessedTerm)
120127
import Swarm.Language.Pipeline.QQ (tmQ)
121-
import Swarm.Language.Syntax (Term (TString))
128+
import Swarm.Language.Syntax (Const, Term (TString), allConst)
122129
import Swarm.Language.Types
123130
import Swarm.Util
124131

@@ -172,6 +179,21 @@ data RunStatus
172179
AutoPause
173180
deriving (Eq, Show)
174181

182+
-- | A data type to keep track of discovered recipes and commands
183+
data Notifications a = Notifications
184+
{ _notificationsCount :: Int
185+
, _notificationsContent :: [a]
186+
}
187+
deriving (Eq, Show)
188+
189+
instance Semigroup (Notifications a) where
190+
Notifications count1 xs1 <> Notifications count2 xs2 = Notifications (count1 + count2) (xs1 <> xs2)
191+
192+
instance Monoid (Notifications a) where
193+
mempty = Notifications 0 []
194+
195+
makeLenses ''Notifications
196+
175197
------------------------------------------------------------
176198
-- The main GameState record type
177199
------------------------------------------------------------
@@ -201,8 +223,8 @@ data GameState = GameState
201223
_waitingRobots :: Map Integer [RID]
202224
, _robotsByLocation :: Map (V2 Int64) IntSet
203225
, _allDiscoveredEntities :: Inventory
204-
, _availableRecipes :: [Recipe Entity]
205-
, _availableRecipesNewCount :: Int
226+
, _availableRecipes :: Notifications (Recipe Entity)
227+
, _availableCommands :: Notifications Const
206228
, _gensym :: Int
207229
, _randGen :: StdGen
208230
, _adjList :: Array Int Text
@@ -273,10 +295,10 @@ robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)
273295
allDiscoveredEntities :: Lens' GameState Inventory
274296

275297
-- | The list of available recipes.
276-
availableRecipes :: Lens' GameState [Recipe Entity]
298+
availableRecipes :: Lens' GameState (Notifications (Recipe Entity))
277299

278-
-- | The number of new recipes (reset to 0 when the player open the recipes view).
279-
availableRecipesNewCount :: Lens' GameState Int
300+
-- | The list of available commands.
301+
availableCommands :: Lens' GameState (Notifications Const)
280302

281303
-- | The names of the robots that are currently not sleeping.
282304
activeRobots :: Getter GameState IntSet
@@ -528,7 +550,7 @@ initGameState = do
528550
, _robotMap = IM.empty
529551
, _robotsByLocation = M.empty
530552
, _availableRecipes = mempty
531-
, _availableRecipesNewCount = 0
553+
, _availableCommands = mempty
532554
, _allDiscoveredEntities = empty
533555
, _activeRobots = IS.empty
534556
, _waitingRobots = M.empty
@@ -572,6 +594,7 @@ scenarioToGameState scenario userSeed toRun g = do
572594
M.fromListWith IS.union $
573595
map (view robotLocation &&& (IS.singleton . view robotID)) robotList
574596
, _activeRobots = setOf (traverse . robotID) robotList
597+
, _availableCommands = Notifications 0 initialCommands
575598
, _waitingRobots = M.empty
576599
, _gensym = initGensym
577600
, _randGen = mkStdGen seed
@@ -609,6 +632,12 @@ scenarioToGameState scenario userSeed toRun g = do
609632
False -> id
610633
True -> const (fromList devices)
611634

635+
initialCaps = mconcat $ map (^. robotCapabilities) robotList
636+
initialCommands =
637+
filter
638+
(maybe False (`S.member` initialCaps) . constCaps)
639+
allConst
640+
612641
theWorld = W.newWorld . (scenario ^. scenarioWorld)
613642
theWinCondition = maybe NoWinCondition WinCondition (scenario ^. scenarioWin)
614643
initGensym = length robotList - 1

src/Swarm/Game/Step.hs

+25-10
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,15 @@ traceLogShow = traceLog . from . show
276276
-- (either because it has a device which gives it that capability,
277277
-- or it is a system robot, or we are in creative mode).
278278
ensureCanExecute :: (Has (State Robot) sig m, Has (State GameState) sig m, Has (Throw Exn) sig m) => Const -> m ()
279-
ensureCanExecute c = do
280-
creative <- use creativeMode
281-
sys <- use systemRobot
282-
robotCaps <- use robotCapabilities
283-
let missingCaps = constCaps c `S.difference` robotCaps
284-
(sys || creative || S.null missingCaps)
285-
`holdsOr` Incapable FixByInstall missingCaps (TConst c)
279+
ensureCanExecute c = case constCaps c of
280+
Nothing -> pure ()
281+
Just cap -> do
282+
creative <- use creativeMode
283+
sys <- use systemRobot
284+
robotCaps <- use robotCapabilities
285+
let hasCaps = cap `S.member` robotCaps
286+
(sys || creative || hasCaps)
287+
`holdsOr` Incapable FixByInstall (S.singleton cap) (TConst c)
286288

287289
-- | Test whether the current robot has a given capability (either
288290
-- because it has a device which gives it that capability, or it is a
@@ -1660,6 +1662,7 @@ updateDiscoveredEntities e = do
16601662
else do
16611663
let newAllDiscovered = E.insertCount 1 e allDiscovered
16621664
updateAvailableRecipes (newAllDiscovered, newAllDiscovered) e
1665+
updateAvailableCommands e
16631666
allDiscoveredEntities .= newAllDiscovered
16641667

16651668
-- | Update the availableRecipes list.
@@ -1675,8 +1678,20 @@ updateAvailableRecipes invs e = do
16751678
allInRecipes <- use recipesIn
16761679
let entityRecipes = recipesFor allInRecipes e
16771680
usableRecipes = filter (knowsIngredientsFor invs) entityRecipes
1678-
knownRecipes <- use availableRecipes
1681+
knownRecipes <- use (availableRecipes . notificationsContent)
16791682
let newRecipes = filter (`notElem` knownRecipes) usableRecipes
16801683
newCount = length newRecipes
1681-
availableRecipes .= newRecipes <> knownRecipes
1682-
availableRecipesNewCount += newCount
1684+
availableRecipes %= mappend (Notifications newCount newRecipes)
1685+
updateAvailableCommands e
1686+
1687+
updateAvailableCommands :: Has (State GameState) sig m => Entity -> m ()
1688+
updateAvailableCommands e = do
1689+
let newCaps = S.fromList (e ^. entityCapabilities)
1690+
keepConsts = \case
1691+
Just cap -> cap `S.member` newCaps
1692+
Nothing -> False
1693+
entityConsts = filter (keepConsts . constCaps) allConst
1694+
knownCommands <- use (availableCommands . notificationsContent)
1695+
let newCommands = filter (`notElem` knownCommands) entityConsts
1696+
newCount = length newCommands
1697+
availableCommands %= mappend (Notifications newCount newCommands)

src/Swarm/Language/Capability.hs

+81-82
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ requiredCaps' = go
202202
TBool _ -> S.empty
203203
-- Look up the capabilities required by a function/command
204204
-- constants using 'constCaps'.
205-
TConst c -> constCaps c
205+
TConst c -> maybe S.empty S.singleton (constCaps c)
206206
-- Note that a variable might not show up in the context, and
207207
-- that's OK. In particular, only variables bound by 'TDef' go
208208
-- in the context; variables bound by a lambda or let will not
@@ -250,85 +250,84 @@ requiredCaps' = go
250250
TDef {} -> S.empty
251251

252252
-- | Capabilities needed to evaluate or execute a constant.
253-
constCaps :: Const -> Set Capability
254-
constCaps =
255-
S.fromList . \case
256-
-- Some built-in constants that don't require any special capability.
257-
Wait -> []
258-
Noop -> []
259-
AppF -> []
260-
Force -> []
261-
Return -> []
262-
Parent -> []
263-
Base -> []
264-
Setname -> []
265-
Undefined -> []
266-
Fail -> []
267-
-- Some straightforward ones.
268-
Log -> [CLog]
269-
Selfdestruct -> [CSelfdestruct]
270-
Move -> [CMove]
271-
Turn -> [CTurn]
272-
Grab -> [CGrab]
273-
Place -> [CPlace]
274-
Give -> [CGive]
275-
Install -> [CInstall]
276-
Make -> [CMake]
277-
Has -> []
278-
Count -> [CCount]
279-
If -> [CCond]
280-
Blocked -> [CSensefront]
281-
Scan -> [CScan]
282-
Ishere -> [CSensehere]
283-
Upload -> [CScan]
284-
Build -> [CBuild]
285-
Salvage -> [CSalvage]
286-
Reprogram -> [CReprogram]
287-
Drill -> [CDrill]
288-
Neg -> [CArith]
289-
Add -> [CArith]
290-
Sub -> [CArith]
291-
Mul -> [CArith]
292-
Div -> [CArith]
293-
Exp -> [CArith]
294-
Whoami -> [CWhoami]
295-
Self -> [CWhoami]
296-
-- Some God-like abilities.
297-
As -> [CGod]
298-
RobotNamed -> [CGod]
299-
RobotNumbered -> [CGod]
300-
Create -> [CGod]
301-
-- String operations, which for now are enabled by CLog
302-
Format -> [CLog]
303-
Concat -> [CLog]
304-
-- Some additional straightforward ones, which however currently
305-
-- cannot be used in classic mode since there is no craftable item
306-
-- which conveys their capability.
307-
Teleport -> [CTeleport] -- Some space-time machine like Tardis?
308-
Appear -> [CAppear] -- paint?
309-
Whereami -> [CSenseloc] -- GPS?
310-
Random -> [CRandom] -- randomness device (with bitcoins)?
253+
constCaps :: Const -> Maybe Capability
254+
constCaps = \case
255+
-- Some built-in constants that don't require any special capability.
256+
Wait -> Nothing
257+
Noop -> Nothing
258+
AppF -> Nothing
259+
Force -> Nothing
260+
Return -> Nothing
261+
Parent -> Nothing
262+
Base -> Nothing
263+
Setname -> Nothing
264+
Undefined -> Nothing
265+
Fail -> Nothing
266+
-- Some straightforward ones.
267+
Log -> Just CLog
268+
Selfdestruct -> Just CSelfdestruct
269+
Move -> Just CMove
270+
Turn -> Just CTurn
271+
Grab -> Just CGrab
272+
Place -> Just CPlace
273+
Give -> Just CGive
274+
Install -> Just CInstall
275+
Make -> Just CMake
276+
Has -> Nothing
277+
Count -> Just CCount
278+
If -> Just CCond
279+
Blocked -> Just CSensefront
280+
Scan -> Just CScan
281+
Ishere -> Just CSensehere
282+
Upload -> Just CScan
283+
Build -> Just CBuild
284+
Salvage -> Just CSalvage
285+
Reprogram -> Just CReprogram
286+
Drill -> Just CDrill
287+
Neg -> Just CArith
288+
Add -> Just CArith
289+
Sub -> Just CArith
290+
Mul -> Just CArith
291+
Div -> Just CArith
292+
Exp -> Just CArith
293+
Whoami -> Just CWhoami
294+
Self -> Just CWhoami
295+
-- Some God-like abilities.
296+
As -> Just CGod
297+
RobotNamed -> Just CGod
298+
RobotNumbered -> Just CGod
299+
Create -> Just CGod
300+
-- String operations, which for now are enabled by CLog
301+
Format -> Just CLog
302+
Concat -> Just CLog
303+
-- Some additional straightforward ones, which however currently
304+
-- cannot be used in classic mode since there is no craftable item
305+
-- which conveys their capability.
306+
Teleport -> Just CTeleport -- Some space-time machine like Tardis?
307+
Appear -> Just CAppear -- paint?
308+
Whereami -> Just CSenseloc -- GPS?
309+
Random -> Just CRandom -- randomness device (with bitcoins)?
311310

312-
-- comparator?
313-
Eq -> [CCompare]
314-
Neq -> [CCompare]
315-
Lt -> [CCompare]
316-
Gt -> [CCompare]
317-
Leq -> [CCompare]
318-
Geq -> [CCompare]
319-
And -> []
320-
Or -> []
321-
-- Some more constants which *ought* to have their own capability but
322-
-- currently don't.
323-
Say -> []
324-
View -> [] -- XXX this should also require something.
325-
Run -> [] -- XXX this should also require a capability
326-
-- which the base starts out with.
327-
Not -> [] -- XXX some kind of boolean logic cap?
328-
Inl -> [] -- XXX should require cap for sums
329-
Inr -> []
330-
Case -> []
331-
Fst -> [] -- XXX should require cap for pairs
332-
Snd -> []
333-
Try -> [] -- XXX these definitely need to require something.
334-
Knows -> []
311+
-- comparator?
312+
Eq -> Just CCompare
313+
Neq -> Just CCompare
314+
Lt -> Just CCompare
315+
Gt -> Just CCompare
316+
Leq -> Just CCompare
317+
Geq -> Just CCompare
318+
And -> Nothing
319+
Or -> Nothing
320+
-- Some more constants which *ought* to have their own capability but
321+
-- currently don't.
322+
Say -> Nothing
323+
View -> Nothing -- XXX this should also require something.
324+
Run -> Nothing -- XXX this should also require a capability
325+
-- which the base starts out with.
326+
Not -> Nothing -- XXX some kind of boolean logic cap?
327+
Inl -> Nothing -- XXX should require cap for sums
328+
Inr -> Nothing
329+
Case -> Nothing
330+
Fst -> Nothing -- XXX should require cap for pairs
331+
Snd -> Nothing
332+
Try -> Nothing -- XXX these definitely need to require something.
333+
Knows -> Nothing

src/Swarm/TUI/Controller.hs

+6-2
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,12 @@ handleMainEvent s = \case
193193
| isJust (s ^. uiState . uiError) -> continue $ s & uiState . uiError .~ Nothing
194194
| isJust (s ^. uiState . uiModal) -> maybeUnpause s >>= (continue . (uiState . uiModal .~ Nothing))
195195
FKey 1 -> toggleModal s HelpModal >>= continue
196-
FKey 2 | not (null (s ^. gameState . availableRecipes)) -> do
196+
FKey 2 | not (null (s ^. gameState . availableRecipes . notificationsContent)) -> do
197197
s' <- toggleModal s RecipesModal
198-
continue (s' & gameState . availableRecipesNewCount .~ 0)
198+
continue (s' & gameState . availableRecipes . notificationsCount .~ 0)
199+
FKey 3 | not (null (s ^. gameState . availableCommands . notificationsContent)) -> do
200+
s' <- toggleModal s CommandsModal
201+
continue (s' & gameState . availableCommands . notificationsCount .~ 0)
199202
ControlKey 'g' -> case s ^. uiState . uiGoal of
200203
NoGoal -> continueWithoutRedraw s
201204
UnreadGoal g -> toggleModal s (GoalModal g) >>= continue
@@ -295,6 +298,7 @@ handleModalEvent s = \case
295298
s' <- s & uiState . uiModal . _Just . modalDialog %%~ handleDialogEvent ev
296299
case s ^? uiState . uiModal . _Just . modalType of
297300
Just RecipesModal -> handleInfoPanelEvent s' recipesScroll (VtyEvent ev)
301+
Just CommandsModal -> handleInfoPanelEvent s' commandsScroll (VtyEvent ev)
298302
_ -> continue s'
299303

300304
-- | Quit a game. Currently all it does is write out the updated REPL

0 commit comments

Comments
 (0)