life-0.1: Life.hs
import Data.IORef
import Data.Array
import Graphics.UI.GLUT
import System.Random
import Control.Monad
-- TODO read these from the commandline
cellsX = 120
cellsY = 120
scaleFactor = 6
-- |Conway's life is classically described with these rules:
-- * If a cell has less than 2 neighbors, it dies of loneliness
-- * If a cell has four or more neighbors, it dies of overcrowding
-- * If a cell has 2 or 3 neighbors, it continues to live
-- * If an empty cell has three neighbors, a new cell is born
--
-- Functionally it's easier to describe with just three rules:
-- * An existing cell with two neighbors is live
-- * Any cell/space with exactly three neighbors is live
-- * All other cells/spaces are dead
rule :: Bool -> Int -> Bool
rule True 2 = True
rule _ 3 = True
rule _ _ = False
type World = Array (Int, Int) Bool
mkWorld :: Int -> Int -> [Bool] -> World
mkWorld sx sy = listArray ((1,1),(sx,sy))
generation :: World -> World
generation w = mkWorld cellsX cellsY $ map (uncurry rule . neighborhood w) $ indices w
neighborhood :: World -> (Int, Int) -> (Bool, Int)
neighborhood world (x,y) = (world ! (x,y), length . filter id $ neighbors)
where
neighbors = map (world !*) [(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)]
(!*) :: World -> (Int, Int) -> Bool
w !* (x,y) = w ! (wrap cellsX x, wrap cellsY y)
where wrap bound val | val < 1 = wrap bound $ val + bound - 1
| val > bound = wrap bound $ val - bound + 1
| otherwise = val
main :: IO ()
main = do
rng <- newStdGen
let world = (mkWorld cellsX cellsY . map toEnum . randomRs (0,1)) rng
worldRef <- newIORef $ world
getArgsAndInitialize
initialDisplayMode $= [ DoubleBuffered ]
let screenX = fromIntegral $ cellsX * scaleFactor
screenY = fromIntegral $ cellsY * scaleFactor
initialWindowSize $= Size screenX screenY
createWindow "Conway's Life"
idleCallback $= Just (updateWorld worldRef)
clearColor $= Color4 0 0 0 0
ortho 0.0 (fromIntegral screenX) 0.0 (fromIntegral screenY) (-1.0) 1.0
mainLoop
updateWorld :: IORef World -> IO ()
updateWorld wr = do
atomicModifyIORef wr $ \w -> (generation w, ())
drawWorld wr
v2 :: GLfloat -> GLfloat -> Vertex2 GLfloat
v2 x y = Vertex2 x y :: Vertex2 GLfloat
drawWorld :: IORef World -> IO ()
drawWorld wr = do
world <- readIORef wr
clear [ColorBuffer]
sequence [drawCell world x y | x <- [1..cellsX], y <- [1..cellsY]]
swapBuffers
flush
drawCell :: World -> Int -> Int -> IO ()
drawCell world x y = do
let sf = fromIntegral scaleFactor
sx = (fromIntegral x) * sf :: GLfloat
sy = (fromIntegral y) * sf :: GLfloat
currentColor $= (cellColor $ world ! (x,y))
renderPrimitive Polygon $ do
vertex $ v2 sx sy
vertex $ v2 sx (sy + sf)
vertex $ v2 (sx + sf) (sy + sf)
vertex $ v2 (sx + sf) sy
where
cellColor True = Color4 1 1 1 1
cellColor False = Color4 0 0 0 1