module SessionUIMock ( unwindMacros ) where import Prelude () import Game.LambdaHack.Core.Prelude import Control.Monad.Trans.Class import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.Writer.Lazy import Data.Bifunctor (bimap) import qualified Data.Map.Strict as M import qualified Game.LambdaHack.Client.UI.Content.Input as IC import Game.LambdaHack.Client.UI.ContentClientUI import Game.LambdaHack.Client.UI.FrameM import Game.LambdaHack.Client.UI.HandleHumanLocalM import Game.LambdaHack.Client.UI.HandleHumanM import qualified Game.LambdaHack.Client.UI.HumanCmd as HumanCmd import qualified Game.LambdaHack.Client.UI.Key as K import Game.LambdaHack.Client.UI.SessionUI (KeyMacro (..), KeyMacroFrame (..), emptyMacroFrame) data SessionUIMock = SessionUIMock { smacroFrame :: KeyMacroFrame , smacroStack :: [KeyMacroFrame] , sccui :: CCUI , unwindTicks :: Int } type KeyMacroBufferMock = Either String String type KeyPendingMock = String type KeyLastMock = String type BufferTrace = [(KeyMacroBufferMock, KeyPendingMock, KeyLastMock)] type ActionLog = String data Op = Looped | HeadEmpty humanCommandMock :: WriterT [(BufferTrace, ActionLog)] (State SessionUIMock) () humanCommandMock = do abuffs <- lift $ do sess <- get return $ renderTrace (smacroFrame sess : smacroStack sess) -- log session abortOrCmd <- lift iterationMock -- do stuff -- GC macro stack if there are no actions left to handle, -- removing all unnecessary macro frames at once, -- but leaving the last one for user's in-game macros. lift $ modify $ \sess -> let (smacroFrameNew, smacroStackMew) = dropEmptyMacroFrames (smacroFrame sess) (smacroStack sess) in sess { smacroFrame = smacroFrameNew , smacroStack = smacroStackMew } case abortOrCmd of Left Looped -> void $ tell [(abuffs, "Macro looped")] Left HeadEmpty -> void $ tell [(abuffs, "")] -- exit loop Right Nothing -> tell [(abuffs, "")] >> humanCommandMock Right (Just out) -> tell [(abuffs, show out)] >> humanCommandMock iterationMock :: State SessionUIMock (Either Op (Maybe K.KM)) iterationMock = do SessionUIMock _ _ CCUI{coinput=IC.InputContent{bcmdMap}} ticks <- get if ticks <= 1000 then do modify $ \sess -> sess {unwindTicks = ticks + 1} mkm <- promptGetKeyMock case mkm of Nothing -> return $ Left HeadEmpty -- macro finished Just km -> case km `M.lookup` bcmdMap of Just (_, _, cmd) -> Right <$> cmdSemInCxtOfKMMock km cmd _ -> return $ Right $ Just km -- unknown command; fine for tests else return $ Left Looped cmdSemInCxtOfKMMock :: K.KM -> HumanCmd.HumanCmd -> State SessionUIMock (Maybe K.KM) cmdSemInCxtOfKMMock km cmd = do modify $ \sess -> sess {smacroFrame = updateKeyLast km cmd $ smacroFrame sess} cmdSemanticsMock km cmd cmdSemanticsMock :: K.KM -> HumanCmd.HumanCmd -> State SessionUIMock (Maybe K.KM) cmdSemanticsMock km = \case HumanCmd.Macro s -> do modify $ \sess -> let kms = K.mkKM <$> s (smacroFrameNew, smacroStackMew) = macroHumanTransition kms (smacroFrame sess) (smacroStack sess) in sess { smacroFrame = smacroFrameNew , smacroStack = smacroStackMew } return Nothing HumanCmd.Repeat n -> do modify $ \sess -> let (smacroFrameNew, smacroStackMew) = repeatHumanTransition n (smacroFrame sess) (smacroStack sess) in sess { smacroFrame = smacroFrameNew , smacroStack = smacroStackMew } return Nothing HumanCmd.RepeatLast n -> do modify $ \sess -> sess {smacroFrame = repeatLastHumanTransition n (smacroFrame sess) } return Nothing HumanCmd.Record -> do modify $ \sess -> sess {smacroFrame = fst $ recordHumanTransition (smacroFrame sess) } return Nothing _ -> return $ Just km promptGetKeyMock :: State SessionUIMock (Maybe K.KM) promptGetKeyMock = do SessionUIMock macroFrame _ CCUI{coinput=IC.InputContent{bcmdMap}} _ <- get case keyPending macroFrame of KeyMacro (km : kms) -> do modify $ \sess -> sess {smacroFrame = (smacroFrame sess) {keyPending = KeyMacro kms}} modify $ \sess -> sess {smacroFrame = addToMacro bcmdMap km $ smacroFrame sess} return (Just km) KeyMacro [] -> return Nothing unwindMacrosFull :: IC.InputContent -> KeyMacro -> [(BufferTrace, ActionLog)] unwindMacrosFull coinput keyPending = let initSession = SessionUIMock { smacroFrame = emptyMacroFrame {keyPending} , smacroStack = [] , sccui = emptyCCUI {coinput} , unwindTicks = 0 } in evalState (execWriterT humanCommandMock) initSession accumulateActions :: [(BufferTrace, ActionLog)] -> [(BufferTrace, ActionLog)] accumulateActions ba = let (buffers, actions) = unzip ba actionlog = concat <$> inits actions in if snd (last ba) == "Macro looped" then ba else zip buffers actionlog unwindMacros :: IC.InputContent -> KeyMacro -> [(BufferTrace, ActionLog)] unwindMacros coinput keyPending = accumulateActions $ unwindMacrosFull coinput keyPending renderTrace :: [KeyMacroFrame] -> BufferTrace renderTrace macroFrames = let buffers = bimap (concatMap K.showKM) (concatMap K.showKM . unKeyMacro) . keyMacroBuffer <$> macroFrames pendingKeys = concatMap K.showKM . unKeyMacro . keyPending <$> macroFrames lastKeys = maybe "" K.showKM . keyLast <$> macroFrames in zip3 buffers pendingKeys lastKeys