Haskell coding challenge
As the final part of this blogging series I will put myself to the test using a coding challenge. I am really glad that I have been prepared to do this challenge.
Give me six hours to chop down a tree and I will spend the first four sharpening the axe.
- Abraham Lincoln
Create tic-tac-toe in Haskell with a board size of 3.
I think this challenge is great to do because of the user input that needs to be processed into the game, which has been a challenge to do with Haskell.
The first thing we need to do in order to achieve this challenge is defining some types:
data Move = O | X
deriving (Eq, Show, Enum, Ord)
type Position = (Char, Int)
data BoardMove = BoardMove
{ bMove :: Maybe Move, bPos :: Position }
deriving (Eq, Show)
type Board = [BoardMove]
type InvalidMove = String
To start the game we will make a main method like this:
main :: IO ()
main = do
putStrLn "Starting game..."
putStrLn "Type quit to exit the game."
let newBoard = empty 3
in do (putStrLn . (\s->"\n"++s++"\n") . printBoard) newBoard
gameExecution Nothing newBoard
After that we code the game to make it work:
coord = (['A'..], [1..])empty :: Int -> Board
empty size = do
x <- take size (fst coord)
y <- take size (snd coord)
return $ BoardMove Nothing (x,y)printBoard :: Board -> String
printBoard b = intercalate "\n" $
map (\row-> [(fst . bPos) (row !! 0)] ++ "] | " ++
(intercalate " | "
$ map (\bm-> maybe " " show $ bMove bm) row)
++ " |")
(cut 3 b)cut :: Int -> [a] -> [[a]]
cut n [] = []
cut n xs = take n xs : cut n (drop n xs)gameExecution prevMove board = do
let currPlayer = maybe X (\(BoardMove mv _) ->
case mv of
Just X -> O
Just O -> X) prevMove
putStr $ "Player '" ++ (show currPlayer) ++ "': "
hFlush stdout
playerMove <- getLine
case (playerMove, (map toUpper playerMove) `elem` allCoord) of
("quit", _) ->
putStrLn "Thanks for playing, come again!"
(_, False) -> do
putStrLn $ "Possible options: " ++ intercalate ", " allCoord
gameExecution prevMove board
otherwise -> do
let pos = (toUpper $ playerMove !! 0,
read [(playerMove !! 1)] :: Int)
currMove = BoardMove (Just currPlayer) pos
currBoard = move currMove board
either putStrLn (putStrLn . (\s->"\n"++s++"\n") . printBoard) currBoard
case currBoard of
Right r -> if win currMove r
then do putStrLn $ "Player '"
++ (show currPlayer) ++"' wins!"
main
else if draw currMove r
then do putStrLn $ "It's a draw!"
main
else gameExecution (Just currMove) r
Left err -> gameExecution prevMove board
where allCoord = [[x] ++ show y | x <- take 3 (fst coord),
y <- take 3 (snd coord)]move :: BoardMove -> Board -> Either InvalidMove Board
move (BoardMove _ (c,r)) [] =
Left $ "Could not make the move to given position " ++ [c] ++ (show r)
move bm@(BoardMove nmov npos) (x:xs)
| findMove x = Right $ bm:xs
| otherwise =
case move bm xs of
Right r -> Right $ x:r
err -> err
where findMove (BoardMove m p) =
p == npos && isNothing m && nmov /= Nothingdraw :: BoardMove -> Board -> Bool
draw bm b = not (any (isNothing . bMove) b)
&& not (win bm b)win :: BoardMove -> Board -> Bool
win (BoardMove Nothing _) _ = False
win (BoardMove m (c,r)) b = row || col || diag' cb || diag' (reverse cb)
where row = length
(filter (\(BoardMove m2 (_,r2)) ->
m2 == m && r2 == r) b) == 3
col = length
(filter (\(BoardMove m2 (c2,_)) ->
m2 == m && c2 == c) b) == 3
diag' xss = all (\(BoardMove m2 _) ->
m2 == m) $ diag xss
cb = cut 3 bdiag :: [[a]] -> [a]
diag xss = [xss !! n !! n | n <- [0 .. length xss - 1]]
And now we will look at the results. Getting a draw:
Starting game...
Type quit to exit the game.A] | | | |
B] | | | |
C] | | | |Player 'X': A1A] | X | | |
B] | | | |
C] | | | |Player 'O': A2A] | X | O | |
B] | | | |
C] | | | |Player 'X': C3A] | X | O | |
B] | | | |
C] | | | X |Player 'O': B2A] | X | O | |
B] | | O | |
C] | | | X |Player 'X': C2A] | X | O | |
B] | | O | |
C] | | X | X |Player 'O': C1A] | X | O | |
B] | | O | |
C] | O | X | X |Player 'X': A3A] | X | O | X |
B] | | O | |
C] | O | X | X |Player 'O': B3A] | X | O | X |
B] | | O | O |
C] | O | X | X |Player 'X': B1A] | X | O | X |
B] | X | O | O |
C] | O | X | X |It's a draw!
Winning:
Starting game...
Type quit to exit the game.A] | | | |
B] | | | |
C] | | | |Player 'X': C3A] | | | |
B] | | | |
C] | | | X |Player 'O': A1A] | O | | |
B] | | | |
C] | | | X |Player 'X': C1A] | O | | |
B] | | | |
C] | X | | X |Player 'O': C2A] | O | | |
B] | | | |
C] | X | O | X |Player 'X': A3A] | O | | X |
B] | | | |
C] | X | O | X |Player 'O': B2A] | O | | X |
B] | | O | |
C] | X | O | X |Player 'X': B3A] | O | | X |
B] | | O | X |
C] | X | O | X |Player 'X' wins!
Wrapping up learning the language
I feel like Haskell is a really hard language to learn if you only know Java (and C#), PHP and just a little more than the basics of Javascript. With a lot of help from the internet and the seven languages in seven weeks book I managed to make something out of learning this language, but I am sure I have still a lot to learn about it. It was a fun time to do programming with Haskell, because of the functional paradigm and the logical outputs. (once you get to know what the functions are supposed to do)
I will definitely keep on learning this language deeper to fully understand it and I will start to learn the other languages that are available in this book.
Originally published at gist.github.com.