Skip to content

fibo/games-of-life

Repository files navigation

games-of-life

is a Mathematical solution to any Game of Life variation

Demo | Installation | Idea | Example | See also | License

NPM version Badge size Build Status No deps JavaScript Style Guide Change log

Whatchers Stargazers Forks

Demo

I created this hexagonal Game of Life demo to show that this package generalize the Game of Life in any of its variations. Click the image below to try it!

Hexagonal game of life

Installation

With npm do

npm install games-of-life

You could also use a CDN adding this to your HTML page

<script src="https://unpkg.com/games-of-life/dist/games-of-life.min.js"></script>

Idea

On the 30th of May 2015, I have participated in a Coderetreat at Milan XPUG.

We have had so much fun coding implementations of the Game of Life.

As a mathematician, I think it is a very interesting problem. I couldn't resist to generalize it and try to solve it in any of its variations.

Let's start with some abstractions.

A function getNeighboursOf, which returns the set of cells adjacent to a given cell, defines the shape of Game of Life universe.

Infact, since

getNeighboursOf(cell1) = getNeightboursOf(cell2) ⇒ cell1 = cell2

it can be said that the set of neighbours of a cell is dual to the cell itself, hence the definition of the getNeighboursOf function is equivalent to the definition of the space of a Game of Life universe. Note that it defines the concept of nearness.

In other words,

if you define a getNeighbours function you also shape the space of a Game of Life universe

On the other hand, let be given the definition of an isAlive function, which returns true if the given cell is alive, false otherwise. It can be easily extended to an areAlive function which, given a list of cells, returns a list of booleans; following a similar identification we used for the getNeighboursOf function, an isAlive function describes the state of a Game of Life universe at a given moment.

The considerations above allow to implement an abstract Game of Life in a functional way, in any of its variations, for example:

  • finite grid
  • infinite grid
  • 2-dimensional, 3-dimensional, n-dimensional
  • square, triangular, hexagonal tiles
  • cylinder, torus, moebius strip, boy surface

Take a look to createWorld.js for the implementation's details.

The world has a transition rule which defaults to the classicTransitionRule.js.

Example

A simple example is the infinite grid with two dimensional coordinates.

Define a getNeighboursOf which returns the neighbours of a given cell.

function getNeighboursOf (cell) {
  var x = cell[0]
  var y = cell[1]

  var neighbours = []

  for (var j = y - 1; j <= y + 1; j++) {
    for (var i = x - 1; i <= x + 1; i++) {
      if ((i === x) && (j === y)) {
        continue
      }

      neighbours.push([i, j])
    }
  }

  return neighbours
}

// Alias the adjacency function  with a more meaningful name,
// to improve semantic in the example code below.
var infiniteGrid2d = getNeighboursOf

Create a Game of Life world, and get the evolve function

var gamesOfLife = require('games-of-life')

var createWorld    = gamesOfLife.createWorld
var transitionRule = gamesOfLife.classicTransitionRule.bind(null, 2, 3, 3)

var world = createWorld(infiniteGrid2d)

var evolve = world(transitionRule)

The empty grid is represented by a function that always returns false, so

function emptyGrid () {
  return false
}

evolve(emptyGrid) // will always return false

Try with a single cell at the origin

function singleCellAtTheOrigin (cell) {
  return ((cell[0] === 0) && (cell[1] === 0))
}

evolve(singleCellAtTheOrigin) // will always return false too, cause the cell dies

Ok, a more interesting example is the blinker

Blinker

function horyzontalBlinker (cell) {
  var x = cell[0]
  var y = cell[1]

  if (y !== 0) {
    return false
  }

  if ((x >= -1) && (x <= 1)) {
    return true
  }

  return false
}

function verticalBlinker (cell) {
  var x = cell[0]
  var y = cell[1]

  if (x !== 0) {
    return false
  }

  if ((y >= -1) && (y <= 1)) {
    return true
  }

  return false
}

You may check that the verticalBlinker evolves in the horyzontalBlinker and vice versa

for (var i = -1; i < 1; i++) {
  for (var j = -1; j < 1; j++) {
    console.log(evolve(verticalBlinker)(i, j) ===  horyzontalBlinker(i, j)) // true
  }
}

License

MIT