module Board( Board(..),    -- ^ type
              nextgen,      -- ^ computes the next generation
              showcells,    -- ^ printing
              seqn          -- ^ does multiple IO actions
            ) where

{- |
Module      :   Board.hs
Description :   Board for Conway's Game Of Life
Copyright   :   (c) Thomas Lang, 2014
License     :   BSD3

Stability   :   stable
Portability :   portable

This module represents the Board for the Game of Life.
 -}



type Pos   = (Int,Int)
type Board = [Pos]


-- setting dimension of board
width :: Int
width = 70

height :: Int
height = 70


-- this function prints out the
-- board of all living cells with a "O"
--
-- it then uses the below defined "seqn"
-- function for printing out one line after another
showcells :: Board -> IO()
showcells b = seqn [ writeAt p "O" | p <- b ]



-- printing empty spaces
goto :: Pos -> IO ()
goto (x,y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")




-- writing at a specific position by
-- firstly printing empty spaces
-- and then printing the "O"
writeAt :: Pos -> String -> IO ()
writeAt pos xs = do goto pos
                    putStr xs




-- this function takes a list of actions
-- and performs one after another
seqn :: [IO a] -> IO()
seqn []     = return ()
seqn (x:xs) = do x
                 seqn xs


-- this checks whether a specific
-- cell on the board is alive or not
isAlive :: Board -> Pos -> Bool
isAlive b p = elem p b


-- this checks, whethter a specific
-- cell on the board is empty (dead)
isEmpty :: Board -> Pos -> Bool
isEmpty b p = not (isAlive b p)



-- this function returns a list of cells
-- that are the immediate neighbours of
-- the passed cell
neighbs :: Pos -> [Pos]
neighbs (x,y) = map wrap [(x-1, y-1),(x, y-1), (x+1, y-1), (x-1, y), (x+1, y), (x-1, y+1), (x, y+1), (x+1, y+1)]



-- this function makes sure, that the 
-- board is restricted by the size
wrap :: Pos -> Pos
wrap (x,y) = (((x-1) `mod` width ) + 1, ((y-1) `mod` height ) + 1 )



-- returns the amount of living neighbours
-- of a specific cell
liveneighbs :: Board -> Pos -> Int
liveneighbs b = length . filter (isAlive b) . neighbs



-- this returns a list of that cells, 
-- that will survive to the next
-- generation (only cells, that have
-- 2 or 3 living neighbours)
survivors :: Board -> [Pos]
survivors b = [p | p <- b, elem (liveneighbs b p) [2,3] ]


-- this returns a list of cells that
-- will be given birth in the next 
-- generation (dead cells that have
-- exactly 3 living neighbours)
births :: Board -> [Pos]
births b = [ p | p <- rmdups (concat (map neighbs b)), isEmpty b p, liveneighbs b p == 3]


-- this general function removes duplicates from a list
rmdups :: Eq a => [a] -> [a]
rmdups []     = []
rmdups (x:xs) = x : rmdups (filter (/= x) xs)


-- this functions computes the next generation 
-- of living cells that is the union of the
-- survivors and the newly given births cells
nextgen :: Board -> Board
nextgen b = survivors b ++ births b

