Sign in to follow this  
DevFred

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

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this