Conway's Game of Life in GHPython

Hi everybody,

A couple of years ago, when I first encountered the concept of Conway’s Game of Life, oftentimes referred to as cellular automaton, during my early architecture studies, I was kind of baffled and intrigued by it. For those of you, not familiar with architectural academia, the Game of Life, like the Vornoi diagram, particles, and other geometrical curiosities are often times experimented with to produce seemingly avant-garde design objects and/or buildings. Back then my overall CG and coding skills were still in early development, and the Game of Life remained a pretty much unrealised side-project.
I’ve revisited the concept the past weekend and am eager to share the results with you all.

For those of you not familiar, I’m not reporting about the parlor game, created in 1860 by Milton Bradley, although that would also be a great topic, but about the conceptual Game of Life devised by British mathematician John Conway in 1970.
It is basically a zero-player game, meaning that merely the initial conditions are set, and no further input is required beyond that. The initial configuration can then be observed evolving over generations, following a rather simple, yet ingenious set of rules.

gol_01_simple

The initial setup is an infinite, orthogonal, two-dimensional grid of rectangular cells, each of which is either alive or dead (populated or unpopulated). It is often referred to as seed of the game.
At each generation, the current state of every cell is reevaluated in function of its immediate neighbourhood. The following rules apply here:

  • Any live cell with fewer than two live neighbours dies, as if by underpopulation.
  • 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 overpopulation.
  • Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

All concerned cell states are changed simultaneously, following the above rules, in a discreet moment called the thick, making each generation purely a function of the preceding one.

I’ve devised a fully animated Grasshopper User Object that is highly customisable (and even features a custom icon).

25

Inputs

  • Plane: A base plane for the Game of Life (by default a world XY-plane)
  • ExtendX: A number of grid cells in the base plane x-direction (by default 10)
  • ExtendY: Number of grid cells in the base plane y-direction (by default 10)
  • SizeX: Grid cell size in the base plane x-direction (by default 1.0)
  • SizeY: Grid cell size in the base plane y-direction (by default 1.0)
  • Max: Maximum number of generations (by default no restrictions)
  • Wrap: True to wrap the grid boundary, or False (by default True).
    • Wrapping means that grid cells that form the grid boundary look for neighbours beyond the boundary on the opposite side, basically like in the Asteroids game from 1979, where you could pass the screen boundary with you spaceship and pop out on the opposite side. This is by default turned on, because the Game of Life is meant to be infinite!
    • :bulb: Wrapping can be switched off though to prevent flying geometries, when stacking individual game generations to produce a three-dimensional Game of Life in recording mode.
  • Rec: True to record and output the full Game of Life history, or False to only to output only the current generation (by default False).
    • :bulb: Recording allows you to produce a three-dimensional, stacked version of the Game of Life.
  • Seed: Random seed for the initial setup of alive or dead cells
  • Run: True to iterate the Game of Life, or False to pause it
  • Reset: True to reset the Game of Life completely

Outputs

Non-recording mode:

  • Count: The current generation count
  • Cells: A tree of grid cell outlines with branches for each grid row
  • States: A tree of current cell states (1 = alive, 0 = dead) with branches for each grid row
  • Ages: A tree of current cell ages with branches for each grid row
    • :bulb: The cell age can for instance be used to color a cell.

Recording mode:

  • Count: A tree of generation counts with branches for each generation
  • Cells: A tree of grid cell outlines with branches for each generation that each include branches for each grid row
  • States: A tree of current cell states (1 = alive, 0 = dead) with branches for each generation that each include branches for each grid row
  • Ages: A tree of current cell ages with branches for each generation that each include branches for each grid row

Downloads

User object:
conways_game_of_life.zip (6.7 KB)

Example files:
conways_game_of_life_example_files.zip (40.8 KB)

Installation

In order to install the component, simply move it to the Grasshopper user objects folder (File > Special Folders > User Objects Folder).

Feel free to alter, manipulate, adapt or bastardize the Python code in any way you like. It is accessible by double clicking the component.

Please, leave feedback or report any bugs here! I would also be interested in seeing project images, if anybody uses the component to realise a design.

29 Likes

Hi ,
Thanks for your nice sharing! it helps me a lot!
however, i have a problem about how to change the density of initial points?
I try to read your code,but i am not good at python at all,
Would help me please?
Thank you very much!
Regards,
Jason

Hi @songpeng.arc,

The number of initial cells in x- and y-direction is defined by the ExtendX and ExtendY inputs. If you set these parameter for instance to 10 cells in the x- and 5 cells in the y-direction, you end up with 50 cell (10 x 5) total!
You can change the size of each cell with the SizeX and SizeY inputs.
A detailed description of what each input is for is shown, when you hover your mouse over it.

2 Likes

Yeah,
My question is that, for the initial grid cells, every cell(in your class) is both 0 or 1, that means that it always has 50%density of initial cells birth.
Actually what i am trying to do is , to add sliders which can control the initial number of birth cell(10% or 30% whatever.)

I mean,maybe can add an inputs called ‘inputpts’ ,to use these points as the birth points.
But i am not sure whether i can modify your code to achieve this function.
If you have some advice, i will be very nice!
Thanks and regards
jason

OK, simply change line 93 of the script to this:

self.grid[x,y]["state"] = True if random.random() < 0.35 else False

Here the initial cell state is initiated.
random.random() generates a random number between 0.0 and 1.0. Now, if this number is smaller than the decimal percentage 0.35, which translates to 35%, the cell will be initially alive. You can adjust this to your desired percentage.

Sure, this would also be possible, it just involves more changes to the current code. Feel free to have a go at it!

Thank you so much! :slight_smile: big hug!

1 Like

Hello,

Prompted by this year’s Advent Of Code challenge I have quickly implemented a 3D version of the game of life in Python in Rhino 7

import rhinoscriptsyntax as rs
import Rhino
from itertools import permutations
from collections import defaultdict

def conway_cubes(rounds):
    rs.EnableRedraw(False)
    # Example from the puzzle
    state = """.#.
..#
###"""
    
    new_cubes = set()
    # Build the starting grid
    for y, row in enumerate(state.splitlines()):
        for x, cell in enumerate(row):
            if cell == '#':
                new_cubes.add((x,y,0))
    
    # Initialise sets and lists 
    old_spheres = []
    neighbouring = set(permutations([1,1,1,0,0,0,-1,-1,-1],3)) # 27 neighbours
    neighbouring.remove((0,0,0)) # a cube is not its own neighbour
    
    for round in range(rounds+1):
        cubes = new_cubes.copy()
        rs.DeleteObjects(old_spheres)
        neighbours = defaultdict(int)
        old_spheres=[]
        
        # Draw cubes and identify neighbours
        for cube in cubes:
            old_spheres.append(rs.AddSphere(cube, 0.6))
            for n in neighbouring:
                neighbours[(cube[0] + n[0],
                            cube[1] + n[1],
                            cube[2] + n[2],
                            )] += 1
        new_cubes = set()
        rs.Redraw()
        
        # Apply rules for creation of cubes in next round
        for location, n in neighbours.iteritems():
            if location in cubes:
                if 2 <= n <= 3:
                    new_cubes.add(location)
            elif n == 3:
                new_cubes.add(location)
    rs.EnableRedraw()


if  __name__ == '__main__':
    conway_cubes(rounds = 6)

Some renders from my solution :

7 Likes