packages feed

tttool-1.4: src/GMERun.hs

{-# LANGUAGE FlexibleContexts #-}
module GMERun (playTipToi, execOID) where

import Text.Printf
import Data.Bits
import Data.List
import qualified Data.Map as M
import Control.Monad.State.Strict
import Control.Monad.Reader
import System.Console.Haskeline
import System.Directory
import System.FilePath
import System.Random
import Data.Foldable (for_)
import Data.Char

import Types
import PrettyPrint
import Utils
import PlaySound

type PlayState r = M.Map r Word16

type GMEM r =
    ReaderT (CodeMap, Transscript, TipToiFile) (StateT (PlayState r) IO)

formatState :: PlayState ResReg -> String
formatState s = spaces $
    map (\(k,v) -> printf "$%d=%d" k v) $
    filter (\(k,v) -> k == 0 || v /= 0) $
    M.toAscList s

playTipToi :: CodeMap -> Transscript -> TipToiFile -> IO ()
playTipToi cm t tt = do
    let initialState = M.fromList $ zip [0..] (ttInitialRegs tt)
    printf "Initial state (not showing zero registers): %s\n" (formatState initialState)

    dir <- getAppUserDataDirectory "tttool"
    createDirectoryIfMissing True dir
    let history_file = dir </> "play_history"
    let completion = completeFromList $
            M.keys cm ++ [show n | (n, Just _) <- ttScripts tt, n `notElem` M.elems cm]
    let haskeline_settings = completion `setComplete` defaultSettings { historyFile = Just history_file }

    flip evalStateT initialState $ flip runReaderT (cm,t,tt) $ do
        mapM_ playTTAudio $ concat $ ttWelcome tt
        runInputT haskeline_settings $ nextOID $ \i -> do
            execOID i
            s <- get
            liftIO $ printf "State now: %s\n" $ formatState s

execOID :: Word16 -> GMEM Word16 ()
execOID i = do
    (cm,t,tt) <- ask
    case lookup (fromIntegral i) (ttScripts tt) of
        Nothing -> do
            liftIO $ printf "OID %d not in main table\n" i
        Just Nothing -> do
            liftIO $ printf "OID %d deactivated\n" i
        Just (Just lines) -> do
            code <- gets $ \s -> find (enabledLine s) lines
            case code of
                Nothing -> liftIO $ do
                    printf "None of these lines matched!\n"
                    mapM_ (putStrLn . ppLine t) lines

                Just l -> do
                    liftIO $ printf "Executing:  %s\n" (ppLine t l)
                    applyLine l

enabledLine :: Ord r => PlayState r -> Line r -> Bool
enabledLine s (Line _ cond _ _) = all (condTrue s) cond

condTrue :: Ord r => PlayState r -> Conditional r -> Bool
condTrue s (Cond v1 o v2) = value s v1 =?= value s v2
  where
    (=?=) = case o of
        Eq  -> (==)
        NEq -> (/=)
        Lt  -> (<)
        GEq -> (>=)
        _   -> \_ _ -> False

value :: Ord r => PlayState r -> TVal r -> Word16
value m (Reg r) = M.findWithDefault 0 r m
value m (Const n) = n

getVal :: (Ord r, MonadState (PlayState r) m) => TVal r -> m Word16
getVal v = gets $ \s ->  value s v

modReg :: (Ord r, MonadState (PlayState r) m) => r -> (Word16 -> Word16) -> m ()
modReg r f = do
    x <- getVal (Reg r)
    modify $ M.insert r (f x)

playTTAudio :: Word16 -> GMEM r ()
playTTAudio i = do
    (_,_,tt) <- ask
    liftIO $ printf "Playing audio sample %d\n" i
    let bs = ttAudioFiles tt !! fromIntegral i
    liftIO $ playSound bs

applyLine :: Line Word16 -> GMEM Word16 ()
applyLine (Line _ _ acts playlist) = go acts
  where
    go (Neg r : acts) = do
        modReg r neg
        go acts
    go (ArithOp o r v : acts) = do
        arg <- getVal v
        let (*) = applyOp o
        modReg r (* arg)
        go acts
    go (Jump v: _ ) = do
        n <- getVal v
        execOID n
    go (Play n: acts) = do
        playTTAudio (playlist !! fromIntegral n)
        go acts
    go (Random a b: acts) = do
        pick <- liftIO $ randomRIO (b,a)
        playTTAudio (playlist !! fromIntegral pick)
        go acts
    go (_: acts) = do
        go acts
    go [] = return ()

    neg 0 = 1
    neg _ = 0

    applyOp Inc = (+)
    applyOp Dec = (-)
    applyOp Mult = (*)
    applyOp Div = div
    applyOp Mod = mod
    applyOp And = (.&.)
    applyOp Or  = (.|.)
    applyOp XOr = xor
    applyOp Set = \_ v -> v


untilNothing :: Monad m => (a -> m (Maybe a)) -> a -> m ()
untilNothing f i = do
    r <- f i
    case r of Just i' -> untilNothing f i'
              Nothing -> return ()

nextOID :: (Word16 -> GMEM r ()) -> InputT (GMEM r) ()
nextOID action = go
  where
    go = do
        (cm,t,tt) <- lift ask
        mstr <- getInputLine "Next OID touched? "
        for_ mstr $ \str -> do
            let str' = dropWhile isSpace $ reverse $ dropWhile isSpace $ reverse $ str
            case () of () | Just i <- readMaybe str'   -> lift $ action i
                          | str' == ""                 -> return ()
                          | Just i <- M.lookup str' cm -> lift $ action i
                          | otherwise -> liftIO $ putStrLn "Please enter a number (OID) or name of a script."
            go

completeFromList :: Monad m => [[Char]] -> CompletionFunc m
completeFromList xs = completeWord Nothing " " $ \p -> return
            [simpleCompletion x | x <- xs, p `isPrefixOf` x ]