I'm trying to practice some functional techniques. I haven't worked much in Haskell, so any language-specific tips would be very appreciated, but what I care for even more are general tips towards my approach that I can carry between functional languages. Performance is not a big concern of mine right now.
I implemented the winner-checking algorithm for the connect-four game. Basically, given a 6x7 grid, I need to be able to determine if a player has successfully placed four consecutive tiles down (horizontally, vertically, or diagonally). If so, I need to return which player it was.
Here is my implementation:
module Logic (Player(..), Board, getWinner) where
import qualified Control.Monad as Monad
data Player = Player1 | Player2
type BoardEntry = Maybe Player
type Board = [[BoardEntry]]
type Coord a = (a, a)
instance Eq Player where
Player1 == Player1 = True
Player2 == Player2 = True
x == y = False
map2dList :: (a -> b) -> [[a]] -> [[b]]
map2dList fn = map (map fn)
getElementAt :: [a] -> Int -> Maybe a
getElementAt (el:_) 0 = Just el
getElementAt (_:rest) n = getElementAt rest (n - 1)
getElementAt [] _ = Nothing
getBoardEntryAt :: Board -> Coord Int -> Maybe BoardEntry
getBoardEntryAt board (x, y) =
getElementAt board y >>= flip getElementAt x
getBoardDimensions :: Board -> (Int, Int)
getBoardDimensions board =
((length . head) board, length board)
addCoords :: Coord Int -> Coord Int -> Coord Int
addCoords (x1, y1) (x2, y2) =
(x1 + x2, y1 + y2)
isCoordInRegion :: (Int, Int) -> Coord Int -> Bool
isCoordInRegion (width, height) (x, y) =
x >= 0 && x < width &&
y >= 0 && y < height
getAllCoordsInRegion :: (Int, Int) -> [Coord Int]
getAllCoordsInRegion (width, height) =
[(x,y) | x <- [0..width-1], y <- [0..height-1]]
getWinner :: Board -> Maybe Player
getWinner board =
let
fourInRowsFromOrigin =
[
[(0, 0), (1, 0), (2, 0), (3, 0)],
[(0, 0), (1, 1), (2, 2), (3, 3)],
[(0, 0), (0, 1), (0, 2), (0, 3)],
[(0, 0), (-1, 1), (-2, 2), (-3, 3)]
]
playerPieceAt = Monad.join . getBoardEntryAt board
results = concat $ do
coord <- (getAllCoordsInRegion . getBoardDimensions) board
fourInRow <- map2dList (addCoords coord) fourInRowsFromOrigin
player <- [Player1, Player2]
return [player | all ((== Just player) . playerPieceAt) fourInRow]
in case results of
[winner] -> Just winner
[] -> Nothing
_ -> undefined
And here is a sample driver, to help see that it works:
import Logic (Player(Player1, Player2), Board, getWinner)
getWinnerAsString :: Maybe Player -> [Char]
getWinnerAsString Nothing = "No one has won"
getWinnerAsString (Just Player1) = "Player 1"
getWinnerAsString (Just Player2) = "Player 2"
printWinner :: Board -> IO ()
printWinner = putStrLn . getWinnerAsString . getWinner
exampleBoard :: Board
exampleBoard =
let
__ = Nothing
p1 = Just Player1
p2 = Just Player2
in [
[__, __, __, __, __, __, __],
[__, __, __, __, __, __, __],
[__, __, p2, p1, __, __, __],
[__, __, p1, p2, __, __, __],
[__, p1, p1, p2, p1, __, __],
[p1, p2, p2, p2, p1, __, __]
]
main :: IO ()
main = printWinner exampleBoard