Conway's Game of Life in CoffeeScript and canvas

Create a small game using CoffeeScript and the HTML5 canvas element

Conway's Game of Life is a zero-player game that relies on only the initial configuration and then works without further input. In this article, walk through an implementation of your own version of the game. Learn to use CoffeeScript features and the HTML5 canvas element to create your game. Code samples are provided.

David Strauß (mail@stravid.com), Programmer, NerdKitchen

David Strauß photoDavid Strauß is a creative programmer who creates useful software and runs NerdKitchen. He discovered the magic of creating things at a very young age through his father's passion for working with wood. David mainly uses CoffeeScript and the Ruby on Rails framework. One of his first projects, Marble Run, won Mozilla's Game On 2010 Challenge, beating teams like Fantasy Interactive and the Google Chrome Team.



31 July 2012

Also available in Chinese Russian Japanese

Introduction

Small games are a fun way to learn more about a new technology. This article walks you through your own implementation of Conway's Game of Life using CoffeeScript and the HTML5 canvas element. Though Conway's Game of Life is probably not exactly a game, it's a great little task that is very manageable.

Frequently used abbreviations

  • CSS: Cascading Style Sheets
  • DOM: Document Object Model
  • HTML: HyperText Markup Language

Regarding technology, you'll need:

  • A decent web browser that can handle the HTML5 canvas element.
  • CoffeeScript, which is a small programming language that compiles to JavaScript. It is recommended that you invest 10 minutes to explore the CoffeeScript features on the website, or read the free online book The Little Book on CoffeeScript (see Resources).
  • The CoffeeScript compiler, in order to program CoffeeScript. It is recommended that you install Node.js and then use Node Package Manager (NPM) to install it (see Resources). The example in this article uses Version 1.3.3 of the CoffeeScript compiler.
  • Your favorite text editor.

You can download the source code for the examples used in this article.


Conway's Game of Life

Conway's Game of Life is basically a simulation that takes place on a two-dimensional orthogonal grid of square cells. Every cell is in one of two possible states: dead or alive. The game consists of periodic updates, which are also called ticks. With every tick the current generation of cells evolves into the next generation. During a tick, the following rules get applied to every single cell:

  • A living cell will die due to under-population if it has fewer than two living neighbors.
  • A living cell with two or three living neighbors lives on to the next generation.
  • A living cell with more than three living neighbors dies due to overcrowding.
  • A dead cell with exactly three living neighbors becomes a living cell due to reproduction.

The first generation of cells is created randomly. After that, the simulation runs until all cells are dead or patterns emerge. See Resources for more information about the different patterns and the history of Conway's Game of Life.

Figure 1 shows the end result of the exercise in this article. You can also see the result online and try it out for yourself (see the author's version of Conway's Game of Life in Resources).

Figure 1. Example Conway's Game of Life implementation
The finished Conway's Game of Life implementation

Implementation

In this article, the Conway's Game of Life implementation consists of two parts: the first part is the HTML5 markup and CSS, which is the groundwork required for the second part; the second part is the actual CoffeeScript implementation of the game.


HTML5 markup and CSS

The first step is to create a directory named game-of-life where you'll save all of the example files. The markup and CSS need a place to stay, so create a new index.html file within the new game-of-life directory. Listing 1 shows the HTML5 markup and CSS needed for the Conway's Game of Life implementation.

Listing 1. Markup and CSS of the index.html file
<html>
<head>
  <title>Game of Life</title>

  <script type="text/javascript" src="javascripts/game_of_life.js">
  </script>

  <style type="text/css">
    body {
      background-color: rgb(38, 38, 38);
    }

    canvas {
      border: 1px solid rgba(242, 198, 65, 0.1);
      margin: 50px auto 0 auto;
      display: block;
    }
  </style>
</head>
<body>
  <script type="text/javascript">
    new GameOfLife();
  </script>
</body>
</html>

Listing 1 gives the game a title and includes a JavaScript file named game_of_life.js from the javascripts directory. Don't worry, we'll program in CoffeeScript. Include this line in your index.html file even though the game_of_life.js file does not exist yet.

Let's add a little style so this Game of Life looks pretty. The body element gets a dark background and you add a border to the canvas element. With the CSS attributes margin and display, you ensure that the canvas is nicely positioned in the center of the screen.

To start the Game of Life, the code adds a little script block to the body of the markup and creates a new GameOfLife instance.


Game of Life CoffeeScript implementation

You probably wondered why the JavaScript file game_of_life.js, not a CoffeeScript file, is included in Listing 1. Many different browsers don't understand CoffeeScript yet, so you need to compile CoffeeScript code into JavaScript so the browser can interpret it. That is also the reason you need two new directories that live within the game-of-life directory. The first one, named coffeescripts, will contain all the CoffeeScript code. The second one, named javascripts, will contain the compiled JavaScript code.

The example implementation consists of only one class, so you need to create only one corresponding file. The game_of_life.coffee file, which will contain all the CoffeeScript code, is located in the coffeescripts directory.


Compiling CoffeeScript

Before you can start implementing the game logic, you need to find a way to compile the CoffeeScript code into JavaScript. Thankfully, the CoffeeScript compiler offers some options to do this. The command to automatically compile the CoffeeScript code to JavaScript is:

coffee --output javascripts/ --watch --compile coffeescripts/

To run this command, you have to navigate to the game-of-life directory in your favorite command line tool. Use the coffee command to compile the CoffeeScript code to JavaScript. At first, the example specifies the output folder with the --output flag. Then the --watch and --compile flags on the coffeescripts directory are used.

What does all this mean? Whenever a file in the coffeescripts directory gets modified, the coffee command will pick it up, compile it, and save the compiled JavaScript file into the javascripts directory. Now you know why we never created the game_of_life.js file that was included in Listing 1. When you write CoffeeScript code in the game_of_life.coffee file, it will get compiled, and the resulting JavaScript code gets automatically saved into the game_of_life.js file.


Initializing the game

Now that the compile issue is addressed, you can start programming the example version of Game of Life. Open the file game_of_life.coffee in your text editor. As shown in Listing 2, there's a single GameOfLife class with several attributes.

Listing 2. GameOfLife class and attributes
class GameOfLife
  currentCellGeneration: null
  cellSize: 7
  numberOfRows: 50
  numberOfColumns: 50
  seedProbability: 0.5
  tickLength: 100
  canvas: null
  drawingContext: null

Self-explaining variable and method names make reading source code easy. In Listing 2:

  • Since Conway's Game of Life consists of a two-dimensional grid of cells, the example also needs something like that. The currentCellGeneration attribute will hold all cells in a two-dimensional array.
  • The cellSize specifies the width and height of a single cell—in our case, seven pixels.
  • The attributes numberOfRows and numberOfColumns determine the size of the grid.
  • Conway's Game of Life needs an initial pattern of cells, which is also called the seed. When creating the seed, the seedProbability attribute is used to determine if a cell is dead or alive.
  • The tickLength attribute specifies in which interval the game gets updated. In the example, the game updates every one hundred milliseconds.
  • The canvas attribute will save the canvas element you're going to create.
  • To draw graphics on the canvas, you need the drawing context, which is stored in the drawingContext attribute.

The constructor of the GameOfLife class is responsible for setting up the game. As shown in Listing 3, you have to create a canvas before you can resize it to the correct dimensions. Then, you can use the newly created canvas to create the drawing context. After that, you're ready to create the initial seed pattern and kick off the game loop by starting the first tick. But first, let's create the canvas.

Listing 3. Constructor for the GameOfLife class
  constructor: ->
    @createCanvas()
    @resizeCanvas()
    @createDrawingContext()

    @seed()

    @tick()

Because modern browsers provide a great API to manipulate the document object model (DOM), and the example doesn't require anything fancy; you can ditch external frameworks like jQuery. Use the document.createElement method to create a new canvas element, which you then store in the equally named attribute. Append the newly created element to the body of the page. All of this happens in the createCanvas method, as shown in Listing 4.

Listing 4. Setting up the canvas element
  createCanvas: ->
    @canvas = document.createElement 'canvas'
    document.body.appendChild @canvas

  resizeCanvas: ->
    @canvas.height = @cellSize * @numberOfRows
    @canvas.width = @cellSize * @numberOfColumns

  createDrawingContext: ->
    @drawingContext = @canvas.getContext '2d'

The resizeCanvas method uses the cellSize, numberOfRows, and numberOfColumns to calculate the width and height of the canvas element. The third method in Listing 4, createDrawingContext, gets the 2d context from the canvas and stores it for future use.

Apart from these three methods, the constructor in Listing 4 invokes two additional methods: seed and tick. They cover a fairly big part of the code and are discussed in the following sections.


Creating the initial seed pattern

Conway's Game of Life requires an initial seed pattern. Based on the initial seed pattern, the cells on the grid evolve every tick into the next generation. To create the seed, you have to decide randomly for every cell on the grid if it is alive or dead using the seed method, as shown in Listing 5. Two nested for loops allow you to visit every cell on the grid.

The outer loop loops over all rows, accomplished with a CoffeeScript feature called ranges. The three periods (.) in for row in [0...@numberOfRows] indicate that the range is exclusive. If numberOfRows has the value 3, the iterator (in this case the row variable) will have the values ranging from 0 to 2. This lets you create the two-dimensional array currentCellGeneration.

The inner loop loops over all columns and creates a new seedCell for every cell. It invokes the createSeedCell method with the current row and column. After creating the seed cell, it stores it at the correct position within the currentCellGeneration.

Listing 5. Initial seed pattern
  seed: ->
    @currentCellGeneration = []

    for row in [0...@numberOfRows]
      @currentCellGeneration[row] = []

      for column in [0...@numberOfColumns]
        seedCell = @createSeedCell row, column
        @currentCellGeneration[row][column] = seedCell

  createSeedCell: (row, column) ->
    isAlive: Math.random() < @seedProbability
    row: row
    column: column

Creating a new seed cell is easy. A cell is a simple object and consists of three attributes. The createSeedCell method in Listing 5 simply passes the row and column arguments to the cell object. The isAlive attribute determines if the cell is dead or alive. With the help of the Math.random method and the seedProbability attribute, you randomly create dead or alive cells. You might have noticed you don't have to use the return keyword since CoffeeScript methods automatically return their final value.


The game loop

Now that you've created the initial seed pattern, it's time to bring the Game of Life to life. You have to draw the current generation of cells to the canvas and evolve the generation into the next one. And all of this needs to happen in a regular interval. As shown in Listing 6, start this interval by invoking the tick method. The tick method, in Listing 6, does three things. It:

  • Draws the current cell generation by invoking the drawGrid method.
  • Evolves the current cell generation into the next one.
  • Sets a timeout to keep the game loop running.

Use the setTimeout method with two arguments. The first argument is the method that should be invoked—in this case, the tick method itself. The second argument defines the number of milliseconds that should pass before it gets invoked. You can control the speed of the game loop with the tickLength attribute.

You might have noticed the difference between the tick method and all other methods. The tick method uses the fat arrow (=>) feature of CoffeeScript. The fat arrow binds the method to the current context. The context will always be correct. Without this, the timeout would not work.

Listing 6. The game loops tick method
  tick: =>
    @drawGrid()
    @evolveCellGeneration()

    setTimeout @tick, @tickLength

Drawing the grid is easy. The drawGrid method in Listing 7 uses two nested loops to visit every cell on the grid and then passes the cell to the drawCell method. The drawCell method calculates the x and y position on the grid using the cellSize as well as the row and column attributes of the cell. Depending on the isAlive attribute, you set the fill style of the cell. Set the strokeStyle and fillStyle attributes of the canvas before using the canvas methods strokeRect and fillRect to draw the cell.

Listing 7. Drawing the grid
  drawGrid: ->
    for row in [0...@numberOfRows]
      for column in [0...@numberOfColumns]
        @drawCell @currentCellGeneration[row][column]

  drawCell: (cell) ->
    x = cell.column * @cellSize
    y = cell.row * @cellSize

    if cell.isAlive
      fillStyle = 'rgb(242, 198, 65)'
    else
      fillStyle = 'rgb(38, 38, 38)'

    @drawingContext.strokeStyle = 'rgba(242, 198, 65, 0.1)'
    @drawingContext.strokeRect x, y, @cellSize, @cellSize

    @drawingContext.fillStyle = fillStyle
    @drawingContext.fillRect x, y, @cellSize, @cellSize

Evolving the current cell generation consists of three methods. The evolveCellGeneration method is shown in Listing 8. Similar to the seed method, use two nested loops to create a two-dimensional array called newCellGeneration, which will store the evolved cell generation. The inner loop passes the cell to the evolveCell method, which will return the evolved cell. The evolved cell then gets stored at the right position in the newCellGeneration array. After you've evolved every single cell of the current cell generation, you can update the currentCellGeneration attribute.

Listing 8. Evolve the current cell generation
evolveCellGeneration: ->
    newCellGeneration = []

    for row in [0...@numberOfRows]
      newCellGeneration[row] = []

      for column in [0...@numberOfColumns]
        evolvedCell = @evolveCell @currentCellGeneration[row][column]
        newCellGeneration[row][column] = evolvedCell

    @currentCellGeneration = newCellGeneration

The evolveCell method in Listing 9 starts by creating an evolvedCell variable with the same attributes as the passed cell. In order to decide if the cell dies, reproduces, or stays alive, you have to know how many neighbor cells are alive. To get this number, invoke the countAliveNeighbors method with the cell. This method counts and returns the number of living neighbors.

After you have the number of living neighbors, you can use the rules of Game of Life to update the isAlive attribute of the evolved cell. After updating that attribute, simply return the evolvedCell object.

Listing 9. Evolving a single cell
  evolveCell: (cell) ->
    evolvedCell =
      row: cell.row
      column: cell.column
      isAlive: cell.isAlive

    numberOfAliveNeighbors = @countAliveNeighbors cell

    if cell.isAlive or numberOfAliveNeighbors is 3
      evolvedCell.isAlive = 1 < numberOfAliveNeighbors < 4

    evolvedCell

The countAliveNeighbors method in Listing 10 takes a single cell as an argument and returns the number of living cells. Typically, a cell on the grid has eight neighbors. However, if the cell is located at the edges of the grid, the neighbor count is smaller. Counting the living neighbors is a slightly complicated task.

For a nice, readable solution to this problem, you have to calculate the area in which you're searching for living neighbors. For a cell in the middle of the grid, it's easy to calculate the bounds within the search. A cell located at row 4 and column 5 has neighbors in rows 3, 4, 5 and columns 4, 5, 6.

It's a different case for a cell located at row 0 and column 0. The neighbor cells range from row 0 to 1 and column 0 to 1. The lower bound for the rows is the row number of the cell minus one, but the minimum is zero. You can implement this using the Math.max method, as in Listing 10. The calculation for the lower bound for the columns is done the same way.

The upper bound is calculated with the Math.min method. Make sure that the cell row plus one is not bigger than the last row index. After you have the upper and lower bounds for the rows and columns, you can loop over them in two nested loops. In this case, the example uses the implicit range operator of CoffeeScript to ensure that the upperRowBound and upperColumnBound values are also used.

You don't want to count the cell itself, so you have to place a continue statement in the inner loop, which happens when the loops row and column variables match the cell's attributes. After that, increment the numberOfAliveNeighbors counter by one if the currently visited cell is alive. In the end, you only have to return this counter.

Listing 10. Count the living neighbors of a cell
  countAliveNeighbors: (cell) ->
    lowerRowBound = Math.max cell.row - 1, 0
    upperRowBound = Math.min cell.row + 1, @numberOfRows - 1
    lowerColumnBound = Math.max cell.column - 1, 0
    upperColumnBound = Math.min cell.column + 1, @numberOfColumns - 1
    numberOfAliveNeighbors = 0

    for row in [lowerRowBound..upperRowBound]
      for column in [lowerColumnBound..upperColumnBound]
        continue if row is cell.row and column is cell.column

        if @currentCellGeneration[row][column].isAlive
          numberOfAliveNeighbors++

    numberOfAliveNeighbors

Since CoffeeScript wraps every file into its own closure, you need to export the GameOfLife class so you can use it outside of its own file. Add a GameOfLife attribute to the window object, as follows: window.GameOfLife = GameOfLife.

And that's it! You're finished with the example Conway's Game of Life implementation. If you open the index.html file in your browser, you should see your own version of Game of Life, as illustrated in Figure 1. If something went wrong, you can compare your version with the author's complete source code (see Resources).


Conclusion

Though Conway's Game of Life is a small game with simple rules, there are some tricky problems you have to solve. It's a great example to use to learn a new programming language or improve your skills.

Small methods and self-explaining variable names make your code more readable. They also help with structuring your source code in a good way. CoffeeScript is great for this since you can omit a lot of JavaScript's syntax noise. CoffeeScript also provides handy features that can increase your productivity.


Download

DescriptionNameSize
Article example source codegame-of-life.zip4KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=825871
ArticleTitle=Conway's Game of Life in CoffeeScript and canvas
publish-date=07312012