Reputation: 824
I'm trying to implement the game on which i can move different blocks on the board. I've set the conditions for moving and an actual moving of the blocks, now what i tried to add was a recursive loop on user input, which would ideally solve the game.
That's what i've written:
userInput tableArr = do
str <- getLine
if str == "help"
then printHelp tableArr
else if str == "gg"
then putStrLn "Game over, you lose"
else if length str == 3
then isCorrectInput str tableArr
else if str == "table"
then let printer = printTable tableArr in userInput tableArr
else displayErrorMsg tableArr
isCorrectInput :: String -> [[Char]] -> [[Char]]
isCorrectInput str tableArr = do
let val = filter (not . isSpace) str
let move = val !! 1
let number = digitToInt(val !! 0)
let posArr = findBlock (myElem number) tableArr
if length val == 2
then (if move == 'R'
then let newArr = moveRIGHT tableArr number posArr in userInput newArr
else if move == 'L'
then let newArr = moveLEFT tableArr number posArr in userInput newArr
else if move == 'U'
then let newArr = moveUP tableArr number posArr in userInput newArr
else if move == 'D'
then let newArr = moveDOWN tableArr number posArr in userInput newArr
else displayErrorMsg tableArr)
else displayErrorMsg tableArr
displayErrorMsg tableArr = do
putStr "Wrong input, type again: "
userInput tableArr
Now a-bit clarification on what's happening here.
Function userInput
takes in a [[Char]]
(i have not set the signature cause i got confused on the return type of this thing). Now there's several conditions for the user input, but the error is thrown in isCorrectInput
. (Also is let printer = printTable tableArr in userInput tableArr
written correctly? I didn't want to add recursive end to function printTable which basically contains only putStrLn
s to print out my game board).
Now let's take a quick look at isCorrectInput
. This evaluates the user input abit more accurately and takes an action based on the user input (Usually the user input is expected in manner "4 R" which means basically block with number 4 to right. Anyways if one of the moving conditions are matched then the move function is called, added to the new variable and then the function userInput
is called out recursively once again.
The function displayErrorMsg
is just to display message and call-out recursion again.
Now the error that i'm getting is:
Couldn't match expected type `IO ()' with actual type `[[Char]]'
In the expression: isCorrectInput str tableArr
In the expression:
if length str == 3 then
isCorrectInput str tableArr
else
if str == "table" then
let printer = ... in userInput tableArr
else
displayErrorMsg tableArr
In the expression:
if str == "gg" then
putStrLn "Game over, you lose"
else
if length str == 3 then
isCorrectInput str tableArr
else
if str == "table" then
let ... in userInput tableArr
else
displayErrorMsg tableArr
I hope that my explanation was clear enough as i'm not a native English speaker and one of you could show what's wrong with my code.
Thank you :)
Upvotes: 0
Views: 257
Reputation: 15959
Your code is messy - on more than one level, so bear with me while I try to tidy it up and help you understand this error and most importantly avoid it in the future.
TL,DR for you claim that isCorrectInput :: String -> [[Char]] -> [[Char]]
in the type signature but all the functions at the end of your abundant ifs are of type IO ()
so the signature should be isCorrectInput :: String -> [[Char]] -> IO ()
probably (for there are some functions without implementation here).
But first of all try to work with the following principles
IO ()
where possible and stick to "pure" functionsif
sif you cannot figure out the signature of a code piece - use ghci
Prelude> :type functionnamewhosesignatureidontknow
will help you
Now to your code - I'll update this section as I get more on with it
I try to start with a skeleton of functions just with a type signature and no implementation
Main.hs
type GameBoard = [String]
data Direction = L|R|U|D deriving (Eq,Show)
type Move = (Int,Direction)
mainLoop :: IO ()
-- checks for abort condition and gives helpful information,
-- while playing the game
mainLoop = undefined
parseMove :: GameBoard -> String -> Maybe Move
-- checks if a given (pure) input string is a valid move for
-- a given game state, for now I use Maybe to indicate all versions of invalid
-- input
parseMove = undefined
help :: IO ()
-- prints the rules of the game
help = undefined
printErr :: String -> IO ()
printErr = undefined
now this should compile - let's fill up the mainLoop
- oh I noticed we want to use the current GameBoard in that one so add it as a parameter - note that there are techniques to make this a bit more convenient (notably the so called
State` monad)
mainLoop :: GameBoard -> IO ()
mainLoop gb = do
str <- getLine
case str of "help" -> do help
mainLoop gb
"gg" -> putStrLn "Game Over"
-- "table" I omit for sake of shortness
_ -> do let nextMove = parseMove gb str
mainLoop (nextBoard gb nextMove)
nextBoard :: GameBoard -> Move -> GameBoard
nextBoard = undefined -- try to implement this one on your own
Ooops there's an error in my code - but hey the compiler told us nextMove is Maybe Move
where nextBoard
expects a Move
so tidy that up quickly
_ -> case parseMove gb str
of Just nextMove -> mainLoop (nextBoard gb nextMove)
_ -> do putStrLn "incorrect move - please repeat"
mainLoop gb
next challenge is to tidy up is parsing the input, but first I redefine my GameBoard
, knowing that haskell is a statically typed language I want to make use of that and actually use this to my advantage
type Tile = Empty | Movable | Fixed | Player deriving (Eq)
instance Show Tile where
-- I usally use my favourite unicode box characters for games like this one
show Empty = " "
show Movable = "0"
show Fixed = "#"
show Player = "@"
type GameBoard = [[Tile]]
first I will split the String
at the ' ' but how if I don't know the name of that function?
=> use HOOGLE https://www.haskell.org/hoogle/?hoogle=%28a+-%3E+Bool%29+-%3E+%5Ba%5D+-%3E+%28%5Ba%5D%2C%5Ba%5D%29
and using ghci for the two candidates in Prelude
we get break
as the desired one
parseMove :: GameBoard -> String -> Maybe Move
-- note despite us changing what Gameboard is this signature has not changed!
-- first of all I assume that number and dir are separated by a space
-- watch out there is still a space at _dir
-- and both tuple entries are still Strings
parseMove gb = do let (_n, _dir) = break (== ' ') str
n <- parseInt n
dir <- parseDir _dir
if canPlayerMove gb (n,dir) then return (n,dir)
else Nothing
where parseInt :: String -> Maybe Int
parseInt s = if all isDigit s -- you might import or define `isDigit` yourself
then Just (read s)
else Nothing
parseDir :: String -> Maybe Direction
parseDir "L" = Just L
parseDir "R" = Just R
-- ..
parseDir _ = Nothing
canPlayerMove :: GameBoard -> Move -> Bool
canPlayerMove = undefined -- here you take care that the Game logic is respected
I hope you get a bit of a picture how to tidy up code. Also note that this code is by far the best, I hope I wrote it understandably
Btw. that do
.. <-
.. return
in parseMove
is a neat trick when working with Maybe
the right arrow takes a Maybe value
on the right hand side and pulls out a value
if it is Just x
, otherwise the whole computation below that point is discarded and Nothing
is returned.
Upvotes: 3
Reputation: 3218
Your problem is that the function isCorrectInput
isn't in IO
monad. Look at its type:
isCorrectInput :: String -> [[Char]] -> [[Char]]
and the error message
Couldn't match expected type `IO ()' with actual type `[[Char]]'
In the expression: isCorrectInput str tableArr
Since your is monadic (because of the do
used) it is assumed that it should return a monadic result. Since it appears to be using the IO
monad, you should correct its type signature to (note that String = [Char]):
isCorrectInput :: String -> [String] -> IO [String]
and each expression that represents a result in your function should be a monadic expression or use a return
in order to put the result in a given monad. So to fix this error, just add return
:
if length str == 3 then
return (isCorrectInput str tableArr)
else ...
My suggestion is to first understand Haskell IO and Monads. The Chapters 9 and 12 of Learn you a Haskell provide the necessary background.
Upvotes: 2