Skip to content

Commit 1568e23

Browse files
Add available recipes help
1 parent 7a35efa commit 1568e23

File tree

6 files changed

+84
-10
lines changed

6 files changed

+84
-10
lines changed

src/Swarm/Game/Recipe.hs

Lines changed: 1 addition & 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+
missingIngredientsFor,
3637
recipesFor,
3738
make,
3839
make',

src/Swarm/Game/State.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module Swarm.Game.State (
3939
robotMap,
4040
robotsByLocation,
4141
activeRobots,
42+
availableRecipes,
4243
gensym,
4344
randGen,
4445
adjList,
@@ -186,6 +187,7 @@ data GameState = GameState
186187
-- append to a list than to a Set.
187188
_waitingRobots :: Map Integer [RID]
188189
, _robotsByLocation :: Map (V2 Int64) IntSet
190+
, _availableRecipes :: (Int, [Recipe Entity])
189191
, _gensym :: Int
190192
, _randGen :: StdGen
191193
, _adjList :: Array Int Text
@@ -248,6 +250,9 @@ robotMap :: Lens' GameState (IntMap Robot)
248250
-- happen.
249251
robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)
250252

253+
-- | The list of available recipes, with the position of the last known item.
254+
availableRecipes :: Lens' GameState (Int, [Recipe Entity])
255+
251256
-- | The names of the robots that are currently not sleeping.
252257
activeRobots :: Getter GameState IntSet
253258
activeRobots = internalActiveRobots
@@ -440,6 +445,7 @@ initGameState cmdlineSeed scenarioToLoad toRun = do
440445
, _runStatus = Running
441446
, _robotMap = IM.empty
442447
, _robotsByLocation = M.empty
448+
, _availableRecipes = (0, mempty)
443449
, _activeRobots = IS.empty
444450
, _waitingRobots = M.empty
445451
, _gensym = 0

src/Swarm/Game/Step.hs

Lines changed: 43 additions & 1 deletion
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
@@ -367,6 +368,7 @@ stepCESK cesk = case cesk of
367368
Left exn -> return $ Up exn s k
368369
Right wo -> do
369370
robotInventory %= robotUpdateInventory rf
371+
updateSelfAllAvailableRecipes
370372
world .= wo
371373
needsRedraw .= True
372374
stepCESK (Out v s k)
@@ -752,6 +754,7 @@ execConst c vs s k = do
752754
Just n -> fromMaybe e <$> uses entityMap (lookupEntityName n)
753755

754756
robotInventory %= insert e'
757+
updateSelfAvailableRecipes e'
755758

756759
-- Return the name of the item grabbed.
757760
return $ Out (VString (e ^. entityName)) s k
@@ -813,6 +816,12 @@ execConst c vs s k = do
813816
robotMap . at otherID . _Just . robotInventory %= insert item
814817
robotInventory %= delete item
815818

819+
otherRobotm <- use $ robotMap . at otherID
820+
case otherRobotm of
821+
Just otherRobot ->
822+
updateAvailableRecipes (otherRobot ^. robotInventory, otherRobot ^. installedDevices) item
823+
Nothing -> pure ()
824+
816825
-- Flag the UI for a redraw if we are currently showing either robot's inventory
817826
when (focusedID == myID || focusedID == otherID) flagRedraw
818827

@@ -844,6 +853,7 @@ execConst c vs s k = do
844853
installedDevices %= insert item
845854
robotInventory %= delete item
846855

856+
updateSelfAllAvailableRecipes
847857
-- Flag the UI for a redraw if we are currently showing our inventory
848858
when (focusedID == myID) flagRedraw
849859
False -> do
@@ -891,6 +901,7 @@ execConst c vs s k = do
891901

892902
-- take recipe inputs from inventory and add outputs after recipeTime
893903
robotInventory .= invTaken
904+
894905
finishCookingRecipe recipe (WorldUpdate Right) (RobotUpdate changeInv)
895906
_ -> badConst
896907
Has -> case vs of
@@ -1075,6 +1086,8 @@ execConst c vs s k = do
10751086
`isJustOrFail` ["I've never heard of", indefiniteQ name <> "."]
10761087

10771088
robotInventory %= insert e
1089+
updateSelfAvailableRecipes e
1090+
10781091
return $ Out VUnit s k
10791092
_ -> badConst
10801093
Ishere -> case vs of
@@ -1354,7 +1367,7 @@ execConst c vs s k = do
13541367
[VBool b] -> return $ Out (VBool (not b)) s k
13551368
_ -> badConst
13561369
Neg -> case vs of
1357-
[VInt n] -> return $ Out (VInt (- n)) s k
1370+
[VInt n] -> return $ Out (VInt (-n)) s k
13581371
_ -> badConst
13591372
Eq -> returnEvalCmp
13601373
Neq -> returnEvalCmp
@@ -1617,3 +1630,32 @@ safeExp :: Has (Throw Exn) sig m => Integer -> Integer -> m Integer
16171630
safeExp a b
16181631
| b < 0 = throwError $ CmdFailed Exp "Negative exponent"
16191632
| otherwise = return $ a ^ b
1633+
1634+
-- | Check if any new recipes is available from the given entity
1635+
updateSelfAvailableRecipes :: (Has (State GameState) sig m, Has (State Robot) sig m) => Entity -> m ()
1636+
updateSelfAvailableRecipes e = do
1637+
inventory <- use robotInventory
1638+
devices <- use installedDevices
1639+
updateAvailableRecipes (inventory, devices) e
1640+
1641+
-- | Check if any new recipes is available from all the inventory
1642+
updateSelfAllAvailableRecipes :: (Has (State GameState) sig m, Has (State Robot) sig m) => m ()
1643+
updateSelfAllAvailableRecipes = do
1644+
inventory <- use robotInventory
1645+
devices <- use installedDevices
1646+
traverse_ (updateAvailableRecipes (inventory, devices) . snd) (filter (\(c, _) -> c > 0) $ E.elems inventory)
1647+
1648+
updateAvailableRecipes :: Has (State GameState) sig m => (Inventory, Inventory) -> Entity -> m ()
1649+
updateAvailableRecipes invs e = do
1650+
allInRecipes <- use recipesIn
1651+
let entityRecipes = recipesFor allInRecipes e
1652+
let canMake recipe = case missingIngredientsFor invs recipe of
1653+
[] -> True
1654+
_ -> False
1655+
let usableRecipes = filter canMake entityRecipes
1656+
(knownLength, knownRecipes) <- use availableRecipes
1657+
let newRecipes = filter (`notElem` knownRecipes) usableRecipes
1658+
newLength = case newRecipes of
1659+
[] -> knownLength
1660+
_ -> length newRecipes
1661+
availableRecipes .= (newLength, newRecipes <> knownRecipes)

src/Swarm/TUI/Controller.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ handleMainEvent s = \case
204204
ControlKey 'k'
205205
| s ^. uiState . uiCheatMode -> continue (s & gameState . creativeMode %~ not)
206206
FKey 1 -> toggleModal s HelpModal >>= continue
207+
FKey 2 -> do
208+
s' <- toggleModal s RecipesModal
209+
continue (s' & gameState . availableRecipes %~ markAsKnown)
207210
-- dispatch any other events to the focused panel handler
208211
ev ->
209212
case focusGetCurrent (s ^. uiState . uiFocusRing) of
@@ -213,6 +216,9 @@ handleMainEvent s = \case
213216
Just InfoPanel -> handleInfoPanelEvent s ev
214217
_ -> continueWithoutRedraw s
215218

219+
markAsKnown :: (Int, a) -> (Int, a)
220+
markAsKnown (_, xs) = (0, xs)
221+
216222
setFocus :: AppState -> Name -> EventM Name (Next AppState)
217223
setFocus s name = continue $ s & uiState . uiFocusRing %~ focusSetCurrent name
218224

src/Swarm/TUI/Model.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ replIndexIsAtInput repl = repl ^. replIndex == replLength repl
304304

305305
data ModalType
306306
= HelpModal
307+
| RecipesModal
307308
| WinModal
308309
| QuitModal
309310
| DescriptionModal Entity

src/Swarm/TUI/View.hs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ generateModal s mt = Modal mt (dialog (Just title) buttons (maxModalWindowWidth
291291
(title, widget, buttons, requiredWidth) =
292292
case mt of
293293
HelpModal -> (" Help ", helpWidget, Nothing, maxModalWindowWidth)
294+
RecipesModal ->
295+
( "Available Recipes"
296+
, helpRecipes (s ^. gameState . availableRecipes)
297+
, Nothing
298+
, 250
299+
)
294300
WinModal ->
295301
let winMsg = "Congratulations!"
296302
continueMsg = "Keep playing"
@@ -322,6 +328,7 @@ helpWidget = (helpKeys <=> fill ' ') <+> (helpCommands <=> fill ' ')
322328
toWidgets (k, v) = [txt k, txt v]
323329
glKeyBindings =
324330
[ ("F1", "Help")
331+
, ("F2", "Available recipes")
325332
, ("Ctrl-q", "quit the game")
326333
, ("Tab", "cycle panel focus")
327334
, ("Meta-w", "focus on the world map")
@@ -344,6 +351,9 @@ helpWidget = (helpKeys <=> fill ' ') <+> (helpCommands <=> fill ' ')
344351
, ("has \"<item>\"", "Check for an item in the inventory")
345352
]
346353

354+
helpRecipes :: (Int, [Recipe Entity]) -> Widget Name
355+
helpRecipes (_, xs) = vBox (map (drawRecipe Nothing Nothing) xs)
356+
347357
descriptionTitle :: Entity -> String
348358
descriptionTitle e = " " ++ from @Text (e ^. entityName) ++ " "
349359

@@ -372,6 +382,14 @@ drawKeyMenu s =
372382
creative = s ^. gameState . creativeMode
373383
cheat = s ^. uiState . uiCheatMode
374384

385+
availRecipes
386+
| null (snd $ s ^. gameState . availableRecipes) = []
387+
| otherwise = [("F2", knownMark (s ^. gameState . availableRecipes) <> "Recipes")]
388+
389+
knownMark (count, _)
390+
| count > 0 = "*"
391+
| otherwise = ""
392+
375393
gameModeWidget =
376394
padLeft Max . padLeftRight 1
377395
. txt
@@ -380,10 +398,8 @@ drawKeyMenu s =
380398
False -> "Classic"
381399
True -> "Creative"
382400
globalKeyCmds =
383-
[ ("F1", "help")
384-
, ("Tab", "cycle")
385-
]
386-
++ [("^k", "creative") | cheat]
401+
[("F1", "help")] <> availRecipes <> [("Tab", "cycle")] <> [("^k", "creative") | cheat]
402+
387403
keyCmdsFor (Just REPLPanel) =
388404
[ ("↓↑", "history")
389405
]
@@ -569,7 +585,7 @@ explainRecipes s e
569585
, padLeftRight 2 $
570586
hCenter $
571587
vBox $
572-
map (hLimit widthLimit . padBottom (Pad 1) . drawRecipe e inv) recipes
588+
map (hLimit widthLimit . padBottom (Pad 1) . drawRecipe (Just e) (Just inv)) recipes
573589
]
574590
where
575591
recipes = recipesWith s e
@@ -594,8 +610,8 @@ recipesWith s e =
594610

595611
-- | Draw an ASCII art representation of a recipe. For now, the
596612
-- weight is not shown.
597-
drawRecipe :: Entity -> Inventory -> Recipe Entity -> Widget Name
598-
drawRecipe e inv (Recipe ins outs reqs time _weight) =
613+
drawRecipe :: Maybe Entity -> Maybe Inventory -> Recipe Entity -> Widget Name
614+
drawRecipe me minv (Recipe ins outs reqs time _weight) =
599615
vBox
600616
-- any requirements (e.g. furnace) go on top.
601617
[ hCenter $ drawReqs reqs
@@ -637,7 +653,9 @@ drawRecipe e inv (Recipe ins outs reqs time _weight) =
637653
)
638654
]
639655
where
640-
missing = E.lookup ingr inv < n
656+
missing = case minv of
657+
Just inv -> E.lookup ingr inv < n
658+
Nothing -> False
641659

642660
drawOut i (n, ingr) =
643661
hBox
@@ -655,7 +673,7 @@ drawRecipe e inv (Recipe ins outs reqs time _weight) =
655673
-- If it's the focused entity, draw it highlighted.
656674
-- If the robot doesn't have any, draw it in red.
657675
fmtEntityName missing ingr
658-
| ingr == e = withAttr deviceAttr $ txtLines nm
676+
| Just ingr == me = withAttr deviceAttr $ txtLines nm
659677
| ingr == timeE = withAttr sandAttr $ txtLines nm
660678
| missing = withAttr invalidFormInputAttr $ txtLines nm
661679
| otherwise = txtLines nm

0 commit comments

Comments
 (0)