Skip to content

Commit f50439b

Browse files
committed
add goal field to scenarios
If there is a goal, immediately pop up a dialog with the goal, then allow showing it any time afterwards with ^G. See #356.
1 parent 389e3b7 commit f50439b

File tree

7 files changed

+70
-6
lines changed

7 files changed

+70
-6
lines changed

src/Swarm/Game/Entity.hs

+1-3
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ import Swarm.Util.Yaml
115115
import Swarm.Game.Display
116116
import Swarm.Language.Capability
117117
import Swarm.Language.Syntax (toDirection)
118-
import Swarm.Util (plural, (?))
118+
import Swarm.Util (plural, reflow, (?))
119119

120120
import Paths_swarm
121121

@@ -339,8 +339,6 @@ instance FromJSON Entity where
339339
<*> v .:? "capabilities" .!= []
340340
<*> pure empty
341341
)
342-
where
343-
reflow = T.unwords . T.words
344342

345343
-- | If we have access to an 'EntityMap', we can parse the name of an
346344
-- 'Entity' as a string and look it up in the map.

src/Swarm/Game/Scenario.hs

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module Swarm.Game.Scenario (
2727
-- ** Fields
2828
scenarioName,
2929
scenarioDescription,
30+
scenarioGoal,
3031
scenarioCreative,
3132
scenarioSeed,
3233
scenarioEntities,
@@ -78,13 +79,15 @@ import Swarm.Game.Terrain
7879
import Swarm.Game.World
7980
import Swarm.Game.WorldGen (Seed, findGoodOrigin, testWorld2FromArray)
8081
import Swarm.Language.Pipeline (ProcessedTerm)
82+
import Swarm.Util (reflow)
8183
import Swarm.Util.Yaml
8284

8385
-- | A 'Scenario' contains all the information to describe a
8486
-- scenario.
8587
data Scenario = Scenario
8688
{ _scenarioName :: Text
8789
, _scenarioDescription :: Text
90+
, _scenarioGoal :: Maybe [Text]
8891
, _scenarioCreative :: Bool -- Maybe generalize this to a mode enumeration
8992
, _scenarioSeed :: Maybe Int
9093
, _scenarioEntities :: EntityMap
@@ -103,6 +106,7 @@ instance FromJSONE EntityMap Scenario where
103106
Scenario
104107
<$> liftE (v .: "name")
105108
<*> liftE (v .:? "description" .!= "")
109+
<*> liftE ((fmap . fmap . map) reflow (v .:? "goal"))
106110
<*> liftE (v .:? "creative" .!= False)
107111
<*> liftE (v .:? "seed")
108112
<*> pure em
@@ -115,9 +119,14 @@ instance FromJSONE EntityMap Scenario where
115119
-- | The name of the scenario.
116120
scenarioName :: Lens' Scenario Text
117121

118-
-- | A description of the scenario.
122+
-- | A high-level description of the scenario, shown /e.g./ in the
123+
-- menu.
119124
scenarioDescription :: Lens' Scenario Text
120125

126+
-- | An explanation of the goal of the scenario (if any), shown to the
127+
-- player during play.
128+
scenarioGoal :: Lens' Scenario (Maybe [Text])
129+
121130
-- | Whether the scenario should start in creative mode.
122131
scenarioCreative :: Lens' Scenario Bool
123132

src/Swarm/Game/State.hs

+34-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ module Swarm.Game.State (
2424
_WinCondition,
2525
_Won,
2626
RunStatus (..),
27+
GoalStatus (..),
28+
goalNeedsDisplay,
29+
markGoalRead,
2730
GameState,
2831
Seed,
2932
initGameState,
@@ -32,6 +35,7 @@ module Swarm.Game.State (
3235

3336
-- ** GameState fields
3437
creativeMode,
38+
gameGoal,
3539
winCondition,
3640
winSolution,
3741
runStatus,
@@ -162,11 +166,34 @@ data RunStatus
162166
AutoPause
163167
deriving (Eq, Show)
164168

169+
-- | Status of the scenario goal: whether there is one, and whether it has been
170+
-- displayed to the user initially.
171+
data GoalStatus
172+
= -- | There is no goal.
173+
NoGoal
174+
| -- | There is a goal, and we should display it to the user initially.
175+
UnreadGoal [Text]
176+
| -- | There is a goal, and we have already displayed it to the user.
177+
-- It can be displayed again if the user chooses.
178+
ReadGoal [Text]
179+
deriving (Eq, Show)
180+
181+
-- | Do we need to display the goal initially to the user?
182+
goalNeedsDisplay :: GoalStatus -> Maybe [Text]
183+
goalNeedsDisplay (UnreadGoal g) = Just g
184+
goalNeedsDisplay _ = Nothing
185+
186+
-- | Mark the goal as having been read.
187+
markGoalRead :: GoalStatus -> GoalStatus
188+
markGoalRead (UnreadGoal g) = ReadGoal g
189+
markGoalRead gs = gs
190+
165191
-- | The main record holding the state for the game itself (as
166192
-- distinct from the UI). See the lenses below for access to its
167193
-- fields.
168194
data GameState = GameState
169195
{ _creativeMode :: Bool
196+
, _gameGoal :: GoalStatus
170197
, _winCondition :: WinCondition
171198
, _winSolution :: Maybe ProcessedTerm
172199
, _runStatus :: RunStatus
@@ -220,6 +247,10 @@ let exclude = ['_viewCenter, '_focusedRobotID, '_viewCenterRule, '_activeRobots,
220247
-- | Is the user in creative mode (i.e. able to do anything without restriction)?
221248
creativeMode :: Lens' GameState Bool
222249

250+
-- | Status of the scenario goal: whether there is one, and whether it
251+
-- has been displayed to the user initially.
252+
gameGoal :: Lens' GameState GoalStatus
253+
223254
-- | How to determine whether the player has won.
224255
winCondition :: Lens' GameState WinCondition
225256

@@ -377,7 +408,7 @@ viewingRegion :: GameState -> (Int64, Int64) -> (W.Coords, W.Coords)
377408
viewingRegion g (w, h) = (W.Coords (rmin, cmin), W.Coords (rmax, cmax))
378409
where
379410
V2 cx cy = g ^. viewCenter
380-
(rmin, rmax) = over both (+ (- cy - h `div` 2)) (0, h - 1)
411+
(rmin, rmax) = over both (+ (-cy - h `div` 2)) (0, h - 1)
381412
(cmin, cmax) = over both (+ (cx - w `div` 2)) (0, w - 1)
382413

383414
-- | Find out which robot is currently specified by the
@@ -435,6 +466,7 @@ initGameState cmdlineSeed scenarioToLoad toRun = do
435466
let initState =
436467
GameState
437468
{ _creativeMode = False
469+
, _gameGoal = NoGoal
438470
, _winCondition = NoWinCondition
439471
, _winSolution = Nothing
440472
, _runStatus = Running
@@ -485,6 +517,7 @@ playScenario em scenario userSeed toRun g = do
485517
return $
486518
g
487519
{ _creativeMode = scenario ^. scenarioCreative
520+
, _gameGoal = maybe NoGoal UnreadGoal (scenario ^. scenarioGoal)
488521
, _winCondition = theWinCondition
489522
, _winSolution = scenario ^. scenarioSolution
490523
, _runStatus = Running

src/Swarm/TUI/Controller.hs

+6
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ handleMainEvent :: AppState -> BrickEvent Name AppEvent -> EventM Name (Next App
193193
handleMainEvent s = \case
194194
AppEvent Frame
195195
| s ^. gameState . paused -> continueWithoutRedraw s
196+
| Just g <- s ^. gameState . gameGoal . to goalNeedsDisplay ->
197+
toggleModal s (GoalModal g) <&> (gameState . gameGoal %~ markGoalRead) >>= runFrameUI
196198
| otherwise -> runFrameUI s
197199
VtyEvent (V.EvResize _ _) -> do
198200
invalidateCacheEntry WorldCache
@@ -213,6 +215,10 @@ handleMainEvent s = \case
213215
-- toggle creative mode if in "cheat mode"
214216
ControlKey 'k'
215217
| s ^. uiState . uiCheatMode -> continue (s & gameState . creativeMode %~ not)
218+
ControlKey 'g' -> case s ^. gameState . gameGoal of
219+
NoGoal -> continueWithoutRedraw s
220+
UnreadGoal g -> toggleModal s (GoalModal g) >>= continue
221+
ReadGoal g -> toggleModal s (GoalModal g) >>= continue
216222
FKey 1 -> toggleModal s HelpModal >>= continue
217223
MouseDown n _ _ mouseLoc ->
218224
case n of

src/Swarm/TUI/Model.hs

+1
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ data ModalType
309309
| WinModal
310310
| QuitModal
311311
| DescriptionModal Entity
312+
| GoalModal [Text]
312313
deriving (Eq, Show)
313314

314315
data ButtonSelection = Cancel | Confirm

src/Swarm/TUI/View.hs

+13-1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ generateModal s mt = Modal mt (dialog (Just title) buttons (maxModalWindowWidth
319319
, Just (0, [("Keep playing", Cancel), (stopMsg, Confirm)])
320320
, T.length quitMsg + 4
321321
)
322+
GoalModal g -> (" Goal ", padLeftRight 1 (displayParagraphs g), Nothing, 80)
322323

323324
helpWidget :: Widget Name
324325
helpWidget = (helpKeys <=> fill ' ') <+> (helpCommands <=> fill ' ')
@@ -381,6 +382,7 @@ drawKeyMenu s =
381382
viewingBase = (s ^. gameState . viewCenterRule) == VCRobot 0
382383
creative = s ^. gameState . creativeMode
383384
cheat = s ^. uiState . uiCheatMode
385+
goal = (s ^. gameState . gameGoal) /= NoGoal
384386

385387
gameModeWidget =
386388
padLeft Max . padLeftRight 1
@@ -394,6 +396,7 @@ drawKeyMenu s =
394396
, ("Tab", "cycle")
395397
]
396398
++ [("^k", "creative") | cheat]
399+
++ [("^g", "goal") | goal]
397400
keyCmdsFor (Just REPLPanel) =
398401
[ ("↓↑", "history")
399402
]
@@ -571,7 +574,7 @@ explainFocusedItem s = case focusedItem s of
571574

572575
explainEntry :: AppState -> Entity -> Widget Name
573576
explainEntry s e =
574-
vBox (map (padBottom (Pad 1) . txtWrap) (e ^. entityDescription))
577+
displayParagraphs (e ^. entityDescription)
575578
<=> explainRecipes s e
576579

577580
explainRecipes :: AppState -> Entity -> Widget Name
@@ -730,3 +733,12 @@ drawREPL s =
730733
histIdx = fromString $ show (history ^. replIndex)
731734
fmt (REPLEntry e) = txt replPrompt <+> txt e
732735
fmt (REPLOutput t) = txt t
736+
737+
------------------------------------------------------------
738+
-- Utility
739+
------------------------------------------------------------
740+
741+
-- | Display a list of text-wrapped paragraphs with one blank line after
742+
-- each.
743+
displayParagraphs :: [Text] -> Widget Name
744+
displayParagraphs = vBox . map (padBottom (Pad 1) . txtWrap)

src/Swarm/Util.hs

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module Swarm.Util (
3232
readAppData,
3333

3434
-- * English language utilities
35+
reflow,
3536
quote,
3637
squote,
3738
commaList,
@@ -167,6 +168,10 @@ readAppData = do
167168
------------------------------------------------------------
168169
-- Some language-y stuff
169170

171+
-- | Reflow text by removing newlines and condensing whitespace.
172+
reflow :: Text -> Text
173+
reflow = T.unwords . T.words
174+
170175
-- | Prepend a noun with the proper indefinite article (\"a\" or \"an\").
171176
indefinite :: Text -> Text
172177
indefinite w = MM.indefiniteDet w <+> w

0 commit comments

Comments
 (0)