Reputation: 902
I am trying to write a simple implementation of the game Hangman in Haskell and one problem I have is keeping the state of the game.
A simplified / abstract version of my problem looks like this:
advanceState :: Char -> [Char] -> [Char]
advanceState = (:)
My question seems simple but I can't figure out the answer: How can I invoke this function after each character supplied via the standard input and how can I terminate the loop once a certain set of characters was read?
Note that I have also tried versions with State [Char] a
and StateT [Char] IO a
to no avail.
Upvotes: 2
Views: 224
Reputation: 2076
Provided that don't know how do you to keep the state. Here just provide a simple example show how to use StateT
with reading characters from standard input.
Firstly, to define the advanceState
function as:
advanceState::StateT [Char] IO [Char]
advanceState = StateT readInput where
readInput cs = do c <- getChar
let newState = (c:cs)
return (newState, newState)
The function embedded in StateT
is readInput
, it has type:
[Char]->IO ([Char], [Char])
The first element of pair ([Char], [Char])
is input that what we want, and the second element of that is the "state" will pass to readInput
whenever advanceState
is called again.
Now, below is an example show how to use advanceState
:
getChar10::StateT [Char] IO [Char]
getChar10 = do s <- advanceState
if (length s) <= 10
then getChar10
else do put []
return (reverse s)
This example show how to get 10 character from standard input when the user press enter, note that put []
reset the state so that next call of advanceState
will not has the previous input.
Finally, we can play around with them:
playState::IO ()
playState = print =<< evalStateT loop [] where
loop = do s1 <- getChar10
s2 <- getChar10
return (s1 ++ "::" ++ s2)
The playState
function will print first 20 characters (included new line character \n, \r) whatever how many characters user has inputted before press enter. If input not enough 20 characters, playState
will request user to input more characters.
Upvotes: 1
Reputation: 9179
A simple solution to this problem which doesn't involve using monads besides IO
is to use inner loop
function which keeps its state as an argument. I'm going to provide an artificial example below:
myProgram :: IO [Char]
myProgram = loop []
where
loop :: [Char] -> IO [Char]
loop state = do
input <- getLine
case input of
"A" -> do
putStrLn "Advancing state with A"
loop ('A' : state)
"B" -> do
putStrLn "State didn't change"
loop state
_ -> do
putStrLn "Finishing game"
pure state -- recursion ends here
So a combination of recursion and pattern-matching allows you to change your state depending on your input. So when you call loop
next time, it's given a possible new state
.
Upvotes: 2