In this post we will create John Conway’s Game of Life using HTML, CSS & JavaScript. John Conway is a brilliant mathematician in our life time. I encourage you to check-out his interview below. The Game of Life is a zero player game. Each cell interacts with it’s eight neighbors (up, down, left, right and the four (4) diagonals). So is it a game or not…let me know your thoughts in the comments.
The Game Of Life came about by trying to solve a problem. The problem is, in the likelihood we ever make it to the Mars surface, how do we control the population based on limited resources? Based on the below rules, it determines the survivability of a society. Considerations are over & under population. Based on the population, cells will thrive or dwindle. Catastrophic over population will cause cells to die quickly. Under population will case the society to be crippled and die off.
Rules to be followed:
For a space that is populated:
- Has no or one neighbor will die, due to solitude.
- Has four or more neighbors dies, smothered due to overpopulation.
- Cell with two or three neighbors thrive.
For a space that is empty or unpopulated:
- Each cell with three neighbors becomes populated for growth.
Game of Life Code
The below life.css is simple. Interestingly, part is the td.isDead and td.isAlive. These classes are assigned to table-data or the cell. A dead cell is transparent and is alive when yellow. Some spaces are needed between the gridContainer and the buttons.
/*
Build the game of life using HTML, CSS & JavaScript
ToolboxAid.com
life.css
*/
body{
padding:20px;
background-color:#dddddd;
}
#gridContainer{
padding-bottom:10px;
}
table{
background: #444444;
border-spacing: 0px;
}
td{
border: 1px solid rgb(90,90,90);
width:10px;
height:10px;
}
td.isDead{
background-color: transparent;
}
td.isAlive{
background-color:#ffff00;
}
life.html is also simple. It has the gridContainer to hold the table and a div to hold the controls.
<!--
Build the game of life using HTML, CSS & JavaScript
ToolboxAid.com
life.html
-->
<!DOCTYPE html>
<head>
<meta charset = "UTF-8"/>
<title>The Game of Life</title>
<link rel="stylesheet" href="life.css">
<script src="life.js"></script>
</head>
<body>
<div id="gridContainer"> </div>
<div class="controls">
<button id="start">Start</button>
<button id="clear">Clear</button>
<button id="random">Random</button>
</div>
</body>
</html>
The JavaScript is not written in an object oriented way, however, it’s methods are ordered based on groupings that would be used as if it were an OO program. It is not a common practice to over document a program, as it is better to make your code read as if you are speaking. You accomplish this by using real words, not acronyms. In this manor, anyone picking up your code, can understand it.
boolean fields need to be written as: is, was, has. In this way, if you have isAlive, if true, you can understand without a deeper dive into the code.
Variable should be easily understood by using camelCase names. If you’re collecting the names of someone, it would be firstName, lastName & middleName.
function names should also be easily understood by using action words in camelCase. For instance, createTable, applyRules or playTheGame.
/*
Build the game of life using HTML, CSS & JavaScript
ToolboxAid.com
life.js
*/
var rows = 50;
var cols = 66;
var isPlaying = false;
var grid = new Array(rows);
var nextGrid = new Array(rows);
var timer;
var timerInterval = 100;
//Initialize
function initialize(){
createTable();
setupButtonControls();
initializeGrid();
resetGrids();
}
// lay out the board
function createTable(){
var gridContainer = document.getElementById("gridContainer");
if (!gridContainer){
// throw error
console.error("Problem: no gridContainer found");
}
var table = document.createElement("table");
for (var row = 0 ; row < rows; row++){
var tr = document.createElement("tr");
for (var col = 0 ; col < cols; col++){
var cell = document.createElement("td");
cell.setAttribute("id", row + "_" + col);
cell.setAttribute("class" ,"isDead");
cell.onclick = cellClicked;
tr.appendChild(cell);
}
table.appendChild(tr);
}
gridContainer.appendChild(table);
}
function initializeGrid(){
for (var row = 0; row < rows; row++){
grid[row] = new Array(cols);
nextGrid[row] = new Array(cols);
}
}
function resetGrids(){
for (var row = 0; row < rows; row++){
for (var col = 0; col < cols; col++){
grid[row][col] = 0;
nextGrid[row][col] = 0;
}
}
}
function copyAndResetGrid(){
for (var row = 0; row < rows; row++){
for (var col = 0; col < cols; col++){
grid[row][col] = nextGrid[row][col];
nextGrid[row][col] = 0;
}
}
}
function computeNextGrid(){
for (var row = 0 ; row < rows; row++){
for (var col = 0 ; col < cols; col++){
applyRules(row, col);
}
}
copyAndResetGrid();
updateView();
}
/* The rules
For a space that is populated:</p>
- Each cell with one or no neighbors dies, as if by solitude.
- Each cell with four or more neighbors dies, as if by overpopulation.
- Each cell with two or three neighbors survives.
For a space that is empty or unpopulated:</p>
- Each cell with three neighbors becomes populated.
*/
function applyRules(row, col){
var numHeighbors = countNeighbors(row, col);
if (grid[row][col] == 1){
// Is Alive
if (numHeighbors < 2){
nextGrid[row][col] = 0;
} else if (numHeighbors == 2 || numHeighbors == 3){
nextGrid[row][col] = 1;
} else if (numHeighbors > 3){
nextGrid[row][col] = 0;
}
// Is Dead
} else if (grid[row][col] == 0){
if (numHeighbors == 3){
nextGrid[row][col] = 1;
}
}
}
function countNeighbors(row, col){
var neighbors = 0;
// #1) -1 & -1 = up & left
if (row-1 >= 0 && col-1 >= 0){
if (grid[row-1][col-1] == 1 ) neighbors++;
}
// #2) 0 & -1 = up
if (col-1 >= 0){
if (grid[row][col-1] == 1 ) neighbors++;
}
// #3) +1 & -1 = up & right
if (row+1 < rows && col-1 >= 0){
if (grid[row+1][col-1] == 1 ) neighbors++;
}
// #4) -1 & 0 = left
if (row-1 >= 0){
if (grid[row-1][col] == 1 ) neighbors++;
}
// #5) +1 & 0 = right
if (row+1 < rows){
if (grid[row+1][col] == 1 ) neighbors++;
}
// #6) -1 & +1 = down & left
if (row-1 >= 0 && col+1 < cols){
if (grid[row-1][col+1] == 1 ) neighbors++;
}
// #7) 0 & +1 = down
if (col+1 < cols){
if (grid[row][col+1] == 1 ) neighbors++;
}
// #8) +1 & +1 = down & right
if (row+1 < rows && col+1 < cols){
if (grid[row+1][col+1] == 1 ) neighbors++;
}
return neighbors;
}
function cellClicked(){
var rowcol = this.id.split("_");
var row = rowcol[0];
var col = rowcol[1];
var classes = this.getAttribute("class");
console.info(classes);
if (classes.indexOf("isAlive") > -1){
this.setAttribute("class","isDead");
grid[row][col] = 0;
} else {
this.setAttribute("class","isAlive");
grid[row][col] = 1;
}
}
function setupButtonControls(){
// Start button
var startButton = document.getElementById("start");
startButton.onclick = startButtonHandler;
// Clear button
var clearButton = document.getElementById("clear");
clearButton.onclick = clearButtonHandler;
var randomButton = document.getElementById("random");
randomButton.onclick = randomButtonHandler;
}
function getRandomInt(max){
return Math.floor(Math.random() * max);
}
function randomButtonHandler(){
resetGrids();
for (var row = 0; row < rows; row++){
for (var col = 0; col < cols; col++){
grid[row][col] = getRandomInt(2);
}
}
updateView();
}
function startButtonHandler(){
if (isPlaying){
console.log("Pause the game");
isPlaying = false;
this.innerHTML = "Continue";
clearTimeout(timer);
} else {
console.log("Continue the game");
isPlaying = true;
this.innerHTML = "Pause";
playTheGame();
}
}
function clearButtonHandler(){
console.log("Clear the game & clear the grid");
isPlaying = false;
var startButton = document.getElementById("start");
startButton.innerHTML = "Start";
clearTimeout(timer);
/* bug
var liveCellList = document.getElementsByClassName("isAlive");
for (var i = 0; i < liveCellList.length; i++){
liveCellList[i].setAttribute("class","isDead");
}
*/
var liveCellList = document.getElementsByClassName("isAlive");
var cells = [];
for (var i = 0; i < liveCellList.length; i++){
cells.push(liveCellList[i]);
}
for (var i = 0; i < cells.length; i++){
cells[i].setAttribute("class","isDead");
}
resetGrids();
}
function playTheGame(){
console.log("Play the game");
computeNextGrid();
if (isPlaying){
timer = setTimeout(playTheGame,timerInterval);
}
}
function updateView(){
for (var row = 0; row < rows; row++){
for (var col = 0; col < cols; col++){
var cell = document.getElementById(row+"_"+col);
if (grid[row][col] == 0){
cell.setAttribute("class","isDead");
} else {
cell.setAttribute("class","isAlive");
}
}
}
}
//start everything
window.onload = initialize;
JavaScript coding standards
The filenames should be nouns as they are objects. Again, you should be using camelCase if needed. In the below list of files, it was not needed. However, if this program was written using OO, we would have a list of classes like: cell, table, input, or game.
You can download the sample code (or copy from above) and place all three (3) files in the same folder, then Double click life.html to run in your browser.
I’m thankful to see his accomplishments and see he is so humble.
Reference: