• Advertisement
Sign in to follow this  

My first monoid (or: Tic Tac Toe in 99 lines of Haskell)

This topic is 2906 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Recently I was pondering how to detect if a player owns a line on a Tic Tac Toe board in a high-level, functional way -- so no conditionals or boolean operators allowed, just for fun:
import Data.Monoid
import Data.Array
import Data.List (intercalate)
import Data.Char (isDigit, digitToInt)

data Player = None | One | Two | Unknown
     deriving Eq

instance Show Player where
    show None = " "
    show One  = "X"
    show Two  = "O"

data Board = Board (Array Int Player)

emptyBoard = Board (listArray (1, 9) (repeat None))

isFree (Board a) pos =
    a ! pos == None

isFull (Board a) =
    all (/= None) $ elems a

makeMove (Board a) player pos =
    Board (a // [(pos, player)])

instance Show Board where
    show (Board a) = embed' "+---+---+---+\n" (map line [7, 4, 1]) where
            line y = embed  "| " " | " " |\n" (map col  [0, 1, 2]) where
             col x = show $ a!(y+x)

embed front middle back xs =
    front ++ intercalate middle xs ++ back

embed' sep = embed sep sep sep

winners board =
    [(player, line) | line <- boardLines,
                      let player = owner board line,
                      player /= None]

boardLines = [

    [1, 2, 3], [4, 5, 6], [7, 8, 9],   -- horizontal

    [1, 4, 7], [2, 5, 8], [3, 6, 9],   -- vertical

    [3, 5, 7], [1, 5, 9] ]             -- diagonal

instance Monoid Player where
    mempty                = Unknown

    x   `mappend` Unknown = x
    One `mappend` One     = One
    Two `mappend` Two     = Two
    _   `mappend` _       = None

owner :: Board -> [Int] -> Player
owner (Board a) = mconcat . map (a !)

hasWinner :: Board -> Bool
hasWinner = not . null . winners

main = do
    print emptyBoard
    gameloop emptyBoard One Two

gameloop board player enemy = do
    pos <- getFreePosition board
    if (pos == 0)
        then putStrLn "goodbye!"
        else evaluate (makeMove board player pos) player enemy

evaluate board player enemy = do
    print board
    if (hasWinner board)
        then putStrLn ("player " ++ show player ++ " wins!")
        else if (isFull board)
             then putStrLn "It's a tie!"
             else gameloop board enemy player

getFreePosition board = do
    pos <- getPosition
    if (pos == 0 || isFree board pos)
        then return pos
        else getFreePosition board

getPosition = do
    c <- getCharacter
    if (isDigit c)
        then return (digitToInt c)
        else getPosition

getCharacter = do
    putStr "> "
    line <- getLine
    if (null line)
        then getCharacter
        else return (head line)
What do you think of the owner function? Horrible? Beautiful? An abomination?

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement