[Haskell-cafe] Re: Simple game: a monad for each player
Heinrich Apfelmus
apfelmus at quantentunnel.de
Wed Apr 14 06:00:08 EDT 2010
Bertram Felgenhauer wrote:
> Yves Parès wrote:
>>
>> I answered my own question by reading this monad-prompt example:
>> http://paste.lisp.org/display/53766
>>
>> But one issue remains: those examples show how to make play EITHER a human
>> or an AI. I don't see how to make a human player and an AI play SEQUENTIALLY
>> (to a TicTacToe, for instance).
>
> A useful idea is to turn the construction upside-down - rather than
> implementing the game logic using MonadPrompt (or operational),
> implement the players in such a monad.
>
> A sketch:
>
> {-# LANGUAGE GADTs, EmptyDataDecls #-}
> import Control.Monad.Prompt hiding (Lift)
>
> data Game -- game state
> data Move -- move
>
> data Request m a where
> Board :: Request m Game
> MakeMove :: Move -> Request m ()
> Lift :: m a -> Request m a
>
> type Player m a = Prompt (Request m) a
Just a small simplification: it is not necessary to implement the Lift
constructor by hand, the operational library implements a generic
monad transformer. The following will do:
import Control.Monad.Operational
data Request a where
Board :: Request Game
MakeMove :: Move -> Request ()
type Player m a = ProgramT Request m a
game :: Monad m => Player m () -> Player m () -> m ()
game p1 p2 = do
g <- initGame
eval' g p1 p2
where
eval' g p1 p2 = viewT p1 >>= \p1' -> eval g p1' p2
eval :: Monad m => Game ->
-> Prompt Request m ()
-> Player m ()
-> m ()
eval g (Return _) _ = return ()
eval g (Board :>>= p1) p2 = eval' g (p1 g) p2
eval g (MakeMove mv :>>= p1) p2 =
makeMove mv g >>= \g -> eval' g p2 (p1 ())
This way, you are guaranteed not to break the lifting laws, too.
> What have we achieved? Both players still can only access functions from
> whatever monad m turns out to be. But now each strategy can pile its own
> custom monad stack on the Player m monad! And of course, the use of
> the m Monad is completely optional.
Of course, the custom monad stack has to provide a projection back to
the Player m a type
runMyStackT :: MyStackT (Player m) a -> Player m a
Fortunately, you can't expect anything better anyway! After all, if the
game function were to accept say LogicT (Player m) as well, this
would mean that the player or AI could interleave the game arbitrarily,
clearly not a good idea.
> Mapping between various 'm' monads may also be useful:
>
> mapPlayerM :: forall m1 m2 a . (forall a . m1 a -> m2 a)
> -> Player m1 a -> Player m2 a
> mapPlayerM m1m2 pl = runPromptC return handle pl where
> handle :: Request m1 x -> (x -> Player m2 a) -> Player m2 a
> handle (Lift a) x = prompt (Lift (m1m2 a)) >>= x
> handle (MakeMove mv) x = prompt (MakeMove mv) >>= x
> handle (Board) x = prompt (Board) >>= x
>
> This could be used to lock out the AI player from using IO, say.
Shouldn't this actually be a member of the MonadTrans class?
mapMonad :: (Monad m1, Monad m2, MonadTrans t) =>
(forall a . m1 a -> m2 a) -> t m1 a -> t m2 a
?
Regards,
Heinrich Apfelmus
--
http://apfelmus.nfshost.com
More information about the Haskell-Cafe
mailing list