Search code examples
elmconways-game-of-life

Elm Game of life program becomes unresponsive - is there a way to fail gracefully?


I have a basic implementation of Conway's game of life written in elm running at uminokirin.com.

The source is visible here.

The program let users adjust the size of the toroïdal grid, click on cells to change their status, and randomize the world. It works well for small values (less than 50) on my computer.

However when attempting to use the randomize grid function on bigger grids (the threshold value doesn't seem to be always the same), the program becomes unresponsive without any warning and the only way to recover is to reload the app.

There is zero optimization in the GOL algorithm and using a single svg rectangle for every cell is probably horribly inefficient, but it sill doesn't explain why the program behaves in this way instead of say, slowing down.

Is this the elm runtime giving up? Or some kind of browser safeguard?

More importantly is there a way to prevent this behavior other than arbitrarily capping the maximum size of the grid?


Solution

  • The behavior you are observing is due to a Javascript stack overflow. After pressing the "randomize" button, in the browser console you can see the message "Uncaught RangeError: Maximum call stack size exceeded"

    This happens because the randomize function allocates several large temporary variables. In particular, the shuffle function (which is called from the randomize function) appears to allocate two temporary lists that each have one element for every cell in the life grid. Elm may be smart about releasing these on a timely basis but this appears to push it too far.

    To fix this you can use a simpler randomize function. The version shown below uses Elm Generators to generate a single list of Dead/Alive values and then initializes the randomized array from that list.

    randomize2 : Array Cell -> Int -> Int -> Int -> Array Cell
    randomize2 grid gs sd n = 
      let floatGen = Random.float 0.0 1.0
          lifeGen = Random.map (\b -> if (b < toFloat n/100) then Alive else Dead) floatGen
          listGen = Random.list (gs*gs) lifeGen
      in fst (Random.step listGen (initialSeed sd)) |> fromList
    

    Using this randomize function I was able to resize the grid up to 600x600 and randomize successfully. At that point I stopped testing.