Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split debug options #2094

Merged
merged 9 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions app/game/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ module Main (main) where

import Control.Monad (when)
import Data.Foldable qualified
import Data.Set qualified as Set
import Data.Text.IO qualified as T
import GitHash (GitInfo, giBranch, giHash, tGitInfoCwdTry)
import Options.Applicative
import Options.Applicative.Help hiding (color, fullDesc)
import Swarm.App (appMain)
import Swarm.Game.ResourceLoading (getSwarmConfigIniFile)
import Swarm.Language.Format
import Swarm.Language.LSP (lspMain)
import Swarm.Language.Parser.Core (LanguageVersion (..))
import Swarm.TUI.Model (AppOpts (..), ColorMode (..))
import Swarm.TUI.Model.DebugOption
import Swarm.TUI.Model.KeyBindings (KeybindingPrint (..), showKeybindings)
import Swarm.TUI.Model.UI (defaultInitLgTicksPerSecond)
import Swarm.Version
Expand Down Expand Up @@ -64,7 +67,7 @@ cliParser =
pausedAtStart <- paused
autoPlay <- autoplay
speed <- speedFactor
cheatMode <- cheat
debugOptions <- debug
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest something like this:

Suggested change
debugOptions <- debug
initialDebugOptions <- debug
let debugOptions = Set.union cheatMode initialDebugOptions

Then we can get rid of the lambda + record update in the final return.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might have underestimated the powers of ApplicativeDo after last issues. I will try this. 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is not accepted by ApplicativeDo.

app/game/Main.hs:62:13: error:
    • No instance for (Monad Parser) arising from a use of ‘return’

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xsebek what about

debugOptions <- Set.union cheatMode <$> debug

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that also needs a monad. I suppose it makes sense, how else would you write it in a a <$> ... <*> ... style?

I added a comment and made the return a bit nicer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, because it's referring to cheatMode which is the result of a previous action. Actually, I now understand why let debug = Set.union cheatMode ... is not accepted either---for the same reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I don't even understand why the existing code works.

Copy link
Member Author

@xsebek xsebek Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@byorgey I imagine GHC constucts a suitable a that puts everything in the right place:

(\c d o -> A (c + d) o) <$> cheat <*> debug <*> others

colorMode <- color
userWebPort <- webPort
return $ AppOpts {..}
Expand Down Expand Up @@ -130,8 +133,30 @@ cliParser =
, "t/s."
, "(Negative values are allowed, e.g. -3 means 1 tick per 8 sec.)"
]

debug :: Parser (Set.Set DebugOption)
debug = Set.unions <$> (([Set.singleton ToggleCreative] <$ cheat) <|> many debugOption)
xsebek marked this conversation as resolved.
Show resolved Hide resolved
debugOption :: Parser (Set.Set DebugOption)
debugOption = option debugOptionList (long "debug" <> short 'd' <> metavar "OPTS" <> hidden <> helpDoc debugOptionHelp)
debugOptionList :: ReadM (Set.Set DebugOption)
debugOptionList = eitherReader $ \case
"all" -> pure $ Set.fromAscList [minBound .. maxBound]
opts -> Set.fromList <$> readDebugOptionList opts
debugOptionHelp :: Maybe Doc
debugOptionHelp =
Just . nest 2 . vsep $
"Use 'all' or a comma separated list of options:"
: [ fillBreak 20 ("*" <+> pretty name) <+> pretty desc
| o <- [minBound .. maxBound]
, let name = debugOptionName o
, let desc = debugOptionDescription o
]
cheat :: Parser Bool
cheat = switch (long "cheat" <> short 'x' <> help "Enable cheat mode. This allows toggling Creative Mode with Ctrl+v and unlocks \"Testing\" scenarios in the menu.")
cheat = flag' True (long "cheat" <> short 'x' <> helpDoc (Just cheatHelp))
cheatHelp =
"Enable cheat mode."
<+> pretty ("This is an alias for --debug=" <> debugOptionName ToggleCreative)

color :: Parser (Maybe ColorMode)
color = optional $ option colorModeParser (long "color" <> short 'c' <> metavar "MODE" <> help "Use none/8/16/full color mode.")
colorModeParser =
Expand Down
15 changes: 8 additions & 7 deletions src/swarm-tui/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import Swarm.TUI.Launch.Model
import Swarm.TUI.Launch.Prep (prepareLaunchDialog)
import Swarm.TUI.List
import Swarm.TUI.Model
import Swarm.TUI.Model.DebugOption (DebugOption (..))
import Swarm.TUI.Model.Goal
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Popup (progressPopups)
Expand Down Expand Up @@ -171,19 +172,19 @@ handleMainMenuEvent menu = \case
Nothing -> pure ()
Just x0 -> case x0 of
NewGame -> do
cheat <- use $ uiState . uiCheatMode
showTesting <- use $ uiState . uiDebugOptions . icontains ShowTestingScenarios
ss <- use $ runtimeState . scenarios
uiState . uiMenu .= NewGameMenu (pure $ mkScenarioList cheat ss)
uiState . uiMenu .= NewGameMenu (pure $ mkScenarioList showTesting ss)
Tutorial -> do
-- Set up the menu stack as if the user had chosen "New Game > Tutorials"
cheat <- use $ uiState . uiCheatMode
showTesting <- use $ uiState . uiDebugOptions . icontains ShowTestingScenarios
ss <- use $ runtimeState . scenarios
let tutorialCollection = getTutorials ss
topMenu =
BL.listFindBy
((== tutorialsDirname) . T.unpack . scenarioItemName)
(mkScenarioList cheat ss)
tutorialMenu = mkScenarioList cheat tutorialCollection
(mkScenarioList showTesting ss)
tutorialMenu = mkScenarioList showTesting tutorialCollection
menuStack = tutorialMenu :| pure topMenu
uiState . uiMenu .= NewGameMenu menuStack

Expand Down Expand Up @@ -252,8 +253,8 @@ handleNewGameMenuEvent scenarioStack@(curMenu :| rest) = \case
Nothing -> pure ()
Just (SISingle siPair) -> invalidateCache >> startGame siPair Nothing
Just (SICollection _ c) -> do
cheat <- use $ uiState . uiCheatMode
uiState . uiMenu .= NewGameMenu (NE.cons (mkScenarioList cheat c) scenarioStack)
showTesting <- use $ uiState . uiDebugOptions . icontains ShowTestingScenarios
uiState . uiMenu .= NewGameMenu (NE.cons (mkScenarioList showTesting c) scenarioStack)
CharKey 'o' -> showLaunchDialog
CharKey 'O' -> showLaunchDialog
Key V.KEsc -> exitNewGameMenu scenarioStack
Expand Down
13 changes: 7 additions & 6 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Swarm.TUI.Controller.UpdateUI (updateUI)
import Swarm.TUI.Controller.Util
import Swarm.TUI.Editor.Model (isWorldEditorEnabled, worldOverdraw)
import Swarm.TUI.Model
import Swarm.TUI.Model.DebugOption (DebugOption (ToggleCreative, ToggleWorldEditor))
import Swarm.TUI.Model.Event (MainEvent (..), SwarmEvent (..))
import Swarm.TUI.Model.Goal
import Swarm.TUI.Model.UI
Expand Down Expand Up @@ -51,8 +52,8 @@ mainEventHandlers = allHandlers Main $ \case
FocusRobotEvent -> ("Set focus on the Robot panel", setFocus RobotPanel)
FocusREPLEvent -> ("Set focus on the REPL panel", setFocus REPLPanel)
FocusInfoEvent -> ("Set focus on the Info panel", setFocus InfoPanel)
ToggleCreativeModeEvent -> ("Toggle creative mode", whenCheating toggleCreativeMode)
ToggleWorldEditorEvent -> ("Toggle world editor mode", whenCheating toggleWorldEditor)
ToggleCreativeModeEvent -> ("Toggle creative mode", whenDebug ToggleCreative toggleCreativeMode)
ToggleWorldEditorEvent -> ("Toggle world editor mode", whenDebug ToggleWorldEditor toggleWorldEditor)
ToggleREPLVisibilityEvent -> ("Collapse/Expand REPL panel", toggleREPLVisibility)

toggleQuitGameDialog :: EventM Name AppState ()
Expand Down Expand Up @@ -149,7 +150,7 @@ isRunning = do
whenRunning :: EventM Name AppState () -> EventM Name AppState ()
whenRunning a = isRunning >>= \r -> when r a

whenCheating :: EventM Name AppState () -> EventM Name AppState ()
whenCheating a = do
s <- get
when (s ^. uiState . uiCheatMode) a
whenDebug :: DebugOption -> EventM Name AppState () -> EventM Name AppState ()
whenDebug d a = do
debug <- use $ uiState . uiDebugOptions . icontains d
xsebek marked this conversation as resolved.
Show resolved Hide resolved
when debug a
26 changes: 15 additions & 11 deletions src/swarm-tui/Swarm/TUI/Controller/SaveScenario.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module Swarm.TUI.Controller.SaveScenario (
-- See Note [liftA2 re-export from Prelude]
import Brick.Widgets.List qualified as BL
import Control.Lens as Lens
import Control.Monad (forM_, unless, void, when)
import Control.Monad (forM_, void, when)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.State (MonadState)
import Data.Maybe (fromMaybe)
Expand All @@ -24,6 +24,7 @@ import Swarm.Game.State.Runtime
import Swarm.Game.State.Substate
import Swarm.TUI.Model
import Swarm.TUI.Model.Achievements (attainAchievement')
import Swarm.TUI.Model.DebugOption (DebugOption (..))
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI
import System.FilePath (splitDirectories)
Expand Down Expand Up @@ -71,24 +72,26 @@ saveScenarioInfoOnFinish p = do
liftIO $ saveScenarioInfo p si
return status

-- | Don't save progress for developers and cheaters.
unlessCheating :: MonadState AppState m => m () -> m ()
unlessCheating a = do
debugging <- use $ uiState . uiDebugOptions
isAuto <- use $ uiState . uiGameplay . uiIsAutoPlay
when (null debugging && not isAuto) a

-- | Write the @ScenarioInfo@ out to disk when finishing a game (i.e. on winning or exit).
saveScenarioInfoOnFinishNocheat :: (MonadIO m, MonadState AppState m) => m ()
saveScenarioInfoOnFinishNocheat = do
-- Don't save progress if we are in cheat mode
cheat <- use $ uiState . uiCheatMode
unless cheat $ do
saveScenarioInfoOnFinishNocheat =
unlessCheating $ do
-- the path should be normalized and good to search in scenario collection
getNormalizedCurrentScenarioPath >>= \case
Nothing -> return ()
Just p -> void $ saveScenarioInfoOnFinish p

-- | Write the @ScenarioInfo@ out to disk when exiting a game.
saveScenarioInfoOnQuit :: (MonadIO m, MonadState AppState m) => m ()
saveScenarioInfoOnQuit = do
-- Don't save progress if we are in cheat mode
-- NOTE This check is duplicated in "saveScenarioInfoOnFinishNocheat"
cheat <- use $ uiState . uiCheatMode
unless cheat $ do
saveScenarioInfoOnQuit =
unlessCheating $ do
getNormalizedCurrentScenarioPath >>= \case
Nothing -> return ()
Just p -> do
Expand All @@ -115,4 +118,5 @@ saveScenarioInfoOnQuit = do
-- Now rebuild the NewGameMenu so it gets the updated ScenarioInfo,
-- being sure to preserve the same focused scenario.
sc <- use $ runtimeState . scenarios
forM_ (mkNewGameMenu cheat sc (fromMaybe p curPath)) (uiState . uiMenu .=)
showTesting <- use $ uiState . uiDebugOptions . icontains ShowTestingScenarios
forM_ (mkNewGameMenu showTesting sc (fromMaybe p curPath)) (uiState . uiMenu .=)
10 changes: 6 additions & 4 deletions src/swarm-tui/Swarm/TUI/Controller/UpdateUI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import Swarm.Language.Value (Value (VExc, VUnit), envTydefs, prettyValue)
import Swarm.TUI.Controller.SaveScenario (saveScenarioInfoOnFinishNocheat)
import Swarm.TUI.Controller.Util
import Swarm.TUI.Model
import Swarm.TUI.Model.DebugOption (DebugOption (..))
import Swarm.TUI.Model.Goal
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Popup (Popup (..), addPopup)
Expand Down Expand Up @@ -174,7 +175,6 @@ updateUI = do
doGoalUpdates :: EventM Name AppState Bool
doGoalUpdates = do
curGoal <- use (uiState . uiGameplay . uiGoal . goalsContent)
isCheating <- use (uiState . uiCheatMode)
curWinCondition <- use (gameState . winCondition)
announcementsSeq <- use (gameState . messageInfo . announcementQueue)
let announcementsList = toList announcementsSeq
Expand Down Expand Up @@ -203,7 +203,8 @@ doGoalUpdates = do
-- advance the menu at that point.
return True
WinConditions _ oc -> do
let newGoalTracking = GoalTracking announcementsList $ constructGoalMap isCheating oc
showHiddenGoals <- use $ uiState . uiDebugOptions . icontains ShowHiddenGoals
let newGoalTracking = GoalTracking announcementsList $ constructGoalMap showHiddenGoals oc
-- The "uiGoal" field is initialized with empty members, so we know that
-- this will be the first time showing it if it will be nonempty after previously
-- being empty.
Expand Down Expand Up @@ -240,8 +241,9 @@ doGoalUpdates = do
-- automatically popped up.
gameState . messageInfo . announcementQueue .= mempty

hideGoals <- use $ uiState . uiGameplay . uiHideGoals
unless hideGoals $
isAutoPlay <- use $ uiState . uiGameplay . uiIsAutoPlay
showGoalsAnyway <- use $ uiState . uiDebugOptions . icontains ShowGoalDialogsInAutoPlay
unless (isAutoPlay && not showGoalsAnyway) $
openModal GoalModal

return goalWasUpdated
Expand Down
8 changes: 5 additions & 3 deletions src/swarm-tui/Swarm/TUI/Model.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import Control.Monad.State (MonadState)
import Data.List (findIndex)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import Data.Text (Text)
import Data.Vector qualified as V
import GitHash (GitInfo)
Expand All @@ -99,6 +100,7 @@ import Swarm.Game.Tick (TickNumber (..))
import Swarm.Game.World.Gen (Seed)
import Swarm.Log
import Swarm.TUI.Inventory.Sorting
import Swarm.TUI.Model.DebugOption (DebugOption)
import Swarm.TUI.Model.Event (SwarmEvent)
import Swarm.TUI.Model.Menu
import Swarm.TUI.Model.Name
Expand Down Expand Up @@ -255,8 +257,8 @@ data AppOpts = AppOpts
-- ^ Automatically run the solution defined in the scenario file
, speed :: Int
-- ^ Initial game speed (logarithm)
, cheatMode :: Bool
-- ^ Should cheat mode be enabled?
, debugOptions :: Set DebugOption
-- ^ Debugging options, for example show creative switch.
, colorMode :: Maybe ColorMode
-- ^ What colour mode should be used?
, userWebPort :: Maybe Port
Expand All @@ -275,7 +277,7 @@ defaultAppOpts =
, pausedAtStart = False
, autoPlay = False
, speed = defaultInitLgTicksPerSecond
, cheatMode = False
, debugOptions = mempty
, colorMode = Nothing
, userWebPort = Nothing
, repoGitInfo = Nothing
Expand Down
58 changes: 58 additions & 0 deletions src/swarm-tui/Swarm/TUI/Model/DebugOption.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Sum type representing the Swarm debug options.
module Swarm.TUI.Model.DebugOption (
DebugOption (..),
debugOptionName,
debugOptionDescription,
readDebugOption,
readDebugOptionList,
) where

import Data.Foldable (find, foldl')
import Data.List.Extra (enumerate, splitOn, trim)

data DebugOption
= ToggleCreative
| ToggleWorldEditor
| DebugCESK
| ListAllRobots
| ListRobotIDs
| ShowHiddenGoals
| ShowGoalDialogsInAutoPlay
| ShowTestingScenarios
deriving (Eq, Ord, Show, Enum, Bounded)

debugOptionName :: DebugOption -> String
debugOptionName = \case
ToggleCreative -> "creative"
ToggleWorldEditor -> "editor"
DebugCESK -> "cesk"
ListAllRobots -> "all_robots"
ListRobotIDs -> "robot_id"
ShowHiddenGoals -> "hidden_goals"
ShowGoalDialogsInAutoPlay -> "autoplay_goals"
ShowTestingScenarios -> "testing"

debugOptionDescription :: DebugOption -> String
debugOptionDescription = \case
ToggleCreative -> "allow toggling creative mode on/off"
ToggleWorldEditor -> "allow toggling the world editor mode on/off"
DebugCESK -> "allow toggling the CESK debug view on/off"
ListAllRobots -> "list all robots (including system robots) in the robot panel"
ListRobotIDs -> "list robot IDs in the robot panel"
ShowHiddenGoals -> "show hidden objectives in the goal dialog"
ShowGoalDialogsInAutoPlay -> "show goal dialogs when running in autoplay"
ShowTestingScenarios -> "show Testing folder in scenarios menu"

readDebugOption :: String -> Maybe DebugOption
readDebugOption name = find ((trim name ==) . debugOptionName) enumerate

readDebugOptionList :: String -> Either String [DebugOption]
readDebugOptionList = foldl' eitherRead (Right []) . splitOn ","
where
eitherRead s o = case (s, readDebugOption o) of
(Left e, _) -> Left e
(_, Nothing) -> Left $ "unknown option '" <> o <> "'"
(Right oss, Just os) -> Right $ os : oss
4 changes: 2 additions & 2 deletions src/swarm-tui/Swarm/TUI/Model/Goal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ hasMultipleGoals gt =
goalCount = sum . M.elems . M.map NE.length . goals $ gt

constructGoalMap :: Bool -> ObjectiveCompletion -> CategorizedGoals
constructGoalMap isCheating oc =
constructGoalMap showHidden oc =
M.fromList $
mapMaybe (traverse nonEmpty) categoryList
where
Expand All @@ -118,7 +118,7 @@ constructGoalMap isCheating oc =
filter (maybe False previewable . view objectivePrerequisite) inactiveGoals

suppressHidden =
if isCheating
if showHidden
then id
else filter $ not . view objectiveHidden

Expand Down
8 changes: 4 additions & 4 deletions src/swarm-tui/Swarm/TUI/Model/Menu.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ makePrisms ''Menu

-- | Create a brick 'BL.List' of scenario items from a 'ScenarioCollection'.
mkScenarioList :: Bool -> ScenarioCollection -> BL.List Name ScenarioItem
mkScenarioList cheat = flip (BL.list ScenarioList) 1 . V.fromList . filterTest . scenarioCollectionToList
mkScenarioList showTesting = flip (BL.list ScenarioList) 1 . V.fromList . filterTest . scenarioCollectionToList
where
filterTest = if cheat then id else filter (\case SICollection n _ -> n /= "Testing"; _ -> True)
filterTest = if showTesting then id else filter (\case SICollection n _ -> n /= "Testing"; _ -> True)

-- | Given a 'ScenarioCollection' and a 'FilePath' which is the canonical
-- path to some folder or scenario, construct a 'NewGameMenu' stack
-- focused on the given item, if possible.
mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu
mkNewGameMenu cheat sc path = fmap NewGameMenu $ NE.nonEmpty =<< go (Just sc) (splitPath path) []
mkNewGameMenu showTesting sc path = fmap NewGameMenu $ NE.nonEmpty =<< go (Just sc) (splitPath path) []
where
go ::
Maybe ScenarioCollection ->
Expand All @@ -125,7 +125,7 @@ mkNewGameMenu cheat sc path = fmap NewGameMenu $ NE.nonEmpty =<< go (Just sc) (s
hasName (SISingle (_, ScenarioInfo pth _)) = takeFileName pth == thing
hasName (SICollection nm _) = nm == into @Text (dropTrailingPathSeparator thing)

lst = BL.listFindBy hasName (mkScenarioList cheat curSC)
lst = BL.listFindBy hasName (mkScenarioList showTesting curSC)

nextSC = case M.lookup (dropTrailingPathSeparator thing) (scMap curSC) of
Just (SICollection _ c) -> Just c
Expand Down
5 changes: 2 additions & 3 deletions src/swarm-tui/Swarm/TUI/Model/StateUpdate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ initPersistentState ::
initPersistentState opts@(AppOpts {..}) = do
(warnings :: Seq SystemFailure, (initRS, initUI, initKs)) <- runAccum mempty $ do
rs <- initRuntimeState pausedAtStart
ui <- initUIState speed (not (skipMenu opts)) cheatMode
ui <- initUIState speed (not (skipMenu opts)) debugOptions
ks <- initKeyHandlingState
return (rs, ui, ks)
let initRS' = addWarnings initRS (F.toList warnings)
Expand Down Expand Up @@ -249,15 +249,14 @@ scenarioToUIState isAutoplaying siPair@(scenario, _) gs u = do
return $
u
& uiPlaying .~ True
& uiCheatMode ||~ isAutoplaying
& uiAttrMap
.~ applyAttrMappings
( map (first getWorldAttrName . toAttrPair) $
fst siPair ^. scenarioLandscape . scenarioAttrs
)
swarmAttrMap
& uiGameplay . uiGoal .~ emptyGoalDisplay
& uiGameplay . uiHideGoals .~ (isAutoplaying && not (u ^. uiCheatMode))
& uiGameplay . uiIsAutoPlay .~ isAutoplaying
& uiGameplay . uiFocusRing .~ initFocusRing
& uiGameplay . uiInventory . uiInventorySearch .~ Nothing
& uiGameplay . uiInventory . uiInventoryList .~ Nothing
Expand Down
Loading