← Back to all posts

It's ALIVE!! Eat your heart out Conway!

published - |written by Joe Peterson

See the Pen Game of Life by Joe Peterson (@skullzarmy) on CodePen.

Initial Prompt

write a clean, well architected implementation of Conway's Game of Life in JavaScript. When the file is included in an HTML file it should be able to be invoked and passed a container id within which it should draw the game using HTML5 canvas


class GameOfLife { constructor(canvas, width, height, cellSize) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.width = width; this.height = height; this.cellSize = cellSize; this.columns = Math.floor(this.width / this.cellSize); this.rows = Math.floor(this.height / this.cellSize); this.grid = this.createGrid(); this.populateGrid(); } createGrid() { let grid = new Array(this.columns); for (let i = 0; i < grid.length; i++) { grid[i] = new Array(this.rows).fill(0); } return grid; } populateGrid() { for (let i = 0; i < this.columns; i++) { for (let j = 0; j < this.rows; j++) { this.grid[i][j] = Math.floor(Math.random() * 2); } } } drawGrid() { this.ctx.clearRect(0, 0, this.width, this.height); for (let i = 0; i < this.columns; i++) { for (let j = 0; j < this.rows; j++) { if (this.grid[i][j] === 1) { this.ctx.fillStyle = "#000"; this.ctx.fillRect( i * this.cellSize, j * this.cellSize, this.cellSize, this.cellSize, ); } } } } getNextGridState() { let newGrid = this.createGrid(); for (let i = 0; i < this.columns; i++) { for (let j = 0; j < this.rows; j++) { let neighbors = this.countNeighbors(i, j); if (this.grid[i][j] === 1 && (neighbors === 2 || neighbors === 3)) { newGrid[i][j] = 1; } else if (this.grid[i][j] === 0 && neighbors === 3) { newGrid[i][j] = 1; } } } return newGrid; } countNeighbors(x, y) { let count = 0; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { if (i === 0 && j === 0) continue; let newX = (x + i + this.columns) % this.columns; let newY = (y + j + this.rows) % this.rows; count += this.grid[newX][newY]; } } return count; } update() { this.grid = this.getNextGridState(); this.drawGrid(); } } function initGameOfLife(containerId, width = 800, height = 800, cellSize = 10) { const container = document.getElementById(containerId); const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; container.appendChild(canvas); const gameOfLife = new GameOfLife(canvas, width, height, cellSize); function gameLoop() { gameOfLife.update(); requestAnimationFrame(gameLoop); } gameLoop(); } // Usage example: // Include this JavaScript file in your HTML and then use the following line to initialize the Game of Life: initGameOfLife("game-of-life", 800, 800, 10);


... that was easy...

Follow Up Prompt

would you keep count of the current generation, add a print out of it on the bottom right corner "Current Generation: XXX" that counts up each time you loop the game. Also, when (if) it runs out of updates to make and the game reaches a stale state, please update the print to Game Over! Total Generations: xxxx and then stop the loop


I've updated the implementation to include the generation counter, display it on the bottom right corner, and stop the loop when the game reaches a stale state:

class GameOfLife { // ... (Previous class implementation) drawGenerationCounter() { this.ctx.fillStyle = "#000"; this.ctx.font = "16px Arial"; this.ctx.fillText( `Current Generation: ${this.generations}`, this.width - 200, this.height - 10, ); } isGridEqual(grid1, grid2) { for (let i = 0; i < this.columns; i++) { for (let j = 0; j < this.rows; j++) { if (grid1[i][j] !== grid2[i][j]) { return false; } } } return true; } update() { const newGrid = this.getNextGridState(); if (this.isGridEqual(this.grid, newGrid)) { this.drawGameOver(); return; } this.grid = newGrid; this.drawGrid(); this.drawGenerationCounter(); this.generations++; } drawGameOver() { this.ctx.fillStyle = "#000"; this.ctx.font = "16px Arial"; this.ctx.fillText( `Game Over! Total Generations: ${this.generations}`, this.width - 300, this.height - 10, ); } } function initGameOfLife(containerId, width = 800, height = 800, cellSize = 10) { // ... (Previous initGameOfLife implementation) gameOfLife.generations = 0; function gameLoop() { gameOfLife.update(); if (!gameOfLife.isGameOver) { requestAnimationFrame(gameLoop); } } gameLoop(); } // Usage example: // Include this JavaScript file in your HTML and then use the following line to initialize the Game of Life: // initGameOfLife("containerId", 800, 800, 10);

Now, the generation counter will display at the bottom right corner and update each time the game loops. When the game reaches a stale state, the text will change to "Game Over! Total Generations: XXXX", and the loop will stop.


I realize now that "stale state" wasn't really a fair request without defining further, since invariably you will end up with infintely recursive shapes that always have a change, yet never really do. It was at this point I realized it was nearly 12:30 in the morning and that was good enough...

Recommended Posts