A Pen by Michael Rubanov on CodePen.
Created
November 2, 2021 22:10
-
-
Save micrub/e9a6e7f80d4935710267eb47f3cc6002 to your computer and use it in GitHub Desktop.
Conway's Game of Life - JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<meta charset = "UTF-8" /> | |
<title>The Game of Life</title> | |
<link rel="stylesheet" href="life.css"> | |
</head> | |
<body> | |
<div id="hud" /> | |
<div id="gridContainer" /> | |
<div class="controls"> | |
<button id="start"><span>Start</span></button> | |
<button id="clear"><span>Clear</span></button> | |
<button id="random"><span>Random</span></button> | |
</div> | |
<script src="code.js"></script> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ROWS = 25; | |
const COLS = 50; | |
let IS_PLAYING = false; | |
let GRID = new Array(ROWS); | |
let NEXT_GRID = new Array(ROWS); | |
const REPRODUCTION_TIME = 100; | |
let epochs = 0; | |
function initializeGrids() { | |
for (var i = 0; i < ROWS; i++) { | |
GRID[i] = new Array(COLS); | |
NEXT_GRID[i] = new Array(COLS); | |
} | |
} | |
function resetGrids() { | |
for (var i = 0; i < ROWS; i++) { | |
for (var j = 0; j < COLS; j++) { | |
GRID[i][j] = 0; | |
NEXT_GRID[i][j] = 0; | |
} | |
} | |
} | |
function copyAndResetGrid() { | |
for (var i = 0; i < ROWS; i++) { | |
for (var j = 0; j < COLS; j++) { | |
GRID[i][j] = NEXT_GRID[i][j]; | |
NEXT_GRID[i][j] = 0; | |
} | |
} | |
} | |
// Initialize | |
function initialize() { | |
createHud(); | |
createTable(); | |
initializeGrids(); | |
resetGrids(); | |
setupControlButtons(); | |
} | |
function createHud() { | |
var hudCont = document.getElementById('hud'); | |
if (!hudCont) { | |
// Throw error | |
console.error("Problem: No div for the hud info!"); | |
} | |
const label = document.createElement("label"); | |
const span = document.createElement("span"); | |
label.textContent = 'Epochs passed: '; | |
span.textContent = epochs; | |
hudCont.appendChild(label); | |
hudCont.appendChild(span); | |
} | |
// Lay out the board | |
function createTable() { | |
var gridContainer = document.getElementById('gridContainer'); | |
if (!gridContainer) { | |
// Throw error | |
console.error("Problem: No div for the drid table!"); | |
} | |
var table = document.createElement("table"); | |
for (var i = 0; i < ROWS; i++) { | |
var tr = document.createElement("tr"); | |
for (var j = 0; j < COLS; j++) {// | |
var cell = document.createElement("td"); | |
cell.setAttribute("id", i + "_" + j); | |
cell.setAttribute("class", "dead"); | |
cell.onclick = cellClickHandler; | |
tr.appendChild(cell); | |
} | |
table.appendChild(tr); | |
} | |
gridContainer.appendChild(table); | |
} | |
function cellClickHandler() { | |
var rowcol = this.id.split("_"); | |
var row = rowcol[0]; | |
var col = rowcol[1]; | |
var classes = this.getAttribute("class"); | |
if (classes.indexOf("live") > -1) { | |
this.setAttribute("class", "dead"); | |
GRID[row][col] = 0; | |
} else { | |
this.setAttribute("class", "live"); | |
GRID[row][col] = 1; | |
} | |
} | |
function updateView() { | |
for (var i = 0; i < rows; i++) { | |
for (var j = 0; j < cols; j++) { | |
var cell = document.getElementById(i + "_" + j); | |
if (GRID[i][j] == 0) { | |
cell.setAttribute("class", "dead"); | |
} else { | |
cell.setAttribute("class", "live"); | |
} | |
} | |
} | |
} | |
function setupControlButtons() { | |
// button to start | |
var startButton = document.getElementById('start'); | |
startButton.onclick = startButtonHandler; | |
// button to clear | |
var clearButton = document.getElementById('clear'); | |
clearButton.onclick = clearButtonHandler; | |
// button to set random initial state | |
var randomButton = document.getElementById("random"); | |
randomButton.onclick = randomButtonHandler; | |
} | |
function randomButtonHandler() { | |
if (IS_PLAYING) return; | |
clearButtonHandler(); | |
for (var i = 0; i < rows; i++) { | |
for (var j = 0; j < cols; j++) { | |
var isLive = Math.round(Math.random()); | |
if (isLive == 1) { | |
var cell = document.getElementById(i + "_" + j); | |
cell.setAttribute("class", "live"); | |
GRID[i][j] = 1; | |
} | |
} | |
} | |
} | |
// clear the grid | |
function clearButtonHandler() { | |
console.log("Clear the game: stop playing, clear the grid"); | |
IS_PLAYING = false; | |
var startButton = document.getElementById('start'); | |
startButton.innerHTML = "Start"; | |
clearTimeout(timer); | |
var cellsList = document.getElementsByClassName("live"); | |
// convert to array first, otherwise, you're working on a live node list | |
// and the update doesn't work! | |
var cells = []; | |
for (var i = 0; i < cellsList.length; i++) { | |
cells.push(cellsList[i]); | |
} | |
for (var i = 0; i < cells.length; i++) { | |
cells[i].setAttribute("class", "dead"); | |
} | |
resetGrids; | |
} | |
// start/pause/continue the game | |
function startButtonHandler() { | |
if (IS_PLAYING) { | |
console.log("Pause the game"); | |
IS_PLAYING = false; | |
this.innerHTML = "Continue"; | |
clearTimeout(timer); | |
} else { | |
console.log("Continue the game"); | |
IS_PLAYING = true; | |
this.innerHTML = "Pause"; | |
play(); | |
} | |
} | |
// run the life game | |
function play() { | |
computeNextGen(); | |
if (IS_PLAYING) { | |
timer = setTimeout(play, REPRODUCTION_TIME); | |
} | |
} | |
function computeNextGen() { | |
for (var i = 0; i < rows; i++) { | |
for (var j = 0; j < cols; j++) { | |
applyRules(i, j); | |
} | |
} | |
// copy NextGrid to grid, and reset nextGrid | |
copyAndResetGrid(); | |
// copy all 1 values to "live" in the table | |
updateView(); | |
} | |
// RULES | |
// 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 overcrowding. | |
// Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. | |
function applyRules(row, col) { | |
var numNeighbors = countNeighbors(row, col); | |
if (GRID[row][col] == 1) { | |
if (numNeighbors < 2) { | |
NEXT_GRID[row][col] = 0; | |
} else if (numNeighbors == 2 || numNeighbors == 3) { | |
NEXT_GRID[row][col] = 1; | |
} else if (numNeighbors > 3) { | |
NEXT_GRID[row][col] = 0; | |
} | |
} else if (GRID[row][col] == 0) { | |
if (numNeighbors == 3) { | |
NEXT_GRID[row][col] = 1; | |
} | |
} | |
} | |
function countNeighbors(row, col) { | |
var count = 0; | |
if (row - 1 >= 0) { | |
if (GRID[row - 1][col] == 1) count++; | |
} | |
if (row - 1 >= 0 && col - 1 >= 0) { | |
if (GRID[row - 1][col - 1] == 1) count++; | |
} | |
if (row - 1 >= 0 && col + 1 < cols) { | |
if (GRID[row - 1][col + 1] == 1) count++; | |
} | |
if (col - 1 >= 0) { | |
if (GRID[row][col - 1] == 1) count++; | |
} | |
if (col + 1 < cols) { | |
if (GRID[row][col + 1] == 1) count++; | |
} | |
if (row + 1 < rows) { | |
if (GRID[row + 1][col] == 1) count++; | |
} | |
if (row + 1 < rows && col - 1 >= 0) { | |
if (GRID[row + 1][col - 1] == 1) count++; | |
} | |
if (row + 1 < rows && col + 1 < cols) { | |
if (GRID[row + 1][col + 1] == 1) count++; | |
} | |
return count; | |
} | |
// Start everything | |
window.onload = initialize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
padding: 20px; | |
background-color: #444; | |
} | |
#gridContainer { | |
padding-bottom: 10px; | |
} | |
table { | |
background-color: #C5D6C6; | |
border-spacing: 0; | |
} | |
td { | |
border: 2px solid #F1F5DA; | |
border-radius: 3px; | |
width: 10px; | |
height: 10px; | |
} | |
span { | |
color: #222; | |
} | |
#start, | |
#clear, | |
#random { | |
padding: .75em; | |
border-radius: 5px; | |
border: none; | |
background: linear-gradient( | |
to bottom right, | |
#C5DEC6, #587559); | |
} | |
td.dead { | |
background-color: transparent; | |
} | |
td.live { | |
background-color: #CC4774; | |
border-radius: 10px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment