[Haskell-cafe] Re: Fwd: Re: Simple game: a monad for each player
Limestraël
limestrael at gmail.com
Thu Apr 15 05:41:49 EDT 2010
Ok, but there is no function such as mapMonad in the operational package?
By the way, I noticed that ProgramT is not automatically made instance of
MonadIO when possible. It could be:
instance (MonadIO m) => MonadIO (ProgramT r m) where
liftIO = lift . liftIO
Is that intentional?
( In fact, I think it's a slip in the mtl package itself, since every
instance of MonadTrans can be declared instance of MonadIO:
instance (MonadTrans t, MonadIO m) => MonadIO (t m) where
liftIO = lift . liftIO
)
By the way, I finally managed to use operational to modify my TicTacToe
game.
(One shot, by the way, I had no bugs ^^. Very nice when it happens...)
Human player and AI are working. I'm currently fixing the Network player.
If you are interested, I could upload my code (it can be another example of
how to use the operational package).
In the end, I used a mix of your solution and my former one.
I have a Request datatype:
data Request a where
GetGrid :: Request Grid
TurnDone :: (Grid, Maybe GridResult) -> Request ()
GetResult :: Request (Maybe GridResult)
(Grid is what you called Board, GridResult is a type which indicates if
someone wins or if there is a draw)
The game monad is PlayerMonadT, and is a newtype:
newtype PlayerMonadT m a = PMT (ProgramT Request m a)
deriving (Functor, Monad, MonadTrans)
I still have a datatype Player, which contains functions: (I tried to use
classes, but it was more complicated)
data Player m m' = Player {
-- | Gets the mark (Cross or Circle) of the player
plMark :: Mark,
-- | Called when the player must play
plTurn :: Grid -> m Pos,
-- | Called when player tries to play at a forbidden position
plForbidden :: Pos -> m (),
-- | Called when game has ended.
plGameOver :: GridResult -> m (),
-- | Used to reach PlayerMonad in the monad stack
plLift :: forall a. PlayerMonadT m' a -> m a,
-- | Used to run the monad stack the player runs in
plRun :: forall a. m a -> PlayerMonadT m' a
}
*m* is the monad stack the player runs in. It must be able to run it, by
providing a plRun function.
*m'* is the top monad, which can't be run (IO for human, any monad for AI,
etc.)
The alteration done to this type is the addition of the plLift and plRun
functions. Those are the functions you, Heinrich, and Bertram told me about.
Then, *all* the players play according to this logic:
playerLogic :: (Monad m) => Player m m' -> m ()
playerLogic pl = do
let toProg = plLift pl . PMT . singleton
grid <- toProg GetGrid
pos <- plTurn pl grid
case checkCell grid (plMark pl) pos of
Nothing -> do -- The cell was already filled in
plForbidden pl pos -- We signal the error
playerLogic pl -- We start the turn again
Just newGridAndResult -> do
-- The cell has been successfully marked, so we got
a new grid
toProg $ TurnDone newGridAndResult
-- At this point, the interpreter will switch to
the other player
mbResult <- toProg $ GetResult
-- This player is back, and wants to know what's
new
case mbResult of
Nothing -> playerLogic pl
Just res -> plGameOver pl res
We can then run this function with the player custom stack thanks to the
runPlayer function:
runPlayer :: (Monad m) => Player m m' -> PlayerMonadT m' ()
runPlayer pl = plRun pl $ playerLogic pl
And finally, the interpreter:
doGame :: (Monad m) => Grid -> [PlayerMonadT m ()] -> m Grid
doGame initGrid players =
mapM unwrap players >>= flip evalStateT (initGrid, Nothing) . eval
where
unwrap (PMT pl) = viewT pl
eval :: (Monad m) => [PromptT Request m ()] -> StateT (Grid, Maybe
GridResult) m Grid
eval [] = liftM fst get
eval ((Return _) : pls) = eval pls
eval ((GetGrid :>>= pl) : pls) = do
(grid, _) <- get
p <- lift . viewT $ pl grid
eval $ p : pls
eval ((TurnDone (newGrid, mbResult) :>>= pl) : pls) = do
put (newGrid, mbResult)
p <- lift . viewT $ pl ()
eval $ pls ++ [p]
eval ((GetResult :>>= pl) : pls) = do
(_, mbResult) <- get
p <- lift . viewT $ pl mbResult
eval $ p : pls
The game can be launched by doing for example:
let pl1 = humanPlayer Cross
let pl2 = artificialPlayer Circle levelOfDifficulty
doGame blankGrid [runPlayer pl1, runPlayer pl2]
I did it!
2010/4/15 Heinrich Apfelmus <apfelmus at quantentunnel.de>
> Limestraël wrote:
> > Okay, I start to understand better...
> >
> > Just, Heinrich, how would implement the mapMonad function in terms of the
> > operational package?
> > You just shown the signature.
>
> Ah, that has to be implemented by the library, the user cannot implement
> this. Internally, the code would be as Bertram suggests:
>
> mapMonad :: (Monad m1, Monad m2)
> => (forall a . m1 a -> m2 a)
> -> ProgramT instr m1 a -> ProgramT instr m2 a
> mapMonad f (Lift m1) = Lift (f m1)
> mapMonad f (Bind m k) = Bind (mapMonad f m) (mapMonad f . k)
> mapMonad f (Instr i) = Instr i
>
> I was musing that every instance of MonadTrans should implement this
> function.
>
> Also note that there's a precondition on f , namely it has to respect
> the monad laws:
>
> f (m >>= k) = f m >>= f . k
> f return = return
>
> For instance,
>
> f :: Identity a -> IO a
> f x = launchMissiles >> return (runIdentity x)
>
> violates this condition.
>
>
> Regards,
> Heinrich Apfelmus
>
> --
> http://apfelmus.nfshost.com
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20100415/c3e3ce73/attachment.html
More information about the Haskell-Cafe
mailing list