packages feed

immutaball-core-0.1.0.4.1: Immutaball/Ball/State/LevelSelect.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 #-}

-- LevelSelect.hs.

{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE Arrows, TemplateHaskell, ScopedTypeVariables #-}

module Immutaball.Ball.State.LevelSelect
	(
		mkLevelSelectState,
		LevelSelectWidget(..), AsLevelSelectWidget(..),
		levelSelectGui,
		levelSelectBaseGui,
		levelSelectButtons
	) where

import Prelude ()
import Immutaball.Prelude

import Control.Arrow
import Control.Exception
import Control.Lens
import Control.Monad
--import Data.Functor.Identity

--import Control.Lens
--import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
--import qualified Data.Map as M
import qualified SDL.Raw.Enum as Raw
import System.FilePath

import Immutaball.Ball.LevelSets
import qualified Immutaball.Ball.State.Play as Play
import Immutaball.Share.Context
import Immutaball.Share.Context.Config
import Immutaball.Share.Level.Base
import Immutaball.Share.Level.Parser
import Immutaball.Share.GUI
import Immutaball.Share.ImmutaballIO
import Immutaball.Share.ImmutaballIO.BasicIO
import Immutaball.Share.Math
import Immutaball.Share.State
import Immutaball.Share.State.Context
import Immutaball.Share.Utils
import Immutaball.Share.Wire

-- (Placed at beginning to avoid Template Haskell errors.)
data LevelSelectWidget =
	  LevelSelectRoot
	| BackButton
	| LevelsVstack
	| LevelButton String
	| Anonymous Integer
	deriving (Eq, Ord, Show)
makeClassyPrisms ''LevelSelectWidget

mkLevelSelectState :: LevelSet -> (Either IBContext IBStateContext -> Immutaball) -> Either IBContext IBStateContext -> Immutaball
mkLevelSelectState levelSet mkBack baseCxt0 = closeSecondI . switch . fromImmutaballSingleWith Nothing . openSecondI $ proc (Identity request) -> do
	rec
		cxtLast <- delay cxt0 -< cxt
		cxtn <- requireBasics -< (cxtLast, request)

		--let (levelSet :: LevelSet) = levelSet

		--(guiResponse, cxtnp1) <- mkGUI $ levelSelectGui levelSet -< (GUIDrive request, cxtn)
		(guiResponse, cxtnp1) <- withM (\gui -> second (mkGUI gui) >>> arr snd) (return . fst) -< (levelSelectGui levelSet, (GUIDrive request, cxtn))
		let response = ContinueResponse

		let isEsc  = (const False ||| (== (fromIntegral Raw.SDLK_ESCAPE, True))) . matching _Keybd $ request
		let isBack = isEsc || guiResponse == WidgetAction BackButton

		() <- finishFrame -< (request, cxtnp1)
		cxt <- returnA -< cxtnp1

	-- If a level was selected, parse it.
	let (toLevelPath :: Maybe String) = (const Nothing ||| Just) . matching (_WidgetAction . _LevelButton) $ guiResponse
	let (toLevelPath' :: Maybe String) = ((cxt^.ibContext.ibDirs.ibStaticDataDir) </>) <$> toLevelPath
	(mtoLevelContents :: Maybe (Either IOException BL.ByteString)) <- monadic -< maybe (pure Nothing) (\path -> (Just <$>) . liftIBIO . BasicIBIOF $ ReadBytesSync path id) $ toLevelPath'
	(toLevelContents :: Maybe BL.ByteString) <- monadic -< maybe (pure Nothing) (\mcontents -> liftIBIO . ThrowIO ||| pure . Just $ mcontents) $ mtoLevelContents
	let (toLevelParse :: Maybe (Either LevelIBParseException LevelIB)) = parseLevelFile' <$> toLevelPath <*> toLevelContents
	(toLevel :: Maybe LevelIB) <- monadic -< maybe (pure Nothing) (liftIBIO . ThrowIO ||| pure . Just) $ toLevelParse
	let (toLevelPathLevel :: Maybe (String, LevelIB)) = (,) <$> toLevelPath <*> toLevel

	-- Switch to a level.
	let switchTo0 = flip fmap toLevelPathLevel $ \(levelPath, level) -> openSecondI $ Play.mkPlayState (Just levelSet) levelPath level (mkLevelSelectState levelSet mkBack) (Right cxt)
	{-
	let switchTo0 = flip fmap toLevel $ \level -> openSecondI $ Play.mkPlayState levelSet level (mkLevelSelectState levelSet mkBack) (Right cxt)
	{-
	let switchTo0 = if' (isJust toLevel) Nothing   . Just . openSecondI $ mkPlayState levelSet level (mkLevelSelectState levelSet mkBack) (Right cxt)
	-}
	-}
	-- Switch on Back button.
	let switchTo  = if' (not    isBack ) switchTo0 . Just . openSecondI $ mkBack (Right cxt)
	returnA -< (Identity response, switchTo)

	where
		cxt0 = either initialStateCxt id baseCxt0
		useUnsafeVersion = False
		parseLevelFile' = if' useUnsafeVersion (\p -> unsafeParseLevelFileRaw p . BL.toStrict) parseLevelFile

-- TODO: make a better UI.  For now we just have a simple list of levels.

levelSelectGui :: LevelSet -> [Widget LevelSelectWidget]
levelSelectGui levelSet =
	levelSelectBaseGui ++
	levelSelectButtons levelSet

levelSelectBaseGui :: [Widget LevelSelectWidget]
levelSelectBaseGui =
	[
		RootWidget   $ Root   { _rootWid   = LevelSelectRoot                                },
		ButtonWidget $ Button { _buttonWid = BackButton, _buttonWparent = LevelSelectRoot,
			_buttonText = "Back", _buttonRect = Just $ Rect (Vec2 (-0.800) (0.720)) (Vec2 (-0.700) (0.800)) },
		VstackWidget $ Vstack { _vstackWid = LevelsVstack, _vstackWparent = LevelSelectRoot }
	]

levelSelectButtons :: LevelSet -> [Widget LevelSelectWidget]
levelSelectButtons levelSet = flip map (zip [0..] (levelSet^.lsLevels)) $ \((idx :: Integer), path) ->
	let (r, c) = join (***) fromIntegral $ idx `divMod` 5 in
	ButtonWidget $ Button { _buttonWid = LevelButton path, _buttonWparent = LevelsVstack,
		_buttonText = "Level", _buttonRect = Just $ Rect (Vec2 (-0.165 + c*0.070) (0.160 - r*0.100)) (Vec2 (-0.115 + c*0.070) (0.240 - r*0.100)) }

--makeClassyPrisms ''LevelSelectWidget