packages feed

immutaball-core-0.1.0.4.1: Immutaball/Ball/LevelSets.hs

{-# OPTIONS_GHC -fno-warn-tabs #-}  -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

-- LevelSets.hs.

{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE TemplateHaskell #-}

module Immutaball.Ball.LevelSets
	(
		ChallengeModeScores(..), cmsBestTimes, cmsMostCoins,
		LevelSet(..), lsTitle, lsDesc, lsName, lsPict, lsChallengeModeScores, lsLevels,
		LevelSets(..), lsExplicitSets, lsLevelSets,
		getLevelSets,
		getLevelSet,

		-- * Utils
		challengeModeLine
	) where

import Prelude ()
import Immutaball.Prelude

import Control.Arrow

import Control.Lens
import qualified Data.Text as T
import qualified Data.Map as M
import System.FilePath
import Text.Parsec

import Immutaball.Share.Config.Parser
import Immutaball.Share.Context
import Immutaball.Share.Context.Config
import Immutaball.Share.ImmutaballIO
import Immutaball.Share.ImmutaballIO.BasicIO

data ChallengeModeScores = ChallengeModeScores {
	_cmsBestTimes :: (Integer, Integer, Integer),
	_cmsMostCoins :: (Integer, Integer, Integer)
}
	deriving (Eq, Ord, Show)
makeLenses ''ChallengeModeScores

data LevelSet = LevelSet {
	-- | "My Levels"
	_lsTitle :: String,
	-- | "A level set.\\Another paragraph.\Another line in the paragraph."
	_lsDesc  :: String,
	-- | "foo"
	_lsName  :: String,
	-- | "shot-foo/foo.jpg"
	_lsPict  :: String,
	-- | Challenge mode scores: best times (hard, medium, easy), most coins (hard, medium easy).
	_lsChallengeModeScores :: ChallengeModeScores,
	-- | ["map-foo/level.sol", …]
	_lsLevels :: [String]
}
	deriving (Eq, Ord, Show)
makeLenses ''LevelSet

data LevelSets = LevelSets {
	-- | Lines of ‘data/sets.txt’.
	-- [‘set-foo.txt’, …]
	_lsExplicitSets :: [String],
	-- | Map of ‘set-foo.txt’ to the level set.
	_lsLevelSets    :: M.Map String LevelSet
}
	deriving (Eq, Ord, Show)
makeLenses ''LevelSets

-- | Get level sets or fail.
getLevelSets :: IBContext' a -> ImmutaballIOF LevelSets
getLevelSets cxt = do
	asets <- BasicIBIOF $ ReadText ((cxt^.ibDirs.ibStaticDataDir) </> "sets.txt") id
	let err ioErr = BasicIBIOF (PutStrLn ("Error: getLevelSets: failed to read data/sets.txt!  Ensure ‘--static-data-dir=’ is set to a compiled neverball data directory.  Error: " ++ (show ioErr)) ()) >> BasicIBIOF ExitFailureBasicIOF
	msets <- Wait asets id
	sets_ <- err ||| return $ msets
	let setPathList = lines $ T.unpack sets_

	levelSets <- M.fromList . zip setPathList <$> mapM (getLevelSet cxt) setPathList
	return $ LevelSets {
		_lsExplicitSets = setPathList,
		_lsLevelSets    = levelSets
	}

-- | Get level set or fail.
--
-- e.g. ‘getLevelSet cxt "set-foo.txt"’
getLevelSet :: IBContext' a -> String -> ImmutaballIOF LevelSet
getLevelSet cxt path = do
	alevelSetContents <- BasicIBIOF $ ReadText (((cxt^.ibDirs.ibStaticDataDir)) </> path) id
	let err ioErr = BasicIBIOF (PutStrLn ("Error: getLevelSet: failed to read data/"++path++"!  Ensure ‘--static-data-dir=’ is set to a compiled neverball data directory.  Error: " ++ (show ioErr)) ()) >> BasicIBIOF ExitFailureBasicIOF
	mlevelSetContents <- Wait alevelSetContents id
	levelSetContents <- err ||| return $ mlevelSetContents

	let mlevelSet = parseLevelSetFile path (T.unpack $ levelSetContents)
	let perr errMsg = BasicIBIOF (PutStrLn ("Error: getLevelSet: failed to parse data/"++path++"!  (But we could read its contents.)  Error: " ++ errMsg) ()) >> BasicIBIOF ExitFailureBasicIOF
	levelSet <- perr ||| return $ mlevelSet
	return levelSet

parseLevelSetFile :: String -> String -> Either String LevelSet
parseLevelSetFile inputName inputContents = show +++ id $ parseLevelSetFile' inputName inputContents

parseLevelSetFile' :: SourceName -> String -> Either ParseError LevelSet
parseLevelSetFile' inputName inputContents = parse levelSetFileParser inputName inputContents

levelSetFileParser :: Parsec String () LevelSet
levelSetFileParser = LevelSet <$>
	line <*>  -- title
	line <*>  -- desc
	line <*>  -- name
	line <*>  -- pict (screenshot)
	challengeModeLine <*>
	(many line <* eof)
	where line = (manyTill anyChar (lookAhead . try $ endOfLine)) <* endOfLine

challengeModeLine :: Parsec String () ChallengeModeScores
challengeModeLine = ChallengeModeScores <$>
	((,,) <$> nat' <*> nat' <*> nat') <*>
	((,,) <$> nat' <*> nat' <*> nat')
	where nat' = (try nat <|> pure 0) <* spaces