{-# LANGUAGE TypeSynonymInstances #-}

module Problem1c where

import Control.Monad.State
import Control.Monad.Error

class Monad m => GameMonad m where
  extraLife  :: m ()
  getLives   :: m Int
  checkPoint :: m a -> m a
  die        :: m a

-- We don't need any information in the errors. We could use
-- the Maybe monad transformer [1] instead (credits to Ann
-- Lillieström for that solution).
type GameT m = ErrorT Err (StateT Lives m)

type Err   = ()
type Lives = Int

-- There's no Error instance for ().
instance Error () where
  noMsg    = ()
  strMsg _ = ()

instance Monad m => GameMonad (GameT m) where
  extraLife    = modify succ
  getLives     = get
  die          = modify pred >> throwError ()
  checkPoint m = m `catchError` \e -> do
    n <- getLives
    if n > 0 then checkPoint m
             else throwError e

runGameT :: Monad m => GameT m a -> Int -> m (Maybe (a, Int))
runGameT m n = do
  (r, n) <- runStateT (runErrorT m) n
  case r of
    Left () -> return Nothing
    Right x -> return $ Just (x, n)

-- [1] http://www.haskell.org/haskellwiki/New_monads/MaybeT