Game of Life
While reading about cellular automata in preparation for an essay it struck me that I have never actually written Conway’s Game of Life. No, really!
To correct this embarrassing fact I quickly wrote a version in Haskell using the GLUT bindings.
It is very simple, but it works.
import Graphics.UI.GLUT hiding (get) import Graphics.Rendering.GLU.Raw (gluOrtho2D) import Data.IORef import System.Random -- dimensions of our cellular space width = 80 :: Int height = 60 :: Int -- takes a two-dimensional list and returns the neighbours of (x,y) neighbours :: [[a]] -> (Int,Int) -> [a] neighbours m (x,y) = map (\(x',y') -> m !! y' !! x') $ filter valid neighbours' where height' = length m width' = length (head m) valid (x',y') = x' >= 0 && x' < width' && y' >= 0 && y' < height' neighbours' = [(x-1,y-1),(x,y-1),(x+1,y-1), -- neighbours over (x-1,y),(x+1,y), -- neighbours left/right (x-1,y+1),(x,y+1),(x+1,y+1)] -- neighbours under -- updates all cells according to the rules in liveOrDead update :: IORef [[Bool]] -> IO () update c = do cells <- readIORef c let coords = [(x,y) | y <- [0..(height-1)], x <- [0..(width-1)]] nextGen <- mapM (\(x,y) -> do let cell = cells !! y !! x let ns = neighbours cells (x,y) return $ liveOrDead cell ((length . filter id) ns) ) coords writeIORef c (nLists width nextGen) display c -- survival rule: a live cell only lives on if it has 2 or 3 live neighbours -- birth rule: a dead cell becomes a live cell if it has 3 live neighbours liveOrDead :: Bool -> Int -> Bool liveOrDead True nLive = nLive `elem` [2,3] liveOrDead False nLive = nLive == 3 -- utility function: split a list into sublists of length n nLists :: Int -> [a] -> [[a]] nLists _ [] = [] nLists n ls = take n ls : nLists n (drop n ls) -- utility function: draws a square at (x,y) with size w×h drawQuad :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> IO () drawQuad x y w h = renderPrimitive Quads $ do vertex (Vertex3 x y 0) vertex (Vertex3 (x+w) y 0) vertex (Vertex3 (x+w) (y-h) 0) vertex (Vertex3 x (y-h) 0) -- draw each cell as a coloured square display :: IORef [[Bool]] -> IO () display c = do cells <- readIORef c let h = fromIntegral height let w = fromIntegral width mapM_ (\(n,b) -> do if b then currentColor $= Color4 1.0 0.8 0.6 1.0 else currentColor $= Color4 0.4 0.5 0.4 1.0 let x = 1/w*fromIntegral (n `mod` width) let y = 1-(1/h*fromIntegral (n `div` width)) drawQuad x y (1/w) (1/h) ) (zip [0..] $ concat cells) swapBuffers main :: IO () main = do g <- newStdGen _ <- getArgsAndInitialize -- random starting values cells <- newIORef ((nLists width . take (width*height) . randoms) g) _ <- createWindow "Conway's Game of Life" initialDisplayMode $= [DoubleBuffered] windowSize $= Size 800 600 displayCallback $= display cells idleCallback $= Just (update cells) gluOrtho2D 0 1 0 1 -- orthogonal projection mainLoop -- start main loop
