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