Skip to content

Instantly share code, notes, and snippets.

@sal-ortiz
Last active May 10, 2021 02:48
Show Gist options
  • Save sal-ortiz/d85dab6f438f76883c94b1f26590f98c to your computer and use it in GitHub Desktop.
Save sal-ortiz/d85dab6f438f76883c94b1f26590f98c to your computer and use it in GitHub Desktop.
A JavaScript implementation of John Conway's Game of Life
<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>
<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>
/*
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