Pages

Wednesday, July 18, 2012

Conway's Game of Life in a MODEL clause

This post has no serious purpose. I was just fooling around with the MODEL clause when I got the idea that ITERATE could be used for modelling Conway's Game of Life. So that's what I did - just a little fun example of what MODEL can be used for. Sure it could be done in any number of other ways, I don't claim this to be a smart or efficient way, just fun ;-)

Conway's game of life models a population of cells in an twodimensional coordinate system with rules for how cells die, survive or reproduce from generation to generation:

Any live cell with fewer than two live neighbours dies, as if caused by under-population.

Any live cell with two or three live neighbours lives on to the next generation.

Any live cell with more than three live neighbours dies, as if by overcrowding.

Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

With a MODEL clause I can model these cells with x,y coordinates, ITERATE through the generations, and use RULES to calculate whether a cell die, survive or reproduce.

Here I create the 12x12 grid by cartesian join of numbers with itself. Then I give all live cells a cell value of 1 and all other cells a value of 0.

30 dimension by (
31 0 as generation
32 , x
33 , y
34 )

This defines the indices of my "three dimensional array" which will be populated with the generation zero 12x12 grid from lines 20-27. "0 as generation" creates the dimension "generation" with a constant value of 0 for all the initial data.

35 measures (
36 cell
37 )

There will be only one value (cell) for each position in the "three dimensional array".

39 rules upsert all iterate (10)

This defines that I wish to iterate through the rules 10 times (meaning I will calculate Conway's Game of Life for 10 generations.) The "upsert all" is needed because I will create entries in the array that did not exist from the start.

41 cell[iteration_number+1, any, any] =

This will set cell values of the next generation for any x,y value. iteration_number will be 0 in the first iteration, which therefore sets values for generation 0+1=1. Next iteration will have iteration_number=1 and set values for generation 1+1=2. Etc.
This assignment operator will therefore in each iteration create 12x12 new entries in our 3-D array. Each iteration calls the assignment 12x12 times to calculate the next generation. We assign into generation "iteration_number+1", so we want to calculate on values from generation = iteration_number.

sum(cell)[...] gives me the sum of the values of cell for all indices defined within the square brackets. cv() is a shortcut for "current value" meaning that for each of the 12x12 calls to this calculation, cv() for x and cv() for y will return the actual x,y values for the specific call.
For example in the first iteration (iteration_number=0) the specific assignment call for x=5 and y=7 the cv() will generate sum(cell)[generation=0, x between 4 and 6, y between 6 and 8].
For the edge cases where cv() of either x or y is 12, this will become "between 11 and 13", but there are no entries for x=13 or y=13 in the array. The "ignore nav" in line 38 tells the calculation to treat these nonexistent data as zero rather than null.
So the sum(cell) is the sum of cell values of the 8 neighbors to the cell + the cell itself. Therefore I subtract the cell itself to get the sum of the neighbors only. That sum is the number of live neighbors of the cell.

49 when 2 then cell[iteration_number,cv(),cv()]
50 when 3 then 1
51 else 0

And so I can apply Conway's rules. If the cell has two live neighbors nothing happens to the cell, it stays either dead or live so I carry over the value from the previous generation.
If the cell has three live neighbors it will always be live in the next generation (if it was live then it survived, if it was dead then it reproduced.)
All other cells (either too few or too many live neighbors) will be dead.

And finally I turn the generation,x,y data into one line for each y in each generation. "cells" will be a string with a space in each x position of dead cells and an X in each position of live cells. (listagg will work in version 11.2!)

And these SQL*PLUS commands/parameters then format the output reasonably nicely with a "page" for each generation. The output needs a non-proportional font anyway, so if you are in TOAD or another GUI tool you probably need to "Execute as script" rather than getting your output in that nice proportional-font grid :-)

Speaking of TOAD... Here's another example just with a smaller grid and a different set of starting data: