I decided to take a more general approach while implementing the TicTacToe game in Haskell.
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
module TicTacToe where import Data.List (transpose) import Data.Foldable (asum) import Data.List.Utils (surround, nth) data Tile = O | X | Empty deriving (Eq) instance Show Tile where show Empty = " " show O = "O" show X = "X" -- ====================== -- Board Helper Functions -- ====================== type Board = [[ Tile ]] showBoard :: Board -> String showBoard xss = unlines . surround vtx . map (concat . surround mid . map show) $ xss where mid = "|" vtx = surround '+' $ replicate (length xss) '-' whoWon :: Board -> Maybe Player whoWon xss = asum . map winner $ diag : anti : cols ++ xss where cols = transpose xss diag = zipWith (!!) xss [0..] anti = zipWith (!!) (reverse xss) [0..] winner (x:xs) = if all (==x) xs && x /= Empty then Just (toPlayer x) else Nothing getCoords :: Int -> Board -> (Int, Int) getCoords n = divMod (n - 1) . length fillTile :: Board -> Int -> Tile -> Board fillTile xss n tile = nth row (nth col (const tile)) xss where (row, col) = getCoords n xss isOver :: Board -> Bool isOver = all (notElem Empty) -- ======================== -- Player related functions -- ======================== data Player = Player1 | Player2 deriving (Eq) instance Show Player where show Player1 = "1st Player" show Player2 = "2nd Player" toPlayer :: Tile -> Player toPlayer X = Player1 toPlayer O = Player2 fromPlayer :: Player -> Tile fromPlayer Player1 = X fromPlayer Player2 = O changePlayer :: Player -> Player changePlayer Player1 = Player2 changePlayer Player2 = Player1 -- ==================== -- Game logic functions -- ==================== validateInput :: Board -> String -> Either String Int validateInput xss s = case reads s of [(n, "")] -> check n _ -> Left "Only integers allowed" where check n | n < 1 || n > length xss ^ 2 = Left "Out of range" | xss !! row !! col /= Empty = Left "Already filled" | otherwise = Right n where (row, col) = getCoords n xss askInput :: Player -> Board -> IO () askInput player board = do putStrLn $ showBoard board putStrLn $ "Your turn, " ++ show player ++ "!" putStr $ "Tile number (1 - " ++ show (length board ^ 2) ++ "): " number <- getLine case validateInput board number of Left s -> putStrLn ("Invalid input: " ++ s) >> gameStep player board Right n -> let tile = fromPlayer player next = changePlayer player in gameStep next (fillTile board n tile) gameStep :: Player -> Board -> IO () gameStep player board = case whoWon board of Just winner -> do putStrLn $ showBoard board putStrLn $ show winner ++ " won!" Nothing | isOver board -> putStrLn "Game ended in a draw" | otherwise -> askInput player board startBoard :: Int -> Board startBoard x = replicate x (replicate x Empty) main :: IO () main = gameStep Player1 (startBoard 3) -- Change NxN