It's been a challenge, and I haven't gotten as far as I would have expected. I wish the Haskell documentation, whether online or provided with the installation, was put together better. I'd like to have ready access to a language reference, not just the auto-generated library documentation; I'd like to have lots of sample code snippets; and I'd like for it to be fully searchable.
For instance: in BASIC you can write INPUT A to read an integer from the keyboard. The closest equivalent in Haskell is read but it aborts the program with an exception if the user does not type in something that can be parsed correctly. Since I was attempting to match BASIC's behavior I needed it to be able to deal more gracefully with bad input. With Google I eventually found a thread somewhere addressing this problem.
So far my program is not ending up any smaller than the original BASIC version, which surprises me a bit. I'm having difficulty deciding the best way to structure the program. I've discovered a method that emulates BASIC's goto style pretty closely, but it doesn't seem like that's necessarily a step toward readability, because the flow of control is embedded throughout the program:
main = buyLand
buyLand =
[choose how much land to buy with stored food]
if bought land:
feedPeople
else:
sellLand
sellLand =
[choose how much land to sell for food]
feedPeople
feedPeople =
[choose how much to feed your people]
plantFields
plantFields =
[choose how many acres to sow with seed]
displayYearResults
displayYearResults =
[show what happened as a result of player's choices]
if game not over:
buyLand
I've omitted several additional control branches: If the user inputs nonsensical numbers the original program sometimes prints a huffy message and quits.
There is a basic game state that I pass through all the functions; it contains information that needs to persist from one year of game time to the next. There are additional bits of information that flow between some stages as well. For instance the choice of how many acres to sow with seed is needed in displayYearResults but not needed after that, so I've left it out of the main game state.
Simple as Hamurabi is, I found myself needing to step back and do an even simpler game first. Here's one where the computer picks a number and the player tries to guess it:
-- Computer chooses a number; player guesses what it is
-- Example of basic I/O
import Char
import Random
import IO
minNum = 1
maxNum = 1000
main = do
targetNum <- randomRIO (minNum, maxNum)
putStrLn ("I'm thinking of a number between " ++ (show minNum) ++
" and " ++ (show maxNum) ++ ". Can you guess it?")
guess 1 targetNum
guess :: Int -> Int -> IO ()
guess totalGuesses targetNum = do
putStr ("Guess " ++ (show totalGuesses) ++ ": ")
hFlush stdout
line <- getLine
case (maybeRead line) of
Nothing -> putStrLn ("Give up? The number was " ++ (show targetNum) ++ ".")
Just guessedNum ->
if targetNum == guessedNum then
putStrLn ("You guessed it in " ++ (show totalGuesses) ++ " tries!")
else do
putStrLn hint
guess (totalGuesses + 1) targetNum
where
hint = "My number is " ++ lessOrGreater ++ " than " ++ (show guessedNum) ++ "."
lessOrGreater = if targetNum < guessedNum then "less" else "greater"
maybeRead :: Read a => String -> Maybe a
maybeRead s = case reads s of
[(x, str)] | all isSpace str -> Just x
_ -> Nothing
This should give an idea of the level of verbosity that I'm contending with right now. Hopefully I can improve on this and come up with a clean way to structure the more complex program.
9 comments:
Two bits of advice:
(1) Use StateT GameState IO and/or Prompt (the MonadPrompt package on hackage.haskell.org)
(2) Build some combinators, like ths one:
until :: m (Maybe a) -> m a
until act = act >>= maybe (until act) return
which loops until you return a non-nothing value.
Here's the "guess a number game" using MonadPrompt.
http://www.mail-archive.com/haskell-cafe@haskell.org/msg34010.html
(This is impossible to resist.)
Or ... you can use the embedded BASIC package (http://augustss.blogspot.com/2009_02_01_archive.html ) Good luck with Hamurabi though...
{-# LANGUAGE ExtendedDefaultRules, OverloadedStrings #-}
import BASIC
main = runBASIC $ do
10 GOSUB 1000
20 PRINT "* Welcome to HiLo *"
30 GOSUB 1000
100 LET I := INT(100 * RND(0))
200 PRINT "Guess my number:"
210 INPUT X
220 LET S := SGN(I-X)
230 IF S <> 0 THEN 300
240 FOR X := 1 TO 5
250 PRINT X*X;" You won!"
260 NEXT X
270 STOP
300 IF S <> 1 THEN 400
310 PRINT "Your guess ";X;" is too low."
320 GOTO 200
400 PRINT "Your guess ";X;" is too high."
410 GOTO 200
1000 PRINT "*******************"
1010 RETURN
9999 END
btw: in case you don't know hoogle, I think it might be useful for you.
$ cabal install hoogle
$ hoogle "Bool -> m () -> m ()"
Control.Monad unless :: Monad m => Bool -> m () -> m ()
Control.Monad when :: Monad m => Bool -> m () -> m ()
...
$ hoogle "[Maybe a] -> [a]"
Data.Maybe catMaybes :: [Maybe a] -> [a]
...
-- http://playtechs.blogspot.com/2009/06/messing-with-haskell.html
-- Computer chooses a number; player guesses what it is
-- Example of basic I/O
import Random
import Text.Printf
import Control.Monad
minNum = 1
maxNum = 1000
main = do
targetNum <- randomRIO (minNum, maxNum)
printf "I'm thinking of a number between %d and %d. Can you guess it?\n"
minNum maxNum
play targetNum [1..]
play tn (x:xs) = do
m <- guess tn x
unless m $ play tn xs
guess :: Int -> Int -> IO Bool
guess targetnum guessnum = do
printf "Guess %d: " guessnum
num <- liftM read getLine
let r = compare targetnum num
printf "My number is %s yours.\n" (show r)
return $ r == EQ
I forgot to add, regarding the remark
"I wish the Haskell documentation, whether online or provided with the installation, was put together better. I'd like to have ready access to a language reference, not just the auto-generated library documentation; I'd like to have lots of sample code snippets; and I'd like for it to be fully searchable"
Everyone agrees this is true. Keep in mind though that Haskell-land has something better than any documentation -- IT REALLY IS TRUE -- namely the #haskell IRC on freenode. Occasionally you'll have to try a couple of times with your query, but I am forever stunned by the brilliance of the denizens and the bright spirit in which help is given to learners of all levels. It is completely unreal and unparalleled. It very much helps to begin any query with problematic code posted on http://hpaste.org/
A simple but deep point on how to structure haskell programs:
http://www.haskell.org/haskellwiki/Data_structures_not_functions
(The whole "Idioms" category of the wiki is a pretty good read).
Thank you all for the help!
The BASIC interpreter is a crazy stunt; I'm going to have to try that out. I've been reading the MonadPrompt stuff to try and understand what it brings to the table.
I have been cruising the "Idioms" section of the Haskell wiki.
I've just about finished my port of Hamurabi using the structure I outlined in my post. After I've finished that I'll try to structure it better. I'm trying to think what the key abstractable things are. Reading a number and validating it with messages is clearly one of them. I've been wondering if I could treat the input as a list of (lazily gotten) lines and if that would simplify anything.
Hello everybody! I do not know where to start but hope this site will be useful for me.
In first steps it is very nice if someone supports you, so hope to meet friendly and helpful people here. Let me know if I can help you.
Thanks in advance and good luck! :)
Post a Comment