Guess a random number
This program started as an experiment in how to work with a random number generator with a known seed, so that results would be reproducible. The seed had to be either user-specified or itself randomly generated. Along the way, it became a game. At this point, it also demonstrates simple interaction with the environment (prompting users, getting command-line arguments, exiting explicitely, etc).
There is nothing fancy or mind-blowing about it; it's my first Haskell program, and I just hope it can help out other newbies. Comments, criticism, and rewrites are welcome. Thanks to #haskell for the advice they've given.
{- A simple 'guess the random number' game: - this demonstrates a use of I/O and, - more importantly, random numbers in Haskell. -} import Char import Data.Maybe import System.Environment import System.Exit import Random maxNum = 100 main :: IO () main = do args <- getArgs verifyArgsOrQuit args seed <- getSeed args showSeed seed playGame $ getRandomGen seed putStrLn "Game over" -- create a random generator with the specified seed value getRandomGen :: Int -> StdGen getRandomGen seed = mkStdGen seed -- If a seed is specified, use it; otherwise, pick a random one -- This is a little ugly: a seed is initially in the form ["123"], -- which is how it's represented as an argument to the program getSeed :: [String] -> IO Int getSeed [] = getRandomSeed getSeed (x:_) = return $ read x -- Use the pre-seeded random generator to get a random seed -- for another random generator if none was specified by the user. -- This is needed as I couldn't find a way to get the seed -- out of an existing random generator (such as the system one), -- yet I needed to be able to tell the user what the seed was, -- so that the game would be repeatable. getRandomSeed :: IO Int getRandomSeed = do randomSrc <- getStdGen return $ fst $ Random.random $ randomSrc -- A top-level wrapper for actually playing the game. playGame :: StdGen -> IO () playGame randomGen = do putStrLn $ "\nGuess the number (between 0 and " ++ (show (maxNum - 1)) ++ ")" let (rawTargetNum, nextGen) = next randomGen let target = rawTargetNum `mod` maxNum guessFor target 0 showAnswer target again <- playAgain if again then playGame nextGen else quitGame -- guessFor handles all of the actual guesses during a game. guessFor :: Int -> Int -> IO () guessFor target attempts = do putStr "Current guess? " guess <- getNum "\nCurrent guess? " if target == guess then guessCorrect $ attempts + 1 else guessWrong target attempts guess guessCorrect :: Int -> IO () guessCorrect numTries = do putStrLn $ "You won in " ++ show numTries ++ " guesses." guessWrong :: Int -> Int -> Int -> IO () guessWrong target attempts guess = do if target > guess then putStrLn "Too Low" else putStrLn "Too high" guessFor target $ attempts + 1 -- The rest of the code is I/O oriented: getting user input, -- and small wrappers to display stuff -- Prompt until a valid Y / N (case-insensitive) is read, and return it. getYN :: String -> IO Char getYN promptAgain = getFromStdin promptAgain getChar (`elem` "yYnN") toUpper -- Prompt until a valid number is read, and return it getNum :: String -> IO Int getNum promptAgain = getFromStdin promptAgain getLine isNum read -- This contains the logic common to getNum and getYN; -- it repeatedly prompts until input matching some criteria -- is given, transforms that input, and returns it getFromStdin :: String -> (IO a) -> (a -> Bool) -> (a -> b) -> IO b getFromStdin promptAgain inputF isOk transformOk = do input <- inputF if isOk input then return $ transformOk input else do putStr promptAgain getFromStdin promptAgain inputF isOk transformOk showSeed :: Int -> IO () showSeed seed = putStrLn $ "The random seed is " ++ show seed showAnswer :: Int -> IO () showAnswer answer = putStrLn $ "The answer was " ++ show answer -- Ask if the user wants to play again; -- getYN always returns an uppercase letter, so the check is sufficient playAgain :: IO Bool playAgain = do putStr "Play again? " again <- getYN "\nPlay again? " return $ again == 'Y' quitGame :: IO () quitGame = do putStrLn "\nEnough already." exitWith ExitSuccess -- Argument verification code verifyArgsOrQuit :: [String] -> IO () verifyArgsOrQuit args = if verifyArgs args then putStrLn "args ok!" else exitWithBadArgs exitWithBadArgs :: IO () exitWithBadArgs = do progName <- getProgName putStrLn $ "Use: " ++ progName ++ " [optional random seed]" exitWith $ ExitFailure 1 -- Legitimate arguments are none, or a string representing -- a random seed. Nothing else is accepted. verifyArgs :: [String] -> Bool verifyArgs [] = True verifyArgs (x:xs) = null xs && isNum x -- Verify that input is a number. This approach was chosen as read raises an -- exception if it can't parse its input. This approach has the benefit -- of being short, yet sufficient to allow the use of read on anything verified -- with it, without having to deal with exceptions. isNum :: String -> Bool isnum [] = False isNum (x:xs) = all isDigit xs && (x == '-' || isDigit x)
