{- | Haskell library for creating simple deterministic games, such as tic-tac-toe. The engine requires a minimal set of actions related to the game, and then will run the game until a terminal state is reached. Simple generic example below. See the specs for a more detailed example. > import GameEngine > > game :: GameEngine Int Int > game = GameEngine gameActions initialState > > gameActions :: GameActions Int Int > gameActions = GameActions { > getPlayer = -- find the next player from a game state, > getMove = -- find a move from the game state, > getResult = -- transitions from a state to another state, > isTerminal = -- determines if the game is terminal, > getScore = -- get score from a terminal state > } > > initialState :: GameState Int > initialState = GameState 0 > > -- run the game engine until a terminal state is reached > playSimple game -} {-# LANGUAGE RankNTypes #-} module GameEngine ( Symbol, GameState(..), Player(..), Move(..), GameActions(..), GameEngine(..), play, playSimple, playIO ) where import Control.Monad.Identity import GameEngine.GameState import GameEngine.GameActions import GameEngine.Move import GameEngine.Player data GameEngine a b = GameEngine { actions :: GameActions a b, -- ^ Defines how the game will be played state :: GameState a -- ^ The current state of the game } -- ^ Holds information about how the game is played, and the current state of the game. -- TODO: provided function should be forced to be an identity only function -- forall a. a -> m a play :: Monad m => (GameState a -> m (GameState a)) -> GameEngine a b -> m Int -- ^ Run the provided game engine under a monadic context until a terminal state is reached. -- Note: provided function should act as an identity only, and should not modify the game state. play f engine | performWithState isTerminal engine = return . performWithState getScore engine $ performWithState getPlayer engine | otherwise = f (getNextState engine) >>= play f . GameEngine (actions engine) playSimple :: GameEngine a b -> Int -- ^ Run the provided game engine without a context until a terminal state is reached. playSimple = runIdentity . play Identity playIO :: (GameState a -> IO ()) -> GameEngine a b -> IO Int -- ^ Run the provided game engine within an IO context until a terminal state is reached. playIO f = play (f >> return) getNextState :: GameEngine a b -> GameState a getNextState engine = performWithState getResult engine $ performWithState getMove engine performWithState :: (GameActions a b -> GameState a -> c) -> GameEngine a b -> c performWithState f engine = f (actions engine) $ state engine