Last active
September 6, 2015 20:19
-
-
Save bjelline/e6ec55b1650a01e63125 to your computer and use it in GitHub Desktop.
TDD: 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
// 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