03
Apr 10

Conway’s Game of Life Using a Convolution Filter

This is a repost from an old blog I used to have. I’ve cleaned up the code a bit but the idea remains the same.

Conway’s Game of Life is an example of a cellular automaton. It is based on a square grid of cells who live or die depending on now many live neighbouring cells they have. The obvious way to code this would be to have a two-dimensional array storing the state of each cell and then going through each cell to calculate its new state.

Ugh, boring.

It would be much more fun to use a convolution filter to determine if a cell should live or die. Convolution filters take an array of values (called a kernel) which it treats as a two-dimensional grid whose values determine the colour of a pixel based on the pixels around it. All that’s needed after that is a palette map to set the variously coloured pixels to their correct alive or dead colours.

Certain starting shapes produce very complex outcomes when the rules are repeatedly applied to the grid. When you click on any of the running demos, a shape called “the Acorn” will be drawn at the mouse position and the state of the grid will be updated every frame.

The actionscript source code for this first example is at the end of the post. Click on the image to play the demo of Conway’s Game of Life:

Simple Conway's Game of Life

This is a more refined version where dead pixels are cycled through a palette of decay colours:

Conway's Game of Life with decay

This is a happy accident I ended up with when I was working out the fastest way of applying the palette of decay colours

Conway's Game of Life gone wrong

Actionscript source code for the first example is available below:

/*
Conway's Game of Life using a convolution filter and a palette map.
 
In the game of life, cells live or die depending on how many live neighbours
they have. In this version, pixels represent cells and the eight pixels around
every pixel are considered to be the cell's neighbours.
 
A convolution filter takes the job of adding up each pixel's number of live
neighbours and setting the pixel's colour to a value that represents that
value. After this, the pixels are palette mapped so the correct pixels are
kept alive. The process is repeated to animate the Game of Life.
 
The four rules controlling the system are:
1. Fewer than two live neighbours and the cell dies (Loneliness)
2. Greater than three live neighbours and the cell dies (Overcrowding)
3. Exactly three live neighbours and the cell comes to life (Creation)
4. Exactly two live neighbours and the cell stays how it is (Stasis)
 
Alistair Macdonald
http://www.stdio.co.uk/blog
*/
 
package {
    
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.filters.ConvolutionFilter;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    [SWF(width="400", height="400", frameRate="30", backgroundColor="#000000")]
    public class SimpleConway extends Sprite {
        
        private static const ALIVE_COLOUR:uint = 0xFFFF0000;
        private static const DEAD_COLOUR:uint = 0x00000000;
        private static const DISPLAY_SCALE:Number = 2;
        // Bitmap that is shown in the display list.
        private var displayContainer:Bitmap;
        // Bitmap that is used to hold the state of the pixels.
        private var state:BitmapData;
        // Filter used to determine how many 
        // alive neighbours a pixel has.
        private var countNeighbours:ConvolutionFilter;
        // Palette map array used to apply the rules of creation, destruction
        // and stasis on the state bitmap after countNeighbours has been applied.
        private var applyConwayRules:Array;
        // Pre-instantated objects do avoid object creation 
        // during the main event loop.
        private var point:Point = new Point();
        private var rect:Rectangle = new Rectangle();
 
        public function SimpleConway() {
            var w:int = stage.stageWidth / DISPLAY_SCALE;
            var h:int = stage.stageHeight / DISPLAY_SCALE;
            
            rect.width = w;
            rect.height = h;
            
            state = new BitmapData(w, h, true, DEAD_COLOUR);
            displayContainer = new Bitmap(state);
            displayContainer.scaleX = DISPLAY_SCALE;
            displayContainer.scaleY = DISPLAY_SCALE;
            addChild(displayContainer);
            
            /* Convolution filter which counts the number of alive neighbours a pixel has. Note the value of 0.5 applied to the source pixel. This allows the filter to carry the state of the state of the source pixel through so the stasis rule can be applied. */
            countNeighbours = new ConvolutionFilter(3, 3, [1, 1, 1, 1, 0.5, 1, 1, 1, 1], 8, 0, false);
            // Palette map, used after applying countNeighbours to convert from 
            // the pixels' values to their alive or dead colours.
            applyConwayRules = new Array(256);
            // Set values which must be counted as alive. All other values
            // will be counted as dead.
            // source pixel is alive and has 2 alive neighbours (stasis)
            applyConwayRules[0x4D] = ALIVE_COLOUR;
            // source pixel is alive and has 3 alive neighbours (creation)
            applyConwayRules[0x6E] = ALIVE_COLOUR;
            // source pixel is dead and has 3 alive neighbours (creation)
            applyConwayRules[0x5E] = ALIVE_COLOUR;
            
            stage.addEventListener(MouseEvent.CLICK, onClick);
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
    
        private function onClick(e:MouseEvent):void {
            // Draw the "acorn" shape at the mouse position. When scaled,
            // displayContainer can return sub-pixel mouse positions so
            // the value is rounded.
            var x:int = Math.round(displayContainer.mouseX);
            var y:int = Math.round(displayContainer.mouseY);
            state.lock();
            state.setPixel32(x, y, ALIVE_COLOUR);
            state.setPixel32(x-2, y-1, ALIVE_COLOUR);
            state.setPixel32(x-3, y+1, ALIVE_COLOUR);
            state.setPixel32(x-2, y+1, ALIVE_COLOUR);
            state.setPixel32(x+1, y+1, ALIVE_COLOUR);
            state.setPixel32(x+2, y+1, ALIVE_COLOUR);
            state.setPixel32(x+3, y+1, ALIVE_COLOUR);
            state.unlock();
        }
        
        private function onEnterFrame(e:Event):void {
            state.lock();
            state.applyFilter(state, rect, point, countNeighbours);
            state.paletteMap(state, rect, point, applyConwayRules, applyConwayRules, applyConwayRules, applyConwayRules);
            state.unlock();
        }
    }
}

Two Responses to “Conway’s Game of Life Using a Convolution Filter”

  1. tlecoz Says:

    funny !

    it reminds me some things I did recently

    this : http://www.machinbidules.com/?p=42

    (sorry it’s in french…Go to the bottom of the page to see the demo in action)

    and this : http://www.machinbidules.com/?p=168

    I will look at your code in details cause I never use BitmapData.paletteMap and never seen a code using it until now.

    Thanks !

  2. AS3 Cyclic Cellular Automata — Ginger Binger Says:

    [...] Daniel Rinehart’s version of Conway’s Game of Life using Pixel Bender. Also, check out this cool implementation using simple BitmapData functions by Alistair [...]

Leave a Reply

Copyright © 2013 The Daily Flash
Proudly powered by WordPress, Free WordPress Themes, and Linux Hosting