Skip to content

Commit 92c1d86

Browse files
Add available recipes help (#441)
This change adds a new help panel with the list of recently acquired recipes. Fixes #436
1 parent 7b9d842 commit 92c1d86

File tree

7 files changed

+139
-26
lines changed

7 files changed

+139
-26
lines changed

src/Swarm/Game/Recipe.hs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module Swarm.Game.Recipe (
3333
inRecipeMap,
3434

3535
-- * Looking up recipes
36+
knowsIngredientsFor,
3637
recipesFor,
3738
make,
3839
make',
@@ -186,6 +187,13 @@ missingIngredientsFor (inv, ins) (Recipe inps _ reqs _ _) =
186187
findLacking inven = filter ((> 0) . fst) . map (countNeeded inven)
187188
countNeeded inven (need, entity) = (need - E.lookup entity inven, entity)
188189

190+
-- | Figure out if a recipe is available, but it can be lacking items.
191+
knowsIngredientsFor :: (Inventory, Inventory) -> Recipe Entity -> Bool
192+
knowsIngredientsFor (inv, ins) recipe =
193+
knowsAll inv (recipe ^. recipeInputs) && knowsAll ins (recipe ^. recipeRequirements)
194+
where
195+
knowsAll xs = all (E.contains xs . snd)
196+
189197
-- | Try to make a recipe, deleting the recipe's inputs from the
190198
-- inventory. Return either a description of which items are
191199
-- lacking, if the inventory does not contain sufficient inputs,

src/Swarm/Game/State.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ module Swarm.Game.State (
3636
robotMap,
3737
robotsByLocation,
3838
activeRobots,
39+
availableRecipes,
40+
availableRecipesNewCount,
41+
allDiscoveredEntities,
3942
gensym,
4043
randGen,
4144
adjList,
@@ -197,6 +200,9 @@ data GameState = GameState
197200
-- append to a list than to a Set.
198201
_waitingRobots :: Map Integer [RID]
199202
, _robotsByLocation :: Map (V2 Int64) IntSet
203+
, _allDiscoveredEntities :: Inventory
204+
, _availableRecipes :: [Recipe Entity]
205+
, _availableRecipesNewCount :: Int
200206
, _gensym :: Int
201207
, _randGen :: StdGen
202208
, _adjList :: Array Int Text
@@ -263,6 +269,15 @@ robotMap :: Lens' GameState (IntMap Robot)
263269
-- happen.
264270
robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)
265271

272+
-- | The list of entities that have been discovered.
273+
allDiscoveredEntities :: Lens' GameState Inventory
274+
275+
-- | The list of available recipes.
276+
availableRecipes :: Lens' GameState [Recipe Entity]
277+
278+
-- | The number of new recipes (reset to 0 when the player open the recipes view).
279+
availableRecipesNewCount :: Lens' GameState Int
280+
266281
-- | The names of the robots that are currently not sleeping.
267282
activeRobots :: Getter GameState IntSet
268283
activeRobots = internalActiveRobots
@@ -512,6 +527,9 @@ initGameState = do
512527
, _runStatus = Running
513528
, _robotMap = IM.empty
514529
, _robotsByLocation = M.empty
530+
, _availableRecipes = mempty
531+
, _availableRecipesNewCount = 0
532+
, _allDiscoveredEntities = empty
515533
, _activeRobots = IS.empty
516534
, _waitingRobots = M.empty
517535
, _gensym = 0

src/Swarm/Game/Step.hs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Control.Monad (forM_, guard, msum, unless, when)
2525
import Data.Array (bounds, (!))
2626
import Data.Bool (bool)
2727
import Data.Either (rights)
28+
import Data.Foldable (traverse_)
2829
import qualified Data.Functor.Const as F
2930
import Data.Int (Int64)
3031
import qualified Data.IntMap as IM
@@ -752,6 +753,7 @@ execConst c vs s k = do
752753
Just n -> fromMaybe e <$> uses entityMap (lookupEntityName n)
753754

754755
robotInventory %= insert e'
756+
updateDiscoveredEntities e'
755757

756758
-- Return the name of the item obtained.
757759
return $ Out (VString (e' ^. entityName)) s k
@@ -891,6 +893,7 @@ execConst c vs s k = do
891893

892894
-- take recipe inputs from inventory and add outputs after recipeTime
893895
robotInventory .= invTaken
896+
traverse_ (updateDiscoveredEntities . snd) (recipe ^. recipeOutputs)
894897
finishCookingRecipe recipe (WorldUpdate Right) (RobotUpdate changeInv)
895898
_ -> badConst
896899
Has -> case vs of
@@ -964,6 +967,7 @@ execConst c vs s k = do
964967
Nothing -> return $ VInj False VUnit
965968
Just e -> do
966969
robotInventory %= insertCount 0 e
970+
updateDiscoveredEntities e
967971
return $ VInj True (VString (e ^. entityName))
968972

969973
return $ Out res s k
@@ -1085,6 +1089,8 @@ execConst c vs s k = do
10851089
`isJustOrFail` ["I've never heard of", indefiniteQ name <> "."]
10861090

10871091
robotInventory %= insert e
1092+
updateDiscoveredEntities e
1093+
10881094
return $ Out VUnit s k
10891095
_ -> badConst
10901096
Ishere -> case vs of
@@ -1627,3 +1633,33 @@ safeExp :: Has (Throw Exn) sig m => Integer -> Integer -> m Integer
16271633
safeExp a b
16281634
| b < 0 = throwError $ CmdFailed Exp "Negative exponent"
16291635
| otherwise = return $ a ^ b
1636+
1637+
-- | Update the global list of discovered entities, and check for new recipes.
1638+
updateDiscoveredEntities :: (Has (State GameState) sig m, Has (State Robot) sig m) => Entity -> m ()
1639+
updateDiscoveredEntities e = do
1640+
allDiscovered <- use allDiscoveredEntities
1641+
if E.contains0plus e allDiscovered
1642+
then pure ()
1643+
else do
1644+
let newAllDiscovered = E.insertCount 1 e allDiscovered
1645+
updateAvailableRecipes (newAllDiscovered, newAllDiscovered) e
1646+
allDiscoveredEntities .= newAllDiscovered
1647+
1648+
-- | Update the availableRecipes list.
1649+
-- This implementation is not efficient:
1650+
-- * Every time we discover a new entity, we iterate through the entire list of recipes to see which ones we can make.
1651+
-- Trying to do something more clever seems like it would definitely be a case of premature optimization.
1652+
-- One doesn't discover new entities all that often.
1653+
-- * For each usable recipe, we do a linear search through the list of known recipes to see if we already know it.
1654+
-- This is a little more troubling, since it's quadratic in the number of recipes.
1655+
-- But it probably doesn't really make that much difference until we get up to thousands of recipes.
1656+
updateAvailableRecipes :: Has (State GameState) sig m => (Inventory, Inventory) -> Entity -> m ()
1657+
updateAvailableRecipes invs e = do
1658+
allInRecipes <- use recipesIn
1659+
let entityRecipes = recipesFor allInRecipes e
1660+
usableRecipes = filter (knowsIngredientsFor invs) entityRecipes
1661+
knownRecipes <- use availableRecipes
1662+
let newRecipes = filter (`notElem` knownRecipes) usableRecipes
1663+
newCount = length newRecipes
1664+
availableRecipes .= newRecipes <> knownRecipes
1665+
availableRecipesNewCount += newCount

src/Swarm/TUI/Attr.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ swarmAttrMap =
4747
, (sandAttr, fg (V.rgbColor @Int 194 178 128))
4848
, (fireAttr, fg V.red `V.withStyle` V.bold)
4949
, (redAttr, fg V.red)
50+
, (notifAttr, fg V.yellow `V.withStyle` V.bold)
5051
, (greenAttr, fg V.green)
5152
, (blueAttr, fg V.blue)
5253
, (deviceAttr, fg V.yellow `V.withStyle` V.bold)
@@ -85,6 +86,7 @@ robotAttr
8586
, baseAttr
8687
, fireAttr
8788
, redAttr
89+
, notifAttr
8890
, greenAttr
8991
, blueAttr
9092
, woodAttr
@@ -119,13 +121,14 @@ snowAttr = "snow"
119121
sandAttr = "sand"
120122
fireAttr = "fire"
121123
redAttr = "red"
124+
highlightAttr = "highlight"
122125
greenAttr = "green"
123126
blueAttr = "blue"
124127
rockAttr = "rock"
125128
woodAttr = "wood"
126129
baseAttr = "base"
127130
deviceAttr = "device"
128-
highlightAttr = "highlight"
131+
notifAttr = "notif"
129132
sepAttr = "sep"
130133
infoAttr = "info"
131134
defAttr = "def"

src/Swarm/TUI/Controller.hs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ handleMainEvent s = \case
191191
| isJust (s ^. uiState . uiError) -> continue $ s & uiState . uiError .~ Nothing
192192
| isJust (s ^. uiState . uiModal) -> maybeUnpause s >>= (continue . (uiState . uiModal .~ Nothing))
193193
FKey 1 -> toggleModal s HelpModal >>= continue
194+
FKey 2 | not (null (s ^. gameState . availableRecipes)) -> do
195+
s' <- toggleModal s RecipesModal
196+
continue (s' & gameState . availableRecipesNewCount .~ 0)
194197
ControlKey 'g' -> case s ^. uiState . uiGoal of
195198
NoGoal -> continueWithoutRedraw s
196199
UnreadGoal g -> toggleModal s (GoalModal g) >>= continue
@@ -237,7 +240,7 @@ handleMainEvent s = \case
237240
Just REPLPanel -> handleREPLEvent s ev
238241
Just WorldPanel -> handleWorldEvent s ev
239242
Just RobotPanel -> handleRobotPanelEvent s ev
240-
Just InfoPanel -> handleInfoPanelEvent s ev
243+
Just InfoPanel -> handleInfoPanelEvent s infoScroll ev
241244
_ -> continueWithoutRedraw s
242245

243246
mouseLocToWorldCoords :: GameState -> Brick.Location -> EventM Name (Maybe W.Coords)
@@ -289,7 +292,9 @@ handleModalEvent s = \case
289292
_ -> continue s'
290293
ev -> do
291294
s' <- s & uiState . uiModal . _Just . modalDialog %%~ handleDialogEvent ev
292-
continue s'
295+
case s ^? uiState . uiModal . _Just . modalType of
296+
Just RecipesModal -> handleInfoPanelEvent s' recipesScroll (VtyEvent ev)
297+
_ -> continue s'
293298

294299
-- | Quit a game. Currently all it does is write out the updated REPL
295300
-- history to a @.swarm_history@ file, and return to the previous menu.
@@ -792,14 +797,14 @@ descriptionModal s e =
792797
------------------------------------------------------------
793798

794799
-- | Handle user events in the info panel (just scrolling).
795-
handleInfoPanelEvent :: AppState -> BrickEvent Name AppEvent -> EventM Name (Next AppState)
796-
handleInfoPanelEvent s = \case
797-
Key V.KDown -> vScrollBy infoScroll 1 >> continue s
798-
Key V.KUp -> vScrollBy infoScroll (-1) >> continue s
799-
CharKey 'k' -> vScrollBy infoScroll 1 >> continue s
800-
CharKey 'j' -> vScrollBy infoScroll (-1) >> continue s
801-
Key V.KPageDown -> vScrollPage infoScroll Brick.Down >> continue s
802-
Key V.KPageUp -> vScrollPage infoScroll Brick.Up >> continue s
803-
Key V.KHome -> vScrollToBeginning infoScroll >> continue s
804-
Key V.KEnd -> vScrollToEnd infoScroll >> continue s
800+
handleInfoPanelEvent :: AppState -> ViewportScroll Name -> BrickEvent Name AppEvent -> EventM Name (Next AppState)
801+
handleInfoPanelEvent s vs = \case
802+
Key V.KDown -> vScrollBy vs 1 >> continue s
803+
Key V.KUp -> vScrollBy vs (-1) >> continue s
804+
CharKey 'k' -> vScrollBy vs 1 >> continue s
805+
CharKey 'j' -> vScrollBy vs (-1) >> continue s
806+
Key V.KPageDown -> vScrollPage vs Brick.Down >> continue s
807+
Key V.KPageUp -> vScrollPage vs Brick.Up >> continue s
808+
Key V.KHome -> vScrollToBeginning vs >> continue s
809+
Key V.KEnd -> vScrollToEnd vs >> continue s
805810
_ -> continueWithoutRedraw s

src/Swarm/TUI/Model.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ module Swarm.TUI.Model (
114114
-- ** Updating
115115
populateInventoryList,
116116
infoScroll,
117+
recipesScroll,
117118

118119
-- * App state
119120
AppState,
@@ -205,11 +206,16 @@ data Name
205206
ScenarioList
206207
| -- | The scrollable viewport for the info panel.
207208
InfoViewport
209+
| -- | The scrollable viewport for the recipe list.
210+
RecipesViewport
208211
deriving (Eq, Ord, Show, Read)
209212

210213
infoScroll :: ViewportScroll Name
211214
infoScroll = viewportScroll InfoViewport
212215

216+
recipesScroll :: ViewportScroll Name
217+
recipesScroll = viewportScroll RecipesViewport
218+
213219
------------------------------------------------------------
214220
-- REPL History
215221
------------------------------------------------------------
@@ -390,6 +396,7 @@ mkReplForm r = newForm [(replPromptAsWidget r <+>) @@= editTextField promptTextL
390396

391397
data ModalType
392398
= HelpModal
399+
| RecipesModal
393400
| WinModal
394401
| QuitModal
395402
| DescriptionModal Entity

0 commit comments

Comments
 (0)