Skip to content

Instantly share code, notes, and snippets.

@bjelline
Last active September 6, 2015 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjelline/e6ec55b1650a01e63125 to your computer and use it in GitHub Desktop.
Save bjelline/e6ec55b1650a01e63125 to your computer and use it in GitHub Desktop.
TDD: Game of Life
// functions for plain coordinate vectors:
// n-dimentional coordinate range from min to max
function min_max_coordinates( min_coords, max_coords ){
let results = [ [] ];
for(var i=0 ; i<min_coords.length; i++ ) {
let next_results = [];
results.forEach( left_end => {
for( let v=min_coords[i]; v<=max_coords[i]; v++ ){
let new_array = left_end.slice(); // copy array
new_array.push( v );
next_results.push( new_array );
}
});
results = next_results;
}
return results;
}
// https://en.wikipedia.org/wiki/Moore_neighborhood
function moore_neighborhood( coordinate_tupel ){
let min_coords = coordinate_tupel.map( v => v-1 );
let max_coords = coordinate_tupel.map( v => v+1 );
let results = min_max_coordinates( min_coords, max_coords );
// remove the middle most coordinate:
// it's the original coordinate
results.splice( results.length/2, 1 );
return results;
}
class Coordinate {
constructor(pos){
this.pos = pos;
}
neighbours(){
let results = [];
moore_neighborhood( this.pos ).forEach(function( x ){
results.push( new Coordinate( x ) );
});
return results;
}
equals(other) {
for (let i = 0; i < this.pos.length; i++){
if (this.pos[i] !== other.pos[i]) {
return false;
}
}
return true;
}
getAsJSON(){
return JSON.stringify(this)
}
static range_of( list_of_coordinates ){
if( list_of_coordinates.length === 0 ) return [];
let dim = list_of_coordinates[0].pos.length;
let min = [];
let max = [];
for(let i=0; i<dim; i++) {
min[i] = Number.MAX_SAFE_INTEGER;
max[i] = Number.MIN_SAFE_INTEGER;
}
list_of_coordinates.forEach( c => {
// error if( c.pos.length != dim )
c.pos.forEach(function(value, i){
if( value < min[i] ) min[i] = value;
if( value > max[i] ) max[i] = value;
});
});
return [ min, max ];
}
static total_neighbourhood( list_of_coordinates ){
var that = this;
if( list_of_coordinates.length < 1 ) return [];
let range = this.range_of( list_of_coordinates );
// start 1 below the minimum, go to one above maximum
let min = range[0].map( m => m-1 );
let max = range[1].map( m => m+1 );
return min_max_coordinates( min, max ).map( c => new Coordinate(c) );
}
}
class GameOfLife {
constructor() {
this.cells = new Map();
}
isCellAlive(coordinate){
// use !! to turn undefined into false
return !!this.cells.get(coordinate.getAsJSON());
}
setCell(coordinate){
this.cells.set(coordinate.getAsJSON(), 1);
}
getAllCoordinates(){
return Array.from(this.cells.keys()).map( s =>
new Coordinate( JSON.parse(s).pos )
);
}
cellWillBeAlive(coordinate){
let game = this;
let count = 0;
let neighbours = coordinate.neighbours();
coordinate.neighbours().forEach(function( neighbour ){
count += game.isCellAlive( neighbour );
});
if( game.isCellAlive( coordinate ) ) {
return count == 2 || count == 3;
} else {
return count == 3;
}
}
nextGeneration( ){
let this_game = this;
let next_game = new GameOfLife();
let all_coordinates = Coordinate.total_neighbourhood(this_game.getAllCoordinates());
all_coordinates.forEach ( coordinate => {
if( this_game.cellWillBeAlive( coordinate )) {
next_game.setCell( coordinate );
}
// else: is dead by default, no need to set
});
return next_game;
}
}
describe('plain coordinate vectors', function(){
it ('should compute coordinate range for one dimension',
function(){
let range = min_max_coordinates( [ -1 ], [ 3 ]);
assert.deepEqual( range, [ [-1], [0], [1], [2], [3] ] );
});
it ('should compute coordinate range for two dimensions',
function(){
let range = min_max_coordinates( [ -1,0 ], [ 3,1 ]);
assert.deepEqual( range, [
[-1,0],[-1,1],
[ 0,0],[ 0,1],
[ 1,0],[ 1,1],
[ 2,0],[ 2,1],
[ 3,0],[ 3,1]
]);
});
it ('should return correct moore neighborhood for one dimensions',
function() {
let c = [0];
let neighbours = moore_neighborhood( c );
assert.deepEqual(neighbours,[ [ -1 ], [ +1 ] ] );
});
it ('should return correct moore neighborhood for two dimensions',
function() {
let c = [-2,3];
let neighbours = moore_neighborhood( c );
assert.deepEqual(neighbours,[
[-3,2],[-3,3],[-3,4],
[-2,2], [-2,4],
[-1,2],[-1,3],[-1,4]
]
);
});
it ('should return correct coordinates for three dimensions',
function() {
let c = [0, 10, -9];
let neighbours = moore_neighborhood( c );
assert.deepEqual(neighbours,[
[-1, 9,-10],[-1, 9, -9],[-1, 9, -8],
[-1, 10,-10],[-1, 10, -9],[-1, 10, -8],
[-1, 11,-10],[-1, 11, -9],[-1, 11, -8],
[ 0, 9,-10],[ 0, 9, -9],[ 0, 9, -8],
[ 0, 10,-10], [ 0, 10, -8],
[ 0, 11,-10],[ 0, 11, -9],[ 0, 11, -8],
[+1, 9,-10],[+1, 9, -9],[+1, 9, -8],
[+1, 10,-10],[+1, 10, -9],[+1, 10, -8],
[+1, 11,-10],[+1, 11, -9],[+1, 11, -8]
]
);
});
})
describe('coordinate', function() {
it('should be able to test object equality', function(){
let c1 = new Coordinate([0]);
let c2 = new Coordinate([0]);
assert.ok( c1.equals(c2) );
});
it('should get two neighbours if it is one dimensinal', function() {
let coordinate = new Coordinate([0]);
assert.equal(2, coordinate.neighbours().length);
});
it ('should be able to handle two dimensions', function() {
let coordinate = new Coordinate([-1,3]);
assert.equal(8, coordinate.neighbours().length);
});
it ('neighbours should return coodinate objects', function() {
let coordinate0 = new Coordinate([-1,3]);
let coordinate1 = new Coordinate([-2,2]);
let neighbourhood = coordinate0.neighbours();
assert.ok(neighbourhood[0].equals( coordinate1 ));
});
it('should compute range given coordinates', function() {
let range = Coordinate.range_of( [
new Coordinate([-1, 300]),
new Coordinate([+10, 20])
] );
let min = range[0];
let max = range[1];
assert.equal( min[0], -1 );
assert.equal( max[0], +10 );
assert.equal( min[1], 20 );
assert.equal( max[1], 300 );
});
it('should generate all possible coordinates +/-1 of given coordinates', function() {
let c1 = new Coordinate([-1,3]);
let c2 = new Coordinate([-2,3]);
let neighbourhood = Coordinate.total_neighbourhood( [c1, c2] )
assert.equal( neighbourhood.length, 4 * 3 );
});
});
describe('game', function() {
it('can access a list of all coordinates',function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
assert.equal(g.getAllCoordinates().length, 1);
});
it('should remember that you set a cell to alive', function() {
let g = new GameOfLife();
let c = new Coordinate([0,0]);
assert.ok( !g.isCellAlive( c ));
g.setCell( c );
assert.ok( g.isCellAlive( c ));
});
it('should know if a cell will live or die: rule 1', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
g.setCell( new Coordinate([1,0]) );
assert.ok( ! g.cellWillBeAlive( new Coordinate([0,0]) ));
});
it('should know if a cell will live or die: rule 2', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
g.setCell( new Coordinate([ 1,0]) );
g.setCell( new Coordinate([-1,0]) );
assert.ok( g.cellWillBeAlive( new Coordinate([0,0]) ));
});
it('should know if a cell will live or die: rule 3', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
g.setCell( new Coordinate([ 1, 0]) );
g.setCell( new Coordinate([-1,-1]) );
g.setCell( new Coordinate([-1, 0]) );
g.setCell( new Coordinate([-1,+1]) );
assert.ok(! g.cellWillBeAlive( new Coordinate([0,0]) ));
});
it('should know if a cell will come alive: rule 4', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([-1,-1]) );
g.setCell( new Coordinate([-1, 0]) );
g.setCell( new Coordinate([-1,+1]) );
assert.ok(g.cellWillBeAlive( new Coordinate([0,0]) ));
});
it('should be able to generate a new generation: rule 3', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
g.setCell( new Coordinate([1,0]) );
g.setCell( new Coordinate([0,1]) );
g.setCell( new Coordinate([1,1]) );
g.setCell( new Coordinate([1,2]) );
let g2 = g.nextGeneration()
assert.ok(! g2.isCellAlive(new Coordinate([1,1])) );
})
it('should be able to generate a new generation: rule 4', function() {
let g = new GameOfLife();
g.setCell( new Coordinate([0,0]) );
g.setCell( new Coordinate([1,0]) );
g.setCell( new Coordinate([0,1]) );
let g2 = g.nextGeneration()
assert.ok( g2.isCellAlive(new Coordinate([1,1])) );
})
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment