<div>Ask and ye shall receive.&nbsp; A simple guess-a-number game in MonadPrompt follows.</div>
<div>&nbsp;</div>
<div>But before I get to that, I have some comments:</div>
<div>&nbsp;</div>
<div>
<div>Serializing&nbsp;the state at arbitrary places&nbsp;is hard; the Prompt contains a continuation function so unless you have a way to serialize closures it seems like you lose.&nbsp; But if you have &quot;safe points&quot; during the execution at which you know all relevant state is inside your &quot;game state&quot;, you can save there by serializing the state and providing a way to restart the computation at those safe points.
</div>
<div>&nbsp;</div>
<div>I haven&#39;t looked at MACID at all; what&#39;s that?</div></div>
<div>&nbsp;</div>
<div>&gt; {-# LANGUAGE GADTs, RankNTypes #-}<br>&gt; module Main where<br>&gt; import Prompt<br>&gt; import Control.Monad.State<br>&gt; import System.Random (randomRIO)<br>&gt; import System.IO<br>&gt; import Control.Exception
 (assert)</div>
<div>&nbsp;</div>
<div>Minimalist &quot;functional references&quot; implementation.<br>In particular, for this example, we skip the really interesting thing: composability.</div>
<div>&nbsp;</div>
<div>See <a href="http://luqui.org/blog/archives/2007/08/05/">http://luqui.org/blog/archives/2007/08/05/</a> for a real implementation.</div>
<div>&nbsp;</div>
<div>&gt; data FRef s a = FRef<br>&gt;&nbsp;&nbsp; { frGet :: s -&gt; a<br>&gt;&nbsp;&nbsp; , frSet :: a -&gt; s -&gt; s<br>&gt;&nbsp;&nbsp; }</div>
<div>&nbsp;</div>
<div>&gt; fetch :: MonadState s m =&gt; FRef s a -&gt; m a<br>&gt; fetch ref = get &gt;&gt;= return . frGet ref</div>
<div>&nbsp;</div>
<div>&gt; infix 1 =:<br>&gt; infix 1 =&lt;&lt;:<br>&gt; (=:) :: MonadState s m =&gt; FRef s a -&gt; a -&gt; m ()<br>&gt; ref =: val = modify $ frSet ref val<br>&gt; (=&lt;&lt;:) :: MonadState s m =&gt; FRef s a -&gt; m a -&gt; m ()
<br>&gt; ref =&lt;&lt;: act = act &gt;&gt;= modify . frSet ref<br>&gt; update :: MonadState s m =&gt; FRef s a -&gt; (a -&gt; a) -&gt; m ()<br>&gt; update ref f = fetch ref &gt;&gt;= \a -&gt; ref =: f a</div>
<div>&nbsp;</div>
<div>Interactions that a user can have with the game:</div>
<div>&nbsp;</div>
<div>&gt; data GuessP a where<br>&gt;&nbsp;&nbsp;&nbsp; GetNumber :: GuessP Int<br>&gt;&nbsp;&nbsp;&nbsp; Guess :: GuessP Int<br>&gt;&nbsp;&nbsp;&nbsp; Print :: String -&gt; GuessP ()</div>
<div>&nbsp;</div>
<div>Game state.</div>
<div>&nbsp;</div>
<div>We could do this with a lot less state, but I&#39;m trying to show what&#39;s possible here.&nbsp; In fact, for this example it&#39;s probably easier to just thread the state through the program directly, but bigger games want real state, so I&#39;m showing how to do that.
</div>
<div>&nbsp;</div>
<div>&gt; data GuessS = GuessS<br>&gt;&nbsp;&nbsp; { gsNumGuesses_ :: Int<br>&gt;&nbsp;&nbsp; , gsTargetNumber_ :: Int<br>&gt;&nbsp;&nbsp; }</div>
<div>&nbsp;</div>
<div>&gt; -- a real implementation wouldn&#39;t do it this way :)<br>&gt; initialGameState :: GuessS<br>&gt; initialGameState = GuessS undefined undefined</div>
<div>&nbsp;</div>
<div>&gt; gsNumGuesses, gsTargetNumber :: FRef GuessS Int<br>&gt; gsNumGuesses&nbsp;&nbsp; = FRef gsNumGuesses_&nbsp;&nbsp; $ \a s -&gt; s { gsNumGuesses_&nbsp;&nbsp; = a }<br>&gt; gsTargetNumber = FRef gsTargetNumber_ $ \a s -&gt; s { gsTargetNumber_ = a }
</div>
<div>&nbsp;</div>
<div>Game monad with some useful helper functions</div>
<div>&nbsp;</div>
<div>&gt; type Game = StateT GuessS (Prompt GuessP)</div>
<div>&nbsp;</div>
<div>&gt; gPrint :: String -&gt; Game ()<br>&gt; gPrint = prompt . Print</div>
<div>&nbsp;</div>
<div>&gt; gPrintLn :: String -&gt; Game ()<br>&gt; gPrintLn s = gPrint (s ++ &quot;\n&quot;)</div>
<div>&nbsp;</div>
<div>Implementation of the game:</div>
<div>&nbsp;</div>
<div>&gt; gameLoop :: Game Int<br>&gt; gameLoop = do<br>&gt;&nbsp;&nbsp;&nbsp; update gsNumGuesses (+1)<br>&gt;&nbsp;&nbsp;&nbsp; guessNum &lt;- fetch gsNumGuesses<br>&gt;&nbsp;&nbsp;&nbsp; gPrint (&quot;Guess #&quot; ++ show guessNum ++ &quot;:&quot;)<br>&gt;&nbsp;&nbsp;&nbsp; guess &lt;- prompt Guess
<br>&gt;&nbsp;&nbsp;&nbsp; answer &lt;- fetch gsTargetNumber<br>&gt;<br>&gt;&nbsp;&nbsp;&nbsp; if guess == answer<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; then do<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gPrintLn &quot;Right!&quot;<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return guessNum<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else do<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gPrintLn $ concat
<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ &quot;You guessed too &quot;<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , if guess &lt; answer then &quot;low&quot; else &quot;high&quot;<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;! Try again.&quot;<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gameLoop
</div>
<div>&nbsp;</div>
<div>&gt; game :: Game ()<br>&gt; game = do<br>&gt;&nbsp;&nbsp;&nbsp; gsNumGuesses =: 0<br>&gt;&nbsp;&nbsp;&nbsp; gsTargetNumber =&lt;&lt;: prompt GetNumber<br>&gt;&nbsp;&nbsp;&nbsp; gPrintLn &quot;I&#39;m thinking of a number.&nbsp; Try to guess it!&quot;<br>&gt;&nbsp;&nbsp;&nbsp; numGuesses &lt;- gameLoop
<br>&gt;&nbsp;&nbsp;&nbsp; gPrintLn (&quot;It took you &quot; ++ show numGuesses ++ &quot; guesses!&quot;)</div>
<div>&nbsp;</div>
<div>Simple unwrapper for StateT that launches the game.</div>
<div>&nbsp;</div>
<div>&gt; runGame :: Monad m =&gt; (forall a. GuessP a -&gt; m a) -&gt; m ()<br>&gt; runGame f = runPromptM f (evalStateT game initialGameState)<br>&nbsp;</div>
<div>Here is the magic function for interacting with the player in IO.&nbsp; Exercise for the reader: make this more robust.</div>
<div>&nbsp;</div>
<div>&gt; gameIOPrompt :: GuessP a -&gt; IO a<br>&gt; gameIOPrompt GetNumber = randomRIO (1, 100)<br>&gt; gameIOPrompt (Print s) = putStr s<br>&gt; gameIOPrompt Guess&nbsp;&nbsp;&nbsp;&nbsp; = fmap read getLine</div>
<div>&nbsp;</div>
<div>If you wanted to add undo, all you have to do is save off the current Prompt in the middle of runPromptM; you can return to the old state at any time.</div>
<div>&nbsp;</div>
<div>&gt; gameIO :: IO ()<br>&gt; gameIO = do<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp; hSetBuffering stdout NoBuffering<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp; runGame gameIOPrompt</div>
<div>&nbsp;</div>
<div>Here&#39;s a scripted version.</div>
<div>&nbsp;</div>
<div>&gt; type GameScript = State [Int]<br>&gt; <br>&gt; scriptPrompt :: Int -&gt; GuessP a -&gt; GameScript a<br>&gt; scriptPrompt n GetNumber = return n<br>&gt; scriptPrompt _ (Print _) = return ()<br>&gt; scriptPrompt _ Guess&nbsp;&nbsp;&nbsp;&nbsp; = do
<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp; (x:xs) &lt;- get -- fails if script runs out of answers<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp; put xs<br>&gt;&nbsp;&nbsp;&nbsp;&nbsp; return x<br>&gt;<br>&gt; scriptTarget :: Int<br>&gt; scriptTarget = 23<br>&gt; scriptGuesses :: [Int]<br>&gt; scriptGuesses = [50, 25, 12, 19, 22, 24, 23]
</div>
<div>&nbsp;</div>
<div>gameScript is True if the game ran to completion successfully, and False or bottom otherwise.<br>Try adding or removing numbers from scriptGuesses above and re-running the program.</div>
<div>&nbsp;</div>
<div>&gt; gameScript :: Bool<br>&gt; gameScript = null $ execState (runGame (scriptPrompt scriptTarget)) scriptGuesses</div>
<div>&nbsp;</div>
<div>&gt; main = do<br>&gt;&nbsp;&nbsp;&nbsp; assert gameScript $ return ()<br>&gt;&nbsp;&nbsp;&nbsp; gameIO<br>&nbsp;</div>