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
= 80 :: Int
width = 60 :: Int
height
-- takes a two-dimensional list and returns the neighbours of (x,y)
neighbours :: [[a]] -> (Int,Int) -> [a]
= map (\\(x',y') -> m !! y' !! x') $ filter valid neighbours'
neighbours m (x,y) where height' = length m
= length (head m)
width' = x' >= 0 && x' < width' && y' >= 0 && y' < height'
valid (x',y') = [(x-1,y-1),(x,y-1),(x+1,y-1), -- neighbours over
neighbours' -1,y),(x+1,y), -- neighbours left/right
(x-1,y+1),(x,y+1),(x+1,y+1)] -- neighbours under
(x
-- updates all cells according to the rules in liveOrDead
update :: IORef [[Bool]] -> IO ()
= do
update c <- readIORef c
cells
let coords = [(x,y) | y <- [0..(height-1)], x <- [0..(width-1)]]
<- mapM (\\(x,y) -> do
nextGen 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
True nLive = nLive `elem` [2,3]
liveOrDead False nLive = nLive == 3
liveOrDead
-- utility function: split a list into sublists of length n
nLists :: Int -> [a] -> [[a]]
= []
nLists _ [] = take n ls : nLists n (drop n ls)
nLists 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 Quads $ do
renderPrimitive Vertex3 x y 0)
vertex (Vertex3 (x+w) y 0)
vertex (Vertex3 (x+w) (y-h) 0)
vertex (Vertex3 x (y-h) 0)
vertex (
-- draw each cell as a coloured square
display :: IORef [[Bool]] -> IO ()
= do
display c <- readIORef c
cells
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))
1/w) (1/h)
drawQuad x y (zip [0..] $ concat cells)
) (
swapBuffers
main :: IO ()
= do
main <- newStdGen
g <- getArgsAndInitialize
_
-- random starting values
<- newIORef ((nLists width . take (width*height) . randoms) g)
cells
<- createWindow "Conway's Game of Life"
_ $= [DoubleBuffered]
initialDisplayMode $= Size 800 600
windowSize $= display cells
displayCallback $= Just (update cells)
idleCallback 0 1 0 1 -- orthogonal projection
gluOrtho2D -- start main loop mainLoop