swarm-0.7.0.0: src/swarm-tui/Swarm/TUI/Controller/EventHandlers/Main.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Here is the main player configurable key event handler while playing the game.
module Swarm.TUI.Controller.EventHandlers.Main (
mainEventHandlers,
isRunning,
whenRunningAppState,
whenRunningPlayState,
runSingleTick,
adjustTPS,
toggleREPLVisibility,
showCESKDebug,
hideRobots,
toggleDiscoveryNotificationModal,
viewGoal,
toggleMessagesModal,
) where
import Brick
import Brick.Keybindings
import Control.Lens as Lens
import Control.Monad (unless, void, when)
import Control.Monad.IO.Class (liftIO)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (originalStructureDefinitions)
import Swarm.Game.State
import Swarm.Game.State.Landscape
import Swarm.Game.State.Robot
import Swarm.Game.State.Substate
import Swarm.Game.Step (finishGameTick)
import Swarm.TUI.Controller.EventHandlers.Frame (runGameTickUI)
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.Dialog.Goal
import Swarm.TUI.Model.Event (MainEvent (..), SwarmEvent (..))
import Swarm.TUI.Model.Menu
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import System.Clock (Clock (..), TimeSpec (..), getTime)
-- | Main keybindings event handler while running the game itself.
--
-- See 'Swarm.TUI.Controller.handleMainEvent'.
mainEventHandlers :: [KeyEventHandler SwarmEvent (EventM Name AppState)]
mainEventHandlers = allHandlers Main $ \case
QuitEvent -> ("Open quit game dialog", toggleQuitGameDialog)
ViewHelpEvent ->
( "View Help screen"
, Brick.zoom (playState . scenarioState) $ toggleMidScenarioModal HelpModal
)
ViewRobotsEvent ->
( "View Robots screen"
, Brick.zoom (playState . scenarioState) $ toggleMidScenarioModal RobotsModal
)
ViewRecipesEvent ->
( "View Recipes screen"
, Brick.zoom (playState . scenarioState) $ toggleDiscoveryNotificationModal RecipesModal availableRecipes
)
ViewCommandsEvent ->
( "View Commands screen"
, Brick.zoom (playState . scenarioState) $ toggleDiscoveryNotificationModal CommandsModal availableCommands
)
ViewMessagesEvent ->
( "View Messages screen"
, Brick.zoom (playState . scenarioState) toggleMessagesModal
)
ViewStructuresEvent ->
( "View Structures screen"
, Brick.zoom (playState . scenarioState) $
toggleStructuresModal (recognizerAutomatons . originalStructureDefinitions)
)
ViewGoalEvent ->
( "View scenario goal description"
, Brick.zoom (playState . scenarioState) viewGoal
)
HideRobotsEvent -> ("Hide robots for a few ticks", Brick.zoom (playState . scenarioState . uiGameplay) hideRobots)
ShowCESKDebugEvent -> ("Show active robot CESK machine debugging line", showCESKDebug)
PauseEvent -> ("Pause or unpause the game", Brick.zoom (playState . scenarioState) $ whenRunningPlayState safeTogglePause)
RunSingleTickEvent -> ("Run game for a single tick", whenRunningAppState runSingleTick)
IncreaseTpsEvent -> ("Double game speed", Brick.zoom (playState . scenarioState) $ whenRunningPlayState . modify $ adjustTPS (+))
DecreaseTpsEvent -> ("Halve game speed", Brick.zoom (playState . scenarioState) $ whenRunningPlayState . modify $ adjustTPS (-))
FocusWorldEvent -> ("Set focus on the World panel", Brick.zoom (playState . scenarioState) $ setFocus WorldPanel)
FocusRobotEvent -> ("Set focus on the Robot panel", Brick.zoom (playState . scenarioState) $ setFocus RobotPanel)
FocusREPLEvent -> ("Set focus on the REPL panel", Brick.zoom (playState . scenarioState) $ setFocus REPLPanel)
FocusInfoEvent -> ("Set focus on the Info panel", Brick.zoom (playState . scenarioState) $ setFocus InfoPanel)
ToggleCreativeModeEvent -> ("Toggle creative mode", whenDebug ToggleCreative $ Brick.zoom (playState . scenarioState) toggleCreativeMode)
ToggleWorldEditorEvent -> ("Toggle world editor mode", whenDebug ToggleWorldEditor $ Brick.zoom (playState . scenarioState) toggleWorldEditor)
ToggleREPLVisibilityEvent -> ("Collapse/Expand REPL panel", Brick.zoom (playState . scenarioState) toggleREPLVisibility)
ViewBaseEvent -> ("View the base robot", Brick.zoom (playState . scenarioState) viewBase)
ToggleFPSEvent -> ("Toggle the FPS display", Brick.zoom (playState . scenarioState) toggleFPS)
toggleQuitGameDialog :: EventM Name AppState ()
toggleQuitGameDialog = do
s <- get
let whichModal = case s ^. playState . scenarioState . gameState . winCondition of
WinConditions (Won _ _) _ -> ScenarioFinishModal WinModal
WinConditions (Unwinnable _) _ -> ScenarioFinishModal LoseModal
_ -> QuitModal
Brick.zoom playState $ toggleEndScenarioModal whichModal $ s ^. uiState . uiMenu
toggleGameModal ::
Foldable t =>
MidScenarioModalType ->
Getter GameState (t a) ->
EventM Name ScenarioState Bool
toggleGameModal m l = do
s <- get
let nothingToShow = null $ s ^. gameState . l
unless nothingToShow $
toggleMidScenarioModal m
return nothingToShow
toggleStructuresModal ::
Foldable t =>
Lens' Landscape (t a) ->
EventM Name ScenarioState ()
toggleStructuresModal l = void $ toggleGameModal StructuresModal (landscape . l)
toggleDiscoveryNotificationModal ::
MidScenarioModalType ->
Lens' Discovery (Notifications a) ->
EventM Name ScenarioState ()
toggleDiscoveryNotificationModal mt l = do
nothingToShow <- toggleGameModal mt $ discovery . l . notificationsContent
unless nothingToShow $ gameState . discovery . l . notificationsCount .= 0
toggleMessagesModal :: EventM Name ScenarioState ()
toggleMessagesModal = do
s <- get
nothingToShow <- toggleGameModal MessagesModal $ messageNotifications . notificationsContent
unless nothingToShow $ gameState . messageInfo . lastSeenMessageTime .= s ^. gameState . temporal . ticks
viewGoal :: EventM Name ScenarioState ()
viewGoal = do
s <- get
if hasAnythingToShow $ s ^. uiGameplay . uiDialogs . uiGoal . goalsContent
then toggleMidScenarioModal GoalModal
else continueWithoutRedraw
hideRobots :: EventM Name UIGameplay ()
hideRobots = do
t <- liftIO $ getTime Monotonic
h <- use uiHideRobotsUntil
case h >= t of
-- ignore repeated keypresses
True -> continueWithoutRedraw
-- hide for two seconds
False -> do
uiHideRobotsUntil .= t + TimeSpec 2 0
invalidateCacheEntry WorldCache
showCESKDebug :: EventM Name AppState ()
showCESKDebug = do
s <- get
let isPaused = s ^. playState . scenarioState . gameState . temporal . paused
let isCreative = s ^. playState . scenarioState . gameState . creativeMode
let hasDebug = hasDebugCapability isCreative $ s ^. playState . scenarioState . gameState
when (isPaused && hasDebug) $ do
debug <- playState . scenarioState . uiGameplay . uiShowDebug Lens.<%= not
if debug
then playState . scenarioState . gameState . temporal . gameStep .= RobotStep SBefore
else zoomGameStateFromAppState finishGameTick >> void updateUI
runSingleTick :: EventM Name AppState ()
runSingleTick = do
playState . scenarioState . gameState . temporal . runStatus .= ManualPause
runGameTickUI
-- | Adjust the ticks per second speed.
adjustTPS :: (Int -> Int -> Int) -> ScenarioState -> ScenarioState
adjustTPS (+/-) = uiGameplay . uiTiming . lgTicksPerSecond %~ (+/- 1)
toggleCreativeMode :: EventM Name ScenarioState ()
toggleCreativeMode = gameState . creativeMode %= not
toggleWorldEditor :: EventM Name ScenarioState ()
toggleWorldEditor = do
uiGameplay . uiWorldEditor . worldOverdraw . isWorldEditorEnabled %= not
setFocus WorldEditorPanel
toggleREPLVisibility :: EventM Name ScenarioState ()
toggleREPLVisibility = do
invalidateCacheEntry WorldCache
uiGameplay . uiShowREPL %= not
viewBase :: EventM Name ScenarioState ()
viewBase = do
invalidateCacheEntry WorldCache
gameState . robotInfo . viewCenterRule .= VCRobot 0
toggleFPS :: EventM Name ScenarioState ()
toggleFPS = uiGameplay . uiTiming . uiShowFPS %= not
-- ----------------------------------------------
-- HELPER UTILS
-- ----------------------------------------------
isRunning :: EventM Name ScenarioState Bool
isRunning = do
mt <- preuse $ uiGameplay . uiDialogs . uiModal . _Just . modalType
return $ maybe True isRunningModal mt
whenRunningAppState :: EventM Name AppState () -> EventM Name AppState ()
whenRunningAppState a = Brick.zoom (playState . scenarioState) isRunning >>= \r -> when r a
whenRunningPlayState :: EventM Name ScenarioState () -> EventM Name ScenarioState ()
whenRunningPlayState a = isRunning >>= \r -> when r a
whenDebug :: DebugOption -> EventM Name AppState () -> EventM Name AppState ()
whenDebug d a = do
debug <- use $ uiState . uiDebugOptions . contains d
when debug a