Game of Life in Grasshopper (with Python)
I introduced some of my students to Python scripting in Grasshopper this week through a simple implementation of Conway's Game of Life. Here's a writeup for public consumption.
The Grasshopper setup is pretty simple. We'll be using a Python script component to do most of the work. For inputs we pass in a file path (pointing to a bitmap file that defines the starting condition of the field) and an integer value (from a slider) that acts like a position on a timeline.
For the output, we'll get a list of points, representing the position of each pixel, and a corresponding list of values in the range 0-1 indicating whether the pixel is on ("alive") or off.
The script has four main parts. First, we load in the starting state from our bitmap as one big list of numbers, data
. We also need to know the width. Second, we set up the output, looping over data
to create the points and on/off values.
Next we need a function to get the values of what we call the "neighborhood" of a given pixel. Game of Life uses a Moore neighborhood (compare with von Neumann neighborhood), which basically means the eight surrounding pixels. Call this get_neighbors
.
The last step is defining and applying the rules of the game. We'll do this with conditional statements that decide what to do for each pixel in data
based on 1) whether it is alive or dead and 2) how many of its neighbors are alive. Let's call this one evaluate
.
One more loop will recursively call evaluate
for each step in time up to t
. Note that in an environment with a render loop (like a game engine) we would do this a bit differently, but everything else works the same.
Here is the finished script:
# borrowed tools for bitmap and Rhino geometry import System.Drawing.Bitmap as bmp import Rhino.Geometry as rg # read initial state of the field from file path def load(path): global h, w, data field = bmp.FromFile(path) h = field.Height w = field.Width data = [] for i in range(0, w * h): x = i % w # modulo operator (division remainder) y = i // h # floor division (divide + round down) val = int(field.GetPixel(x, y).GetBrightness()) data.append(val) return data # get the Moore neighborhood for a given pixel # i is its index, w is width of the field and d is data # try/except handles out-of-bounds cases def get_neighbors(i, w, d): try: return (d[i - w - 1], d[i - w], d[i - w + 1], d[i - 1 ], d[i ], d[i + 1 ], d[i + w - 1], d[i + w], d[i + w + 1]) except: return [] # loop through all pixels, apply rules of the game def evaluate(data): for i, el in enumerate(data): neighbors = get_neighbors(i, w, data) count = sum(neighbors) alive = bool(el) if alive and count < 2: data[i] = 0 elif alive and count <= 3: data[i] = 1 elif alive and count > 3: data[i] = 0 elif count == 3: data[i] = 1 return data # main script body load(F) for t in range(0, T): data = evaluate(data) # output as points and on/off values P = [] D = [] for i, v in enumerate(data): x = i % w y = i // h P.append(rg.Point3d(x, y, 0)) D.append(v)
This works pretty well when using a small field, here it's only 32 pixels in width and height. If you have a need for speed, two places to start are the load
function (the GetPixel
method of System.Drawing.Bitmap
is slow/expensive, discussed on StackOverflow) another could be to reconsider the rendering approach, maybe using the Rhino DisplayPipeline
with Draw2dRectangle
calls.