packages feed

swarm-0.6.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,
) 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 (automatons)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (originalStructureDefinitions)
import Swarm.Game.State
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.Event (MainEvent (..), SwarmEvent (..))
import Swarm.TUI.Model.Goal
import Swarm.TUI.Model.UI
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", toggleModal HelpModal)
  ViewRobotsEvent -> ("View Robots screen", toggleModal RobotsModal)
  ViewRecipesEvent -> ("View Recipes screen", toggleDiscoveryNotificationModal RecipesModal availableRecipes)
  ViewCommandsEvent -> ("View Commands screen", toggleDiscoveryNotificationModal CommandsModal availableCommands)
  ViewMessagesEvent -> ("View Messages screen", toggleMessagesModal)
  ViewStructuresEvent -> ("View Structures screen", toggleDiscoveryModal StructuresModal (structureRecognition . automatons . originalStructureDefinitions))
  ViewGoalEvent -> ("View scenario goal description", viewGoal)
  HideRobotsEvent -> ("Hide robots for a few ticks", hideRobots)
  ShowCESKDebugEvent -> ("Show active robot CESK machine debugging line", showCESKDebug)
  PauseEvent -> ("Pause or unpause the game", whenRunning safeTogglePause)
  RunSingleTickEvent -> ("Run game for a single tick", whenRunning runSingleTick)
  IncreaseTpsEvent -> ("Double game speed", whenRunning . modify $ adjustTPS (+))
  DecreaseTpsEvent -> ("Halve game speed", whenRunning . modify $ adjustTPS (-))
  FocusWorldEvent -> ("Set focus on the World panel", setFocus WorldPanel)
  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)
  ToggleREPLVisibilityEvent -> ("Collapse/Expand REPL panel", toggleREPLVisibility)

toggleQuitGameDialog :: EventM Name AppState ()
toggleQuitGameDialog = do
  s <- get
  case s ^. gameState . winCondition of
    WinConditions (Won _ _) _ -> toggleModal $ ScenarioEndModal WinModal
    WinConditions (Unwinnable _) _ -> toggleModal $ ScenarioEndModal LoseModal
    _ -> toggleModal QuitModal

toggleGameModal :: Foldable t => ModalType -> Getter GameState (t a) -> EventM Name AppState Bool
toggleGameModal m l = do
  s <- get
  let nothingToShow = null $ s ^. gameState . l
  unless nothingToShow $ toggleModal m
  return nothingToShow

toggleDiscoveryModal :: Foldable t => ModalType -> Lens' Discovery (t a) -> EventM Name AppState ()
toggleDiscoveryModal m l = void $ toggleGameModal m (discovery . l)

toggleDiscoveryNotificationModal :: ModalType -> Lens' Discovery (Notifications a) -> EventM Name AppState ()
toggleDiscoveryNotificationModal m l = do
  nothingToShow <- toggleGameModal m (discovery . l . notificationsContent)
  unless nothingToShow $ gameState . discovery . l . notificationsCount .= 0

toggleMessagesModal :: EventM Name AppState ()
toggleMessagesModal = do
  s <- get
  nothingToShow <- toggleGameModal MessagesModal (messageNotifications . notificationsContent)
  unless nothingToShow $ gameState . messageInfo . lastSeenMessageTime .= s ^. gameState . temporal . ticks

viewGoal :: EventM Name AppState ()
viewGoal = do
  s <- get
  if hasAnythingToShow $ s ^. uiState . uiGameplay . uiGoal . goalsContent
    then toggleModal GoalModal
    else continueWithoutRedraw

hideRobots :: EventM Name AppState ()
hideRobots = do
  t <- liftIO $ getTime Monotonic
  h <- use $ uiState . uiGameplay . uiHideRobotsUntil
  case h >= t of
    -- ignore repeated keypresses
    True -> continueWithoutRedraw
    -- hide for two seconds
    False -> do
      uiState . uiGameplay . uiHideRobotsUntil .= t + TimeSpec 2 0
      invalidateCacheEntry WorldCache

showCESKDebug :: EventM Name AppState ()
showCESKDebug = do
  s <- get
  let isPaused = s ^. gameState . temporal . paused
  let isCreative = s ^. gameState . creativeMode
  let hasDebug = hasDebugCapability isCreative s
  when (isPaused && hasDebug) $ do
    debug <- uiState . uiGameplay . uiShowDebug Lens.<%= not
    if debug
      then gameState . temporal . gameStep .= RobotStep SBefore
      else zoomGameState finishGameTick >> void updateUI

runSingleTick :: EventM Name AppState ()
runSingleTick = do
  gameState . temporal . runStatus .= ManualPause
  runGameTickUI

-- | Adjust the ticks per second speed.
adjustTPS :: (Int -> Int -> Int) -> AppState -> AppState
adjustTPS (+/-) = uiState . uiGameplay . uiTiming . lgTicksPerSecond %~ (+/- 1)

toggleCreativeMode :: EventM Name AppState ()
toggleCreativeMode = gameState . creativeMode %= not

toggleWorldEditor :: EventM Name AppState ()
toggleWorldEditor = do
  uiState . uiGameplay . uiWorldEditor . worldOverdraw . isWorldEditorEnabled %= not
  setFocus WorldEditorPanel

toggleREPLVisibility :: EventM Name AppState ()
toggleREPLVisibility = do
  invalidateCacheEntry WorldCache
  uiState . uiGameplay . uiShowREPL %= not

-- ----------------------------------------------
--                 HELPER UTILS
-- ----------------------------------------------

isRunning :: EventM Name AppState Bool
isRunning = do
  mt <- preuse $ uiState . uiGameplay . uiModal . _Just . modalType
  return $ maybe True isRunningModal mt

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