As a beginner, I tried to implement TicTacToe in Haskell. How can I improve my code?
I have refactored my code: General TicTacToe in Haskell
Utils
module Data.List.Utils where
import Data.List (intersperse)
surround :: a -> [a] -> [a]
surround x ys = x : intersperse x ys ++ [x]
nth :: Int -> (a -> a) -> [a] -> [a]
nth _ _ [] = []
nth 0 f (x:xs) = f x : xs
nth n f (x:xs) = x : nth (n - 1) f xs
TicTacToe
module TicTacToe where
import Data.List (transpose)
import Data.Foldable (asum)
import Data.List.Utils (nth, surround)
data Tile = Empty | O | X deriving (Show, Eq)
showTile :: Tile -> String
showTile Empty = " "
showTile O = "O"
showTile X = "X"
type Board = [[ Tile ]]
showBoard :: Board -> String
showBoard = unlines
. surround vert
. map (concat . surround horiz . map showTile)
where vert = "+-+-+-+"
horiz = "|"
whoWon :: Board -> Maybe Tile
whoWon xs = asum . map winner $ diag : anti : rows ++ cols
where rows = xs
cols = transpose xs
diag = zipWith (!!) xs [0..]
anti = zipWith (!!) xs [2,1..]
winner [O, O, O] = Just O
winner [X, X, X] = Just X
winner _ = Nothing
fillTile :: Int -> Int -> Tile -> Board -> Board
fillTile row col = nth row . nth col . const
isOver :: Board -> Bool
isOver = all (all (/= Empty))
overMsg :: Maybe Tile -> String
overMsg (Just x) = "Winner: " ++ (showTile x)
overMsg Nothing = "Game Over! No winner."
inRange :: Int -> Bool
inRange x = x >= 0 && x <= 2
gameLoop :: Tile -> Board -> IO ()
gameLoop player board
| isOver board || winner /= Nothing = putStrLn $ overMsg winner
| otherwise = do
putStrLn $ showBoard board
putStrLn $ "Now player " ++ showTile player
putStr "Row: "
row <- fmap read getLine
putStr "Col: "
col <- fmap read getLine
if inRange row && inRange col && (board !! row !! col == Empty)
then gameLoop (if player == O then X else O) $
fillTile row col player board
else do
putStrLn "Invalid position, please try again."
gameLoop player board
where winner = whoWon board
startBoard :: Board
startBoard = replicate 3 (replicate 3 Empty)
main = gameLoop O startBoard