It took some time to make Conway's Game of Life in HTML, CSS, JavaScript and jQuery. I'd like suggestions, criticisms, and discussions on how it can be done better.
/*
* Conway's - Game of Life.
* Any live cell with fewer than two live neighbours dies, as if caused by under-population.
* Any live cell with two or three live neighbours lives on to the next generation.
* Any live cell with more than three live neighbours dies, as if by over-population.
* Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
*/
'use strict';
/*
* Representation of each cell on the canvas.
* "row" and "col" stores the location of the cell.
* The "alive" property stores whether the cell is alive or dead.
*/
function Cell(row, col) {
var _this = this,
$this = null,
alive = false;
this.activate = function () {
alive = true;
$this.addClass('alive');
};
this.deActivate = function () {
alive = false;
$this.removeClass('alive');
};
this.isAlive = function () {
return alive;
};
this.getRow = function () {
return row;
};
this.getCol = function () {
return col;
};
this.getJqueryElement = function () {
return $this;
};
// If $this is not yet defined, create a new HTML element.
if (null === $this) {
$this = $('<div>').addClass('conway-cell').data('cell', _this);
}
return this;
}
/**
* The main logic of the game goes here.
*/
function ConwayGame(selector, numRows, numCols) {
var $parent = $(selector),
_this = this,
rows = numRows,
cols = numCols,
cells = [],
lifeMap = [],
intervalTime = 500,
intervalId;
this.getSpeed = function () {
return intervalTime;
};
// Initialize the list of cells required. Add the same to the HTML parent element.
var initialize = function () {
for (var i = 0; i < rows; i++) {
cells[i] = [];
var $row = $('<div>').addClass('conway-row');
for (var j = 0; j < cols; j++) {
var cell = cells[i][j] = new Cell(i, j);
var $cell = cell.getJqueryElement();
// Add click handler for the Cell.
$cell.on('click', function (event) {
var cellObj = $(this).data('cell');
if (cellObj.isAlive()) {
cellObj.deActivate()
} else {
cellObj.activate();
}
_this.reMap();
});
$row.append($cell);
}
// Add the HTML Row to the Parent element.
$parent.append($row);
}
};
// Re-draw the Elements based on their status, if they are alive or not.
this.reDraw = function () {
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
var cell = cells[i][j];
cell.isAlive() ? cell.activate() : cell.deActivate();
}
}
};
// Get the count of immediate neighbors for the cell.
this.getNeighborsCount = function (cell) {
var neighbors = 0,
row = cell.getRow(),
col = cell.getCol();
// Top Left to Top Right
if (cells[row - 1]) {
if (cells[row - 1][col - 1] && cells[row - 1][col - 1].isAlive()) neighbors++;
if (cells[row - 1][col] && cells[row - 1][col].isAlive()) neighbors++;
if (cells[row - 1][col + 1] && cells[row - 1][col + 1].isAlive()) neighbors++;
}
// Middle Left to Middle Right. Ignore the current cell.
if (cells[row][col - 1] && cells[row][col - 1].isAlive()) neighbors++;
if (cells[row][col + 1] && cells[row][col + 1].isAlive()) neighbors++;
// Bottom Left to Bottom Right.
if (cells[row + 1]) {
if (cells[row + 1][col - 1] && cells[row + 1][col - 1].isAlive()) neighbors++;
if (cells[row + 1][col] && cells[row + 1][col].isAlive()) neighbors++;
if (cells[row + 1][col + 1] && cells[row + 1][col + 1].isAlive()) neighbors++;
}
return neighbors;
};
this.reMap = function () {
for (var i = 0; i < rows; i++) {
lifeMap[i] = [];
for (var j = 0; j < cols; j++) {
var cell = cells[i][j];
lifeMap[i][j] = _this.getNeighborsCount(cell);
}
}
};
this.getNextLife = function () {
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
var cell = cells[i][j];
var lifeValue = lifeMap[i][j];
if (cell.isAlive()) {
if (lifeValue < 2 || lifeValue > 3) {
cell.deActivate();
}
} else {
if (lifeValue === 3) {
cell.activate();
}
}
}
}
_this.reMap();
};
this.next = function () {
_this.getNextLife();
_this.reDraw();
};
this.play = function () {
intervalId = setInterval(_this.next, intervalTime);
};
this.pause = function () {
clearInterval(intervalId);
};
this.increaseSpeed = function () {
if (intervalTime > 100) {
intervalTime -= 100;
}
_this.pause();
_this.play();
};
this.decreaseSpeed = function () {
if (intervalTime < 2000) {
intervalTime += 100;
}
_this.pause();
_this.play();
};
if (cells.length === 0) {
initialize();
}
return this;
}
// Run as soon as the Document is Ready.
$(function () {
var game = new ConwayGame('.conway-game', 20, 20);
$('#nextButton').on('click', function () {
game.next();
});
$('#playButton').on('click', function () {
game.play();
});
$('#pauseButton').on('click', function () {
game.pause();
});
$('#speedUpButton').on('click', function () {
game.increaseSpeed();
});
$('#slowDownButton').on('click', function () {
game.decreaseSpeed();
});
});
.conway-game {
display: table;
}
.conway-row {
display: table-row;
}
.conway-cell {
display: table-column;
float: left;
width: 12px;
height: 12px;
border: 1px solid #CCE8AF;
}
.alive {
background-color: #7FC539;
}
.controls {
margin-top: 10px;
}
.controls button {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="conway-game"></div>
<div class="controls">
<button id="playButton">Play</button>
<button id="pauseButton">Pause</button>
<button id="nextButton">Step</button>
<button id="speedUpButton">Speed Up</button>
<button id="slowDownButton">Slow Down</button>
</div>
requestanimationframe
method. I did take a look at ES6, I don't think I'll be changing my code for that. Thanks for the comments. :) \$\endgroup\$ – LearningDeveloper Dec 2 '15 at 10:16