The high and mighty John Conway’s “Game of Life” using HTML, CSS & JS
September 12th, 2021 10:35 AM Mr. Q Categories: CSS, HTML, JavaScript
Hey guys, today we’re gonna make a cool game called John Conway’s Game of Life using HTML, CSS, and JavaScript. John Conway is a really smart math guy that we should look up to. The Game of Life is a game that nobody plays, it’s kinda weird. Each square on the game board has eight friends (up, down, left, right, and diagonally) that it talks to. Is it a game? Let me know what you think in the comments!
Game of Life Code
So there’s this game called The Game of Life and it was made because people were trying to solve a problem. The problem is, if we ever go to Mars, how do we make sure everyone has enough stuff to survive? The game has rules that help decide if the society in the game will survive or not. The rules are about how many cells are living in each square. If there are too many, they die fast, and if there are too few, they can’t survive and die off. So it’s like a little simulation to help us think about how to keep people alive on Mars!
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.
The below life.css and it’s pretty simple. What’s cool is that it has two classes: td.isDead and td.isAlive. These classes are used for the cells in the table. A dead cell looks clear and a live cell looks yellow. Also, we need some space 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;
}</pre>
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></pre>
The JavaScript code for this program isn’t written in an object-oriented way, but the methods are organized into groups that would be used in an object-oriented program. It’s not really common to write too many comments in your code because it’s better to write your code in a way that’s easy to understand, like how you would speak. You can do this by using real words instead of abbreviations. That way, anyone who reads your code can understand what it does.
boolean fields need to be written as: is, was, and has. In this way, if you have isAlive, if true, you can understand without a deeper dive into the code.
The 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;</pre>
Sure, the number of lines can be reduced using loops, but that is for another day.
JavaScript coding standards
The filenames should be nouns as they are objects. You should be using camelCase if needed. In the below list of files, it was not needed. However, if this program was written using OOP, 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: