Enhancing Skills

The high and mighty John Conway’s “Game of Life” using HTML, CSS & JS – Free

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:

Leave a Comment

Your email address will not be published. Required fields are marked *

Category Image
Toolbox Aid
Post Calendar
October 2021
S M T W T F S
 12
3456789
10111213141516
17181920212223
24252627282930
31  
Blog categories
Product tags