Learning Haskell with Venu: Cellular automata

ยท Reading time: 4 mins

Lately I've been experimenting a lot with a number of CG and procedural content generation algorithms, and began feeling the need for mathematical rigour over the practical, "real life situation" style I've been used to by writing Javascript day in, day out. I got to a point where simple simulations required way more boilerplate code than I was willing to write, and the usage of native numeric constructs began getting in the way of performance.

For example, I was working on an image processing algorithm, representing the pixels as plain 1D arrays, and I discovered that wrapping the aforementioned array inside a native Uint32Array produced an impressive performance boost, cutting the execution time from ~46ms to ~1.5ms at the expense of adding a little boilerplate code. Which is great in and of itself, but not so great in terms of code cleanliness, and code cleanliness is fundamental when I'm trying to represent somewhat complex mathematical ideas.

So I decided to look into functional programming. After reading about it for a bit, I began working my way through the Ninety-nine Haskell Problems to get comfortable with the new paradigm, and then decided to prototype something a bit more challenging.

Cellular automata

I chose to implement a simple cellular automaton. A cellular automaton is a discrete computation model defined over a grid of cells, each one having a state. Each step of the computation, called generation, involves computing a new state for each cell that depends upon the previous states of the cell and of a chosen subset of neighboring cells. The criteria with which the new state is computed are called rules and are, basically, a function of those previous states.

Let's choose a neighborhood in the form of the Moore neighborhood. Let denote the 2D matrix representing the automaton. The Moore neighborhood for a generic cell is defined as

Let be the matrix at the th generation. We could say that

Finally, we could decide our rule function to be a simplified version of the one described in this nice article about cellular automata which I suggest you read, too.

I decided to represent the automaton's grid as a plain 1D array. The generation function is defined inductively with great ease; being vec the array containing the grid, the generation 0 is the array itself, and each subsequent generation is defined as the step function mapped over the values of the previous generation. step corresponds to the defined before, and the sum over the neighborhood is represented as the fold of the sum operation over the cell values extracted by the getCell function.

generation :: Int -> [Int] -> [Int]

generation 0 vec = vec
generation i vec = map step [0 .. length vec]
  where
    vec' = generation (i-1) vec
    step :: Int -> Int
    step i = if (neighSum > 4) then 1 else 0
      where
        x = i `mod` 32
        y = i `div` 32
        neighSum = foldr (+) 0 [getCell (x + x') (y + y') vec' | x' <- [-1..1], y' <- [-1..1], not (x' == 0 && y' == 0) ]

Coupled with Haskell's System.Random, we get a nice looking dungeon after a few generations:

Cellular Automaton

You can read the whole code here: https://gist.github.com/veeenu/8dbba3e53f94d7142c63.

Surely this could have been done a lot better, and it will be, as I keep studying the language and getting better at it. Meanwhile, please feel free to criticize and comment whatever you feel like to! :)

ERRATA CORRIGE. My friend Roberto pointed out that the 2-tuple wasn't supposed to be part of the Moore neighborhood, and, besides that, its status was already taken into account for (but not used in this particular case) in the form of in the rule function anyway. This has now been corrected both in the code and in the mathematical expression.