Basic tic-tac-toe matrix in Haskell

Posted on

Problem

Beginner functional programmer… (but not beginner programmer)

Currently, I have the following code:

import Control.Monad (mapM_)

main = gridMaker 3

gridMaker :: Int -> IO ()
{-Creates a nxn matrix filling the lower triangle with "O"'s
  and filling the rest with "X"s
  Example:
  O --- X --- X
  O --- O --- X
  O --- O --- O
  -}
gridMaker gridSize =
  -- Creates the indicators
  let startingIter = 1
      indicators = indicatorListCreator gridSize startingIter
  -- Print each indicator out to IO
  in mapM_ linePrinter indicators

indicatorListCreator :: Int -> Int -> [[String]]
{- Build the indicators of
   [["O", "X", "X"],["O", "O", "X"] ... and so forth.
   Recursively determines how many iterations we've been through,
   and determines how many "X"s and "O"s we should have
   per each line. -}
indicatorListCreator gridLen iterNum
  | iterNum > gridLen = []
  | otherwise =
    let itersRemaining = gridLen - iterNum
        indicator = replicate iterNum "O" ++
                    replicate itersRemaining "X"
    in
      indicator: indicatorListCreator gridLen (iterNum + 1)

linePrinter :: [String] -> IO ()
{- Takes the indicators and prints each line accordingly. -}
linePrinter [indicator1, indicator2, indicator3] =
  let between = " --- "
      outString = indicator1 ++ between ++
                  indicator2 ++ between ++
                  indicator3
  in putStrLn outString
linePrinter _ = error"Stupidly hardcoded to only show 3x3"

Running and compiling this code results in:

O --- X --- X
O --- O --- X
O --- O --- O

Some of my thoughts…

  • Is it possible to build indicatorListCreator out of folds?
  • How can I circumvent hardcoding linePrinter
  • Is this optimal?
  • Where should I improve on coding conventions / style?
  • Any other glaring shortcomings?

Thank you in advance!

Solution

I like that you are trying to not use explicit recursions everywhere. That being said, there are a few standard functions that we could use to get a code similar to the following.

import Data.List(intercalate)

grid :: Int -> [String]
grid n = [intercalate " --- " . take n . drop (n-k) $ osxs | k <- [1..n]]
    where osxs = replicate n "O" ++ replicate n "X"

printGrid :: Int -> IO ()
printGrid = putStr . unlines . grid

Most notably, Data.List.intercalate can be used to generalize your linePrinter, and unlines helps us avoid mapM_.


If you are writing a game, then a Data.Map.Strict.Map could be useful for storing the board’s current values.

Leave a Reply

Your email address will not be published. Required fields are marked *