{- |

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 Game.Deterministic.GameEngine
>
> game :: Monad m => GameEngine m [Char] [(Int, Char)]
> game = GameEngine gameActions initialState
>
> gameActions :: Monad m => GameActions m [Char] [(Int, Char)]
> 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 [Char]
> initialState = GameState ['x', 'x', 'x']
>
> -- run the game engine until a terminal state is reached
> playSimple game

-}


module Game.Deterministic.GameEngine (
    GameActions(..),
    GameState(..),
    Move(..),
    Player(..),
    GameEngine(..),
    play,
    playSimple,
    playIO
  ) where


import           Control.Monad.Identity


import           Game.Deterministic.GameEngine.GameActions
import           Game.Deterministic.GameEngine.GameState
import           Game.Deterministic.GameEngine.Move
import           Game.Deterministic.GameEngine.Player


data GameEngine m a b = GameEngine {
    gameEngineActions :: GameActions m a b,
    -- ^ Defines how the game will be played

    gameEngineState   :: GameState a
    -- ^ The current state of the game
  }
-- ^ Holds information about how the game is played, and the current state of the game.


play :: Monad m => GameEngine m a b -> m Int
-- ^ Run the provided game engine under a monadic context until a terminal state is reached.
play (GameEngine actions state) = do
  isTerm <- isTerminal actions state

  if isTerm then do
    player <- getPlayer actions state
    getScore actions state player
    else do
      nextState <- getNextState actions state
      play $ GameEngine actions nextState


playSimple :: GameEngine Identity a b -> Int
-- ^ Run the provided game engine without a context until a terminal state is reached.
playSimple = runIdentity . play


playIO :: GameEngine IO a b -> IO Int
-- ^ Run the provided game engine within an IO context until a terminal state is reached.
playIO = play


getNextState :: Monad m => GameActions m a b -> GameState a -> m (GameState a)
getNextState actions state = do
  nextMove <- getMove actions state
  getResult actions state nextMove