-- | Contains the main functions for interfacing with the engine. -- This can be thought of Helm's own Prelude. module Helm ( -- * Types Cmd(..) , Engine , GameConfig(..) , Graphics(..) , Image , Sub(..) -- * Engine , run ) where import Control.Exception (finally) import Control.Monad (foldM, void) import Control.Monad.Trans.State.Lazy (evalStateT) import FRP.Elerea.Param (start, embed) import Helm.Asset (Image) import Helm.Engine (Cmd(..), Sub(..), GameConfig(..), Engine(..)) import Helm.Graphics -- | A data structure describing a game's state (that is running under an engine). data Game e m a = Game { gameConfig :: GameConfig e m a -- ^ The configuration of the game, passed by a user. , gameModel :: m -- ^ The current game model state. , actionSmp :: e -> IO [a] -- ^ A feedable monad that returns actions from mapped subscriptions. } -- | Prepare the game state from an engine and some game configuration. prepare :: Engine e => e -> GameConfig e m a -> IO (Game e m a) prepare engine config = do {- The call to 'embed' here is a little bit hacky, but seems necessary to get this working. This is because 'start' actually computes the signal gen passed to it, and all of our signal gens try to fetch the 'input' value within the top layer signal gen (rather than in the contained signal). But we haven't sampled with the input value yet, so it'll be undefined unless we 'embed'. -} smp <- start $ embed (return engine) gen return Game { gameConfig = config , gameModel = fst initialFn , actionSmp = smp } where GameConfig { initialFn, subscriptionsFn = Sub gen } = config -- | Runs a Helm game using an engine and some configuration for a game. -- An engine should first be initialized separately to Helm, and then passed -- to this function. Helm is written this way so that library users can -- choose what backend engine they want to use (and hence Helm is engine-agnostic). -- -- The best engine to get started with is the SDL implementation of Helm, -- which is currently bundled with the engine (although it will eventually be moved -- to its own package). See 'Helm.Engine.SDL.startup' for how -- to startup the SDL engine, which can then be run by this function. run :: Engine e => e -> GameConfig e m a -> IO () run engine config@GameConfig { initialFn } = void $ (prepare engine config >>= stepInitial >>= step engine) `finally` cleanup engine where Cmd monad = snd initialFn stepInitial game@Game { gameModel } = do actions <- evalStateT monad engine model <- foldM (stepModel engine game) gameModel actions return game { gameModel = model } -- | Step the game state forward. step :: Engine e => e -> Game e m a -> IO () step engine game = do mayhaps <- tick engine case mayhaps of Nothing -> return () Just sunkEngine -> do actions <- actionSmp sunkEngine model <- foldM (stepModel sunkEngine game) gameModel actions render sunkEngine $ viewFn model step sunkEngine $ game { gameModel = model } where Game { actionSmp, gameModel, gameConfig = GameConfig { viewFn } } = game -- | Step the game model forward with a specific game action. stepModel :: Engine e => e -> Game e m a -> m -> a -> IO m stepModel engine game model action = evalStateT monad engine >>= foldM (stepModel engine game) upModel where Game { gameConfig = GameConfig { updateFn } } = game (upModel, Cmd monad) = updateFn model action