{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} module Main where import Codec.Serialise import Control.Exception.Safe import Control.Monad.Except import Control.Monad.State import Safe import System.Console.GetOpt import System.Directory import System.Environment import System.Exit import System.FilePath (()) import CursesUIMInstance () import GameName import KeyBindings (dvorakViBindings) import Serialise () import qualified Command as Cmd import qualified CursesUI as CU import qualified Game as G import qualified Highscore as HS import qualified HighscoreFile as HSF import qualified UIMonad as UIM version :: String version = CURRENT_PACKAGE_VERSION data Opt = StatePath FilePath | Username HS.Username | AsciiOnly | Dvorak | Help | Version deriving (Eq, Ord, Show) options :: [OptDescr Opt] options = [ Option ['s'] ["statefile"] (ReqArg StatePath "PATH") "Path to game state file" , Option ['n'] ["username"] (ReqArg Username "NAME") "Optional username for highscores" , Option ['a'] ["ascii"] (NoArg AsciiOnly) "Draw only ASCII characters" , Option ['d'] ["dvorak"] (NoArg Dvorak) "Use dvorak roguelike keys (htns)" , Option ['h'] ["help"] (NoArg Help) "Show usage information" , Option ['v'] ["version"] (NoArg Version) "Show version information" ] usage :: String usage = usageInfo header options where header = "Usage: " <> gameName <> " [OPTION...]" parseArgs :: [String] -> IO ([Opt],[String]) parseArgs argv = case getOpt Permute options argv of (o,n,[]) -> return (o,n) (_,_,errs) -> ioError (userError (concat errs ++ usage)) main :: IO () main = do (opts,_) <- parseArgs =<< getArgs when (Help `elem` opts) $ putStr usage >> exitSuccess when (Version `elem` opts) $ putStrLn version >> exitSuccess statePath <- case headMay [ path | StatePath path <- opts ] of Just path -> pure path Nothing -> do dataDir <- getXdgDirectory XdgData gameName createDirectoryIfMissing True dataDir pure $ dataDir "gamestate" let ascii = AsciiOnly `elem` opts let dvorak = Dvorak `elem` opts let username = headMay [ name | Username name <- opts ] let doCUI :: CU.UIM () -> IO () doCUI m = void (UIM.doUI m) `catch` (\QuitException -> exitSuccess) doCUI . G.runT $ do lift $ UIM.setAsciiOnly ascii when dvorak . lift $ sequence_ [ UIM.setUIBinding ch cmd | (ch,cmd) <- dvorakViBindings ] liftIO (tryAny $ readFileDeserialise statePath) >>= \case Right game -> put game Left _ -> G.initGame let mainLoop :: UIM.UIMonad uiM => G.T uiM () mainLoop = do liftIO . writeFileSerialise statePath =<< get lift . UIM.draw =<< get G.clearTrans playSt <- gets G.playState let after = do playSt' <- gets G.playState when (playSt == G.Playing && playSt' `elem` [G.Dead, G.Won]) $ liftIO . HSF.add =<< gets (G.highscore username) mainLoop runExceptT (mapM_ processCommand =<< (lift . lift) UIM.getInput) >>= either pure (const after) mainLoop data QuitException = QuitException deriving Show instance Exception QuitException processCommand :: UIM.UIMonad uiM => Cmd.Command -> ExceptT () (G.T uiM) () processCommand Cmd.Quit = throw QuitException processCommand Cmd.ForceQuit = throw QuitException processCommand Cmd.ToggleAscii = lift . lift $ UIM.toggleAsciiOnly processCommand Cmd.Redraw = lift . lift $ UIM.redraw processCommand Cmd.Suspend = lift . lift $ UIM.suspend processCommand cmd = lift $ G.doCommand cmd