packages feed

swarm-0.7.0.0: src/swarm-tui/Swarm/TUI/Controller/SaveScenario.hs

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Collection of functions used to save the scenario metadata.
module Swarm.TUI.Controller.SaveScenario (
  saveScenarioInfoOnFinish,
  saveScenarioInfoOnFinishNocheat,
  saveScenarioInfoOnQuit,
) where

-- See Note [liftA2 re-export from Prelude]

import Brick
import Control.Lens as Lens
import Control.Monad (forM_, unless, when)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Maybe (listToMaybe)
import Data.Set (Set)
import Data.Time (ZonedTime, getZonedTime)
import Swarm.Game.Achievement.Definitions
import Swarm.Game.Scenario.Status
import Swarm.Game.ScenarioInfo
import Swarm.Game.State
import Swarm.Game.State.Substate
import Swarm.TUI.Model
import Swarm.TUI.Model.Achievements (attainAchievement, attainAchievement')
import Swarm.TUI.Model.DebugOption (DebugOption)
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI.Gameplay
import System.FilePath (splitDirectories)

getNormalizedCurrentScenarioPath ::
  MonadIO m =>
  GameState ->
  ScenarioCollection a ->
  m (Maybe FilePath)
getNormalizedCurrentScenarioPath gs sc = do
  -- the path should be normalized and good to search in scenario collection
  traverse (liftIO . normalizeScenarioPath sc . getScenarioPath) $ gs ^. currentScenarioPath

applyCompletionAchievements ::
  Bool ->
  ZonedTime ->
  FilePath ->
  EventM n ProgressionState ()
applyCompletionAchievements won t p = do
  forM_ (listToMaybe $ splitDirectories p) $ \firstDir -> do
    when (won && firstDir == tutorialsDirname) $
      attainAchievement' t (Just $ ScenarioPath p) $
        GlobalAchievement CompletedSingleTutorial

  -- Check if all tutorials have been completed
  tutorialMap <- use $ scenarios . to getTutorials . to scMap
  let isComplete (SISingle (ScenarioWith _ s)) = scenarioIsCompleted s
      -- There are not currently any subcollections within the
      -- tutorials, but checking subcollections recursively just seems
      -- like the right thing to do
      isComplete (SICollection _ (SC m)) = all isComplete m

  when (all isComplete tutorialMap) $
    attainAchievement $
      GlobalAchievement CompletedAllTutorials

saveScenarioInfoOnFinish ::
  FilePath ->
  EventM n PlayState ()
saveScenarioInfoOnFinish p = do
  initialRunCode <- use $ scenarioState . gameState . gameControls . initiallyRunCode
  t <- liftIO getZonedTime
  wc <- use $ scenarioState . gameState . winCondition
  let won = case wc of
        WinConditions (Won _ _) _ -> True
        _ -> False
  ts <- use $ scenarioState . gameState . temporal . ticks
  saved <- use $ scenarioState . gameState . completionStatsSaved

  let currentScenarioInfo :: Traversal' PlayState ScenarioInfo
      currentScenarioInfo = progression . scenarios . scenarioItemByPath p . _SISingle . getScenarioInfo

  replHist <- use $ scenarioState . uiGameplay . uiREPL . replHistory
  let determinator = CodeSizeDeterminators initialRunCode $ replHist ^. replHasExecutedManualInput

  -- Don't update scenario statistics if we have previously saved
  -- statistics for the current scenario upon scenario completion.
  unless saved $
    currentScenarioInfo
      %= updateScenarioInfoOnFinish determinator t ts won

  status <- preuse currentScenarioInfo
  forM_ status $ \si -> do
    liftIO $ saveScenarioInfo p si
    Brick.zoom progression $ applyCompletionAchievements won t p

  scenarioState . gameState . completionStatsSaved .= won

-- | Don't save progress for developers or cheaters.
unlessCheating :: Set DebugOption -> EventM n PlayState () -> EventM n PlayState ()
unlessCheating dOpts a = do
  isAuto <- use $ scenarioState . uiGameplay . uiIsAutoPlay
  when (null dOpts && not isAuto) a

-- | Write the @ScenarioInfo@ out to disk when finishing a game (i.e. on winning or exit).
saveScenarioInfoOnFinishNocheat :: Set DebugOption -> EventM n PlayState ()
saveScenarioInfoOnFinishNocheat dOpts = do
  sc <- use $ progression . scenarios
  gs <- use $ scenarioState . gameState
  unlessCheating dOpts $
    -- the path should be normalized and good to search in scenario collection
    getNormalizedCurrentScenarioPath gs sc >>= mapM_ saveScenarioInfoOnFinish

-- | Write the @ScenarioInfo@ out to disk when exiting a game.
saveScenarioInfoOnQuit :: Set DebugOption -> EventM n PlayState ()
saveScenarioInfoOnQuit dOpts = do
  sc <- use $ progression . scenarios
  gs <- use $ scenarioState . gameState
  unlessCheating dOpts $
    getNormalizedCurrentScenarioPath gs sc >>= mapM_ saveScenarioInfoOnFinish