Last active
November 26, 2021 19:27
-
-
Save holmberd/c57a1b6ad744f6a6e795252e24c98f22 to your computer and use it in GitHub Desktop.
Game of life Javascript [2]
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
/** | |
* Conway's Game of Life | |
* | |
* The world of the Game of Life is an infinite two-dimensional orthogonal grid of square | |
* "cells", each of which is in one of two possible states, alive or dead. | |
* | |
* Rules: | |
* 1. Any live cell with fewer than two live neighbours dies, as if by underpopulation. | |
* 2. Any live cell with two or three live neighbours lives on to the next generation. | |
* 3. Any live cell with more than three live neighbours dies, as if by overpopulation. | |
* 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. | |
* | |
* Example usage: | |
* const boardScheme = '......\n' + | |
* '***...\n' + | |
* '......\n' + | |
* '......\n' + | |
* '......\n' + | |
* '......\n'; | |
* const world = World.createFromScheme(boardScheme); | |
* world.evolve(); | |
* world.toString(); | |
* | |
* Output: | |
* | |
* .*.... | |
* .*.... | |
* .*.... | |
* ...... | |
* ...... | |
* ...... | |
*/ | |
class Cell { | |
constructor(state) { | |
this.state = state; | |
} | |
} | |
class Location { | |
constructor(rowIndex, colIndex) { | |
this.row = rowIndex; | |
this.col = colIndex; | |
} | |
} | |
var _board = []; | |
class World { | |
constructor(board = [], rows, cols) { | |
_board = board; | |
this.generation = 0; | |
this.rows = board.length; | |
this.cols = board[0].length; | |
// B3/S23 (Conway's Life) | |
this.rule = { | |
born: 3, | |
survival: [2, 3], | |
}; | |
} | |
getBoard() { | |
return _board; | |
} | |
setBoard(newBoard = []) { | |
_board = newBoard; | |
} | |
getCell(location) { | |
var board = this.getBoard(); | |
return board[location.row][location.col]; | |
} | |
setCell(location, cell) { | |
var board = this.getBoard(); | |
board[location.row][location.col] = cell; | |
return this; | |
} | |
evolve() { | |
try { | |
// Evolve board. | |
var newBoard = []; | |
// Evolve each row. | |
for (var rowIndex = 0; rowIndex < this.rows; rowIndex++) { | |
// Evolve each column in each row. | |
for (var colIndex = 0; colIndex < this.cols; colIndex++) { | |
var location = new Location(rowIndex, colIndex); | |
var cell = this.getCell(location); | |
if (!newBoard[location.row]) { | |
newBoard[location.row] = []; | |
} | |
// Evolve each cell. | |
newBoard[location.row][location.col] = this.evolveCell(location, cell); | |
} | |
} | |
this.setBoard(newBoard); | |
this.generation = this.generation + 1; | |
} catch(err) { | |
return false; | |
} | |
return this; | |
} | |
evolveCell(location, cell) { | |
var cellNeighbourPositions = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]]; | |
var aliveNeighbours = 0; | |
var neighbourLocation = null; | |
var neighbourCell = null; | |
// Calculate alive neighbours count. | |
for (var i = 0; i < cellNeighbourPositions.length; i++) { | |
neighbourLocation = new Location( | |
location.row + cellNeighbourPositions[i][0], | |
location.col + cellNeighbourPositions[i][1] | |
); | |
// Only check cell neighbour if it's within the world bounds. | |
var inBounds = true; | |
if (neighbourLocation.row < 0 || neighbourLocation.row > (this.rows - 1)) { | |
inBounds = false; | |
} | |
if (neighbourLocation.col < 0 || neighbourLocation.col > (this.cols - 1)) { | |
inBounds = false; | |
} | |
if (inBounds == true) { | |
neighbourCell = this.getCell(neighbourLocation); | |
// Increase alive neighbour count if cell is alive. | |
if (neighbourCell.state) { | |
aliveNeighbours++; | |
} | |
} | |
} | |
// Appy rules on cell. | |
if (cell.state == true) { | |
for (var j = 0; j < this.rule.survival.length; j++) { | |
if (this.rule.survival[j] == aliveNeighbours) { | |
return new Cell(true); | |
} | |
} | |
return new Cell(false); | |
} else { | |
if (this.rule.born == aliveNeighbours) { | |
return new Cell(true); | |
} | |
return new Cell(false); | |
} | |
} | |
static createFromScheme(boardScheme) { | |
return new World(World.createBoard(boardScheme)); | |
} | |
static createBoard(boardScheme) { | |
var board = World.getBoardRows(boardScheme); | |
for (var rowIndex = 0; rowIndex < board.length; rowIndex++) { | |
board[rowIndex] = World.createRowCells(board[rowIndex]); | |
} | |
return board; | |
} | |
static getBoardRows(boardScheme) { | |
return boardScheme.split('\n').slice(0, -1); | |
} | |
static createRowCells(row) { | |
var chars = []; | |
for (var char of row.split('')) { | |
chars.push(World.convertCharToCell(char)); | |
} | |
return chars; | |
} | |
static convertCharToCell(char) { | |
return new Cell(char == '*'); | |
} | |
// Converts a World instance to a string. | |
toString() { | |
var str = ''; | |
for (var row = 0; row < this.rows; row++) { | |
for (var col = 0; col < this.cols; col++) { | |
if (this.getBoard()[row][col].state) { | |
// Note: While JavaScript strings are immutable as in Java, a StringBuilder is generally not required | |
// for optimizing string concatenation. Though in certain environments `Array.join()` can be more performant | |
// when building very large strings. | |
str += '*'; | |
} | |
else { | |
str += '.'; | |
} | |
} | |
str += '\n'; | |
} | |
return str; | |
} | |
} | |
function main() { | |
var boardScheme = | |
'.*......\n' + | |
'.*......\n' + | |
'.*......\n' + | |
'........\n' + | |
'....***.\n' + | |
'...***..\n' + | |
'........\n' + | |
'........\n'; | |
var world = World.createFromScheme(boardScheme); | |
world.evolve(); | |
console.log(world.toString()); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment