In today’s Programming Praxis exercise, our goal is to make any program we want, as long as it’s cool and it fits within 12 lines of code. I decided to make an implementation of Conway’s Game of Life: it’s interesting, it’s visual, it’s Turing complete… how much cooler can you get? Let’s get started, shall we?
First, some imports. I’m not counting these as lines of code since to me a line of code is something that can contain a bug.
import qualified Data.List.Key as K import qualified Data.Map as M
First we define the rule that the game of life is based on: cells stay alive if they have 2 or 3 neighbors, and they become alive if they have 3 neighbors.
rule (cy,cx) m = elem ns $ if alive (cy,cx) then [2,3] else  where ns = sum [1 | y <- [-1..1], x <- [-1..1], (y,x) /= (0,0), alive (cy+y,cx+x)] alive c = M.lookup c m == Just 'x'
We calculate the next generation of a grid by determining the rectangle that holds the active cells, adding a one-cell border (since that’s the furthest influence distance) and calculating the new state for each cell in the resulting rectangle.
step m = if null on then M.empty else M.fromList [((y,x),if rule (y,x) m then 'x' else '.') | y <- range fst, x <- range snd] where on = M.keys $ M.filter (== 'x') m range f = [minimum (map f on) - 1..maximum (map f on) + 1]
Next, we need some functions to load and show a generation:
load s = M.fromList [((y,x),c) | (y,l)<-zip [0..] $ lines s, (x,c)<-zip [0..] l] display = mapM_ (putStrLn . map snd) . ( :) . K.group (fst . fst) . M.assocs
The program reads the input from a text file that holds the starting situation, e.g.
..... .xxx. .x... ..x.. .....
and then prints all of the subsequent generations until the pattern stabilizes, i.e. it finds two subsequent identical generations.
main = mapM_ (display . snd) . takeWhile (uncurry (/=)) . (\l -> zip l $ tail l) . iterate step . load =<< readFile "life.txt"
And that’s it. A cool little program that’s even one line under our budget of 12.