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.
Regarding technology, you'll need:
- A decent web browser that can handle the HTML5
canvaselement. - 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 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
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.
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.
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.
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 attributesclass 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
currentCellGenerationattribute will hold all cells in a two-dimensional array. - The
cellSizespecifies the width and height of a single cell—in our case, seven pixels. - The attributes
numberOfRowsandnumberOfColumnsdetermine 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
seedProbabilityattribute is used to determine if a cell is dead or alive. - The
tickLengthattribute specifies in which interval the game gets updated. In the example, the game updates every one hundred milliseconds. - The
canvasattribute 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
drawingContextattribute.
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.
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
drawGridmethod. - 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).
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article example source code | game-of-life.zip | 4KB | HTTP |
Information about download methods
Learn
-
CoffeeScript: Learn more about the
little language that compiles into JavaScript.
-
The Little
Book on CoffeeScript (O'Reilly): Read about getting
started with CoffeeScript.
-
Conway's Game
of Life: Read about the history of the game and different patterns
on Wikipedia.
- Author's version of Conway's Game of
Life.
-
HTML5
Canvas cheat sheet: Handy information for this element.
- "Create great graphics with the HTML5 canvas" (developerWorks,
February 2011): Explore how to enhance your web pages with Canvas, a
simple HTML5 element that packs a punch.
- "HTML5 fundamentals, Part 4: The final touch: The Canvas"
(developerWorks, July 2011): Read this introductory article about the
HTML5 Canvas element, which includes several examples to demonstrate
functions.
-
WHATWG: Learn about this community of
developers working with the W3C to fine-tune HTML5.
-
Canvas
tutorial: Use the canvas element in this tutorial from the Mozilla
developers.
-
HTML5 Canvas
reference: Explore several useful exercises to help hone your
canvas knowledge.
- developerWorks Web
development zone: Find articles covering various web-based
solutions. See the Web
development technical library for a wide range of technical
articles and tips, tutorials, standards, and IBM Redbooks.
- developerWorks
technical events and webcasts: Stay current with technology in
these sessions.
- developerWorks Live! briefings: Get up to speed quickly on IBM
products and tools as well as IT industry trends.
-
developerWorks on-demand demos: Watch demos ranging from product
installation and setup for beginners, to advanced functionality for
experienced developers.
- developerWorks on
Twitter: Join today to follow developerWorks tweets.
Get products and technologies
-
Author's implementation
of Conway's Game of Life: Get the example used in this article.
-
Node.js: Download and install this
prerequisite for CoffeeScript.
-
Node Package Manager (NPM): Use this with
Node.js to install CoffeeScript.
-
IBM product
evaluation versions: Download or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from DB2, Lotus,
Rational, Tivoli, and WebSphere.
Discuss
- developerWorks
community: Connect with other developerWorks users while exploring
the developer-driven blogs, forums, groups, and wikis.
- Find other developerWorks members interested in web development.

David 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.



