Last active
May 10, 2021 02:48
-
-
Save sal-ortiz/d85dab6f438f76883c94b1f26590f98c to your computer and use it in GitHub Desktop.
A JavaScript implementation of John Conway's Game of Life
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
<html> | |
<head> | |
<style> | |
body { | |
/* our entire document */ | |
background-color: darkgrey; | |
} | |
#field { | |
/* the html canvas object defined in our document */ | |
border-style: solid; | |
border-width: 5px; | |
border-radius: 10px; | |
border-color: black; | |
} | |
</style> | |
<!-- a common module supporting our implementations. --> | |
<script src='game.js'></script> | |
<script> | |
function nextDay(baseData) { | |
let height = baseData.length // our data's outer array. | |
let width = baseData[0].length // our data's inner array. | |
let data = Game.initializeData(width, height) // an empty workspace. | |
for (let heightIdx = 0; heightIdx < height; heightIdx++) { | |
// iterate through our vertical positions. | |
for (let widthIdx = 0; widthIdx < width; widthIdx++) { | |
// iterate through our horizontal positions. | |
let count = 0 | |
let cell = baseData[heightIdx][widthIdx] | |
for (let xModifier = -1; xModifier < 2; xModifier++) { | |
// iterate through some horizontal modifiers: -1 to 1. | |
for (let yModifier = -1; yModifier < 2; yModifier++) { | |
// iterate through some vertical modifiers: -1 to 1. | |
let xPos = widthIdx + xModifier | |
let yPos = heightIdx + yModifier | |
if (xModifier == 0 && yModifier == 0) { | |
// do not count our current position as a neighbor. | |
continue | |
} | |
if (xPos < 0) { | |
// wrap our horizontal position to the right. | |
xPos = width - 1 | |
} | |
if (yPos < 0) { | |
// wrap our vertical position to the bottom. | |
yPos = height - 1 | |
} | |
if (xPos >= width) { | |
// wrap our horizontal position to the left. | |
xPos = 0 | |
} | |
if (yPos >= height) { | |
// wrap our vertical position to the top. | |
yPos = 0 | |
} | |
if (baseData[yPos][xPos] == 1) { | |
// a cell exists so we increment our neighbor count. | |
count++ | |
} | |
} | |
} | |
if (cell == 1) { | |
// our current space is occupied. | |
if (count < 2 || count > 3) { | |
// an occupied space with less than two or more than three | |
// neighbors becomes unoccupied in the next generation. | |
data[heightIdx][widthIdx] = 0 | |
} else { | |
// an occupied space with two or three neighbors | |
// remains occupied in the next generation.. | |
data[heightIdx][widthIdx] = 1 | |
} | |
} else { | |
// our current space is empty. | |
if (count == 3) { | |
// an unoccupied space with exactly three neighbors | |
// becomes occupied in the next generation. | |
data[heightIdx][widthIdx] = 1 | |
} else { | |
// an unoccupied space with fewer or more than three | |
// neighbors remains unoccupied in the next generation. | |
data[heightIdx][widthIdx] = 0 | |
} | |
} | |
} | |
} | |
return data | |
} | |
function gameLoop(numDays, field, data, scale) { | |
// Our runtime loop. | |
// draw the fied using our field's initial state. | |
Game.drawField(field, data, scale) | |
// calculate the nect generation. | |
data = nextDay(data) | |
numDays-- // count down the number of generations. | |
if (numDays > 0) { | |
// set up the next occurrence of our game loop. | |
setTimeout(gameLoop, 100, numDays, field, data, scale) | |
} | |
} | |
</script> | |
<script> | |
window.addEventListener('DOMContentLoaded', function() { | |
// all contenxt has loaded. our app begins here. | |
let field = document.getElementById('field') | |
let scale = 6.0 // zoom modifier for our field. | |
let width = 100 // our field's base width. | |
let height = 100 // our field's base height. | |
let numDays = 200 // the total number of generations. | |
// the number of cells comprising our initial state. | |
let numCells = Math.floor((width * height) / 2) | |
// create an empty state data object. | |
let data = Game.initializeData(width, height) | |
field.width = width * scale // our field's width, scaled. | |
field.height = height * scale // our field's height, scaled. | |
// place a given number of cells at random locations. | |
data = Game.randomizeField(data, numCells) | |
// initiate our application's runtime. | |
gameLoop(numDays, field, data, scale) | |
}) | |
</script> | |
</head> | |
<body> | |
<canvas id='field'> | |
<!-- intentionally left blank --> | |
</canvas> | |
</body> | |
</html> |
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
<html> | |
</head> | |
<style> | |
body { | |
/* our entire document */ | |
background-color: darkgrey; | |
} | |
#field { | |
/* the html canvas object defined in our document */ | |
border-style: solid; | |
border-width: 5px; | |
border-radius: 10px; | |
border-color: black; | |
} | |
</style> | |
<script> | |
function randomizeField(data, numCells) { | |
let cellCount = numCells; | |
while (cellCount > 0) { | |
let idx = parseInt(Math.random() * data.length); | |
if (data[idx] != 1) { | |
// place a new cell only where none exist. | |
data[idx] = 1; | |
cellCount--; | |
} | |
} | |
return data; | |
} | |
function drawField(data, width, height, scale) { | |
let buffer = document.createElement('canvas'); // our buffer context. | |
let field = document.getElementById('field'); // our target context. | |
buffer.width = field.width; | |
buffer.height = field.height; | |
let bufContext = buffer.getContext('2d'); // our buffer context. | |
let fieldContext = field.getContext('2d'); // our target context. | |
for (let idx = 0; idx < data.length; idx++) { | |
let vertIdx = idx % width; | |
let horzIdx = Math.floor(idx / width); | |
let cell = data[idx]; | |
if (cell == 1) { | |
// an occupied space's color. | |
bufContext.fillStyle = 'black'; | |
} else { | |
// an empty space's color. | |
bufContext.fillStyle = 'lightgrey'; | |
} | |
// draw our pixel, to scale. | |
bufContext.fillRect( | |
horzIdx * scale, | |
vertIdx * scale, | |
(horzIdx + scale) * scale, | |
(vertIdx + scale) * scale | |
); | |
} | |
fieldContext.drawImage(buffer, 0, 0); | |
} | |
function nextDay(baseData, width, height) { | |
let data = new Array(width * height); | |
data.fill(0); | |
for (let idx = 0; idx < baseData.length; idx++) { | |
let widthIdx = idx % width; | |
let heightIdx = Math.floor(idx / width); | |
let count = 0 | |
let cell = baseData[idx] || 0; | |
for (let posModifier = 0; posModifier < 9; posModifier++) { | |
if (posModifier == 4) { | |
continue | |
} | |
let xModifier = (posModifier % 3) - 1; | |
let yModifier = Math.floor(posModifier / 3) - 1; | |
let xModified = widthIdx + xModifier; | |
let yModified = heightIdx + yModifier; | |
let xPos = ((xModified % width) + width) % width | |
let yPos = ((yModified % height) + height) % height | |
let baseIdx = (yPos * width) + xPos; | |
if (baseData[baseIdx] == 1) { | |
// a cell exists so we increment our neighbor count. | |
count++ | |
} | |
} | |
if (cell == 1) { | |
// our current space is occupied. | |
if (count < 2 || count > 3) { | |
// an occupied space with less than two or more than three | |
// neighbors becomes unoccupied in the next generation. | |
data[idx] = 0 | |
} else { | |
// an occupied space with two or three neighbors | |
// remains occupied in the next generation.. | |
data[idx] = 1 | |
} | |
} else { | |
// our current space is empty. | |
if (count == 3) { | |
// an unoccupied space with exactly three neighbors | |
// becomes occupied in the next generation. | |
data[idx] = 1 | |
} else { | |
// an unoccupied space with fewer or more than three | |
// neighbors remains unoccupied in the next generation. | |
data[idx] = 0 | |
} | |
} | |
} | |
return data | |
} | |
function gameLoop(numDays, data, width, height, scale) { | |
// Our runtime loop. | |
// draw the fied using our field's initial state. | |
drawField(data, width, height, scale); | |
// calculate the nect generation. | |
data = nextDay(data, width, height) | |
numDays-- // count down the number of generations. | |
if (numDays > 0) { | |
// set up the next occurrence of our game loop. | |
setTimeout(gameLoop, 100, numDays, data, width, height, scale) | |
} | |
} | |
</script> | |
<script> | |
window.addEventListener('DOMContentLoaded', function() { | |
// all content has loaded. our app begins here. | |
let field = document.getElementById('field') | |
let scale = 8.0 // zoom modifier for our field. | |
let width = 100 // our field's base width. | |
let height = 100 // our field's base height. | |
let numDays = 40 // the total number of generations. | |
// the number of cells comprising our initial state. | |
let numCells = Math.floor((width * height) / 2) | |
// create an empty state data object. | |
let data = new Array(width * height); | |
data.fill(0); | |
field.width = width * scale // our field's width, scaled. | |
field.height = height * scale // our field's height, scaled. | |
// place a given number of cells at random locations. | |
data = randomizeField(data, numCells); | |
// initiate our application's runtime. | |
gameLoop(numDays, data, width, height, scale) | |
}) | |
</script> | |
</head> | |
<body> | |
<canvas id='field'> | |
<!-- intentionally left blank --> | |
</canvas> | |
</body> | |
</html> |
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
/* | |
A module intended to handle the majority of | |
of platform-specific functions associated | |
with this implementation of The Game of Life. | |
*/ | |
class Game { | |
static initializeData(width, height) { | |
// intialize empty state data. | |
let data = new Array(height); // our data's outer array. | |
for (let fillIdx = 0; fillIdx < height; fillIdx++) { | |
let ary = new Array(width); // our data's nested array. | |
ary.fill(0); | |
data[fillIdx] = ary; | |
} | |
return data; | |
} | |
static drawField(field, data, scale) { | |
// draw our field of cells. | |
let height = data.length; // our data's outer array. | |
let width = data[0].length; // our data's nested array. | |
let context = field.getContext('2d'); // our target surface. | |
for (let vertIdx = 0; vertIdx < height; vertIdx++) { | |
// iterate through our vertical positions. | |
for (let horzIdx = 0; horzIdx < width; horzIdx++) { | |
// iterate through our horizontal positions. | |
// retrieve the cell at a given coordinate. | |
let cell = data[vertIdx][horzIdx]; | |
if (cell == 1) { | |
// an occupied space's color. | |
context.fillStyle = 'black'; | |
} else { | |
// an empty space's color. | |
context.fillStyle = 'lightgrey'; | |
} | |
// draw our pixel, to scale. | |
context.fillRect( | |
horzIdx * scale, | |
vertIdx * scale, | |
(horzIdx + scale) * scale, | |
(vertIdx + scale) * scale | |
); | |
} | |
} | |
} | |
static randomizeField(data, numCells) { | |
// populate the given number of spaces at random locations. | |
let cellCount = numCells; | |
let width = data[0].length; | |
let height = data.length; | |
while (cellCount > 0) { | |
// iterate once for each individual cell actually placed. | |
let xPos = parseInt(Math.random() * width); | |
let yPos = parseInt(Math.random() * height); | |
if (data[yPos][xPos] != 1) { | |
// place a new cell only where none exist. | |
data[yPos][xPos] = 1; | |
cellCount--; | |
} | |
} | |
return data; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment