Skip to content

Instantly share code, notes, and snippets.

@latentflip
Forked from joejag/game_of_life.coffee
Created October 28, 2012 22:52
Show Gist options
  • Save latentflip/3970282 to your computer and use it in GitHub Desktop.
Save latentflip/3970282 to your computer and use it in GitHub Desktop.
Conways Game of Life in CoffeeScript
# Utility methods
flatten_nested_array = (array) ->
[].concat array...
includes = (item, coll) ->
item = key_to_array(item)
for potential_match in coll
return true if item[0] == potential_match[0] && item[1] == potential_match[1]
false
key_to_array = (string) ->
return string if typeof string != 'string'
splitted = string.split(',')
[parseInt(splitted[0]), parseInt(splitted[1])]
# Main
# world is a set of live cells
# map all the neighbours of live cells in the world
# reduce to the frequencies of the neighbours occuring i.e. [1, 1] occurs 4 times
# reduce with conway rules: (2 & 3 and alive survives) or (3 and dead becomes alive)
neighbours_of = (x, y) ->
[[x-1, y+1], [x, y+1], [x+1, y+1],
[x-1, y ], [x+1, y ],
[x-1, y-1], [x, y-1], [x+1, y-1]]
frequencies = (all_neighbouring_cells) ->
frequencies_found = {}
for candidate_cell in all_neighbouring_cells
frequencies_found[candidate_cell] ||= 0
frequencies_found[candidate_cell]++
frequencies_found
neighbour_frequencies = (world) ->
neighbours = (neighbours_of(cell[0], cell[1]) for cell in world)
neighbours = flatten_nested_array(neighbours)
frequencies(neighbours)
survives = (num_of_neighbours, is_alive) ->
return true if is_alive and (num_of_neighbours == 2 or num_of_neighbours == 3)
return true if !is_alive and num_of_neighbours == 3
false
evolve = (world) ->
nf = neighbour_frequencies(world)
survivors = key_to_array(cell) for cell, count of nf when survives(count, includes(cell, world))
game_loop = (world, times_to_evolve, listener) ->
evolved = 0
interval = null
iteration = ->
listener(world)
world = evolve(world)
evolved++
clearInterval interval if times_to_evolve && evolved>times_to_evolve
interval = setInterval iteration, 400
class Grid
constructor: (x,y) ->
@svg = d3.select('body').append('svg')
.attr('width', 5*x)
.attr('height', 5*y)
render: (data) =>
rects = @svg.selectAll('rect')
.data(data, (d) -> "#{d[0]},#{d[1]}")
rects.enter().append('rect')
.attr('width', 0)
.attr('height', 0)
.attr('x', (d)->2.5+d[0]*5)
.attr('y', (d)->2.5+d[1]*5)
.transition().duration(400)
.attr('width', 5)
.attr('height', 5)
.attr('x', (d)->d[0]*5)
.attr('y', (d)->d[1]*5)
rects.exit().remove()
appendScript = (src, cb) ->
script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.onload = cb
script.src = src
document.getElementsByTagName('head')[0].appendChild(script)
appendScript 'http://code.jquery.com/jquery-1.8.2.min.js', ->
appendScript 'http://cdnjs.cloudflare.com/ajax/libs/d3/2.10.0/d3.v2.min.js', ->
$ ->
world = [[0, 1], [1, 1], [2, 1], [2,2], [3,3], [3,2]]
g = new Grid(40,40)
game_loop(world, 5, g.render)
# prints
# ["0,1", "1,1", "2,1"]
# ["1,2", "1,1", "1,0"]
# ["0,1", "1,1", "2,1"]
# ["1,2", "1,1", "1,0"]
# ["0,1", "1,1", "2,1"]
// Generated by CoffeeScript 1.3.3
(function() {
var Grid, appendScript, change_listener, evolve, flatten_nested_array, frequencies, game_loop, includes, key_to_array, neighbour_frequencies, neighbours_of, survives,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
flatten_nested_array = function(array) {
var _ref;
return (_ref = []).concat.apply(_ref, array);
};
includes = function(item, coll) {
var potential_match, _i, _len;
item = key_to_array(item);
for (_i = 0, _len = coll.length; _i < _len; _i++) {
potential_match = coll[_i];
if (item[0] === potential_match[0] && item[1] === potential_match[1]) {
return true;
}
}
return false;
};
key_to_array = function(string) {
var splitted;
if (typeof string !== 'string') {
return string;
}
splitted = string.split(',');
return [parseInt(splitted[0]), parseInt(splitted[1])];
};
neighbours_of = function(x, y) {
return [[x - 1, y + 1], [x, y + 1], [x + 1, y + 1], [x - 1, y], [x + 1, y], [x - 1, y - 1], [x, y - 1], [x + 1, y - 1]];
};
frequencies = function(all_neighbouring_cells) {
var candidate_cell, frequencies_found, _i, _len;
frequencies_found = {};
for (_i = 0, _len = all_neighbouring_cells.length; _i < _len; _i++) {
candidate_cell = all_neighbouring_cells[_i];
frequencies_found[candidate_cell] || (frequencies_found[candidate_cell] = 0);
frequencies_found[candidate_cell]++;
}
return frequencies_found;
};
neighbour_frequencies = function(world) {
var cell, neighbours;
neighbours = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = world.length; _i < _len; _i++) {
cell = world[_i];
_results.push(neighbours_of(cell[0], cell[1]));
}
return _results;
})();
neighbours = flatten_nested_array(neighbours);
return frequencies(neighbours);
};
survives = function(num_of_neighbours, is_alive) {
if (is_alive && (num_of_neighbours === 2 || num_of_neighbours === 3)) {
return true;
}
if (!is_alive && num_of_neighbours === 3) {
return true;
}
return false;
};
evolve = function(world) {
var cell, count, nf, survivors, _results;
nf = neighbour_frequencies(world);
_results = [];
for (cell in nf) {
count = nf[cell];
if (survives(count, includes(cell, world))) {
_results.push(survivors = key_to_array(cell));
}
}
return _results;
};
game_loop = function(world, times_to_evolve, listener) {
var evolved, interval, iteration, _i, _results;
evolved = 0;
interval = null;
iteration = function() {
listener(world);
world = evolve(world);
evolved++;
if (times_to_evolve && evolved > times_to_evolve) {
return clearInterval(interval);
}
};
interval = setInterval(iteration, 400);
_results = [];
for (_i = 1; 1 <= times_to_evolve ? _i <= times_to_evolve : _i >= times_to_evolve; 1 <= times_to_evolve ? _i++ : _i--) {
listener(world);
_results.push(world = evolve(world));
}
return _results;
};
change_listener = function(new_world) {
var cell;
return console.log((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = new_world.length; _i < _len; _i++) {
cell = new_world[_i];
_results.push(cell.toString());
}
return _results;
})());
};
Grid = (function() {
function Grid(x, y) {
this.render = __bind(this.render, this);
this.svg = d3.select('body').append('svg').attr('width', 5 * x).attr('height', 5 * y);
}
Grid.prototype.render = function(data) {
var rects;
rects = this.svg.selectAll('rect').data(data, function(d) {
return "" + d[0] + "," + d[1];
});
rects.enter().append('rect').attr('width', 0).attr('height', 0).attr('x', function(d) {
return 2.5 + d[0] * 5;
}).attr('y', function(d) {
return 2.5 + d[1] * 5;
}).transition().duration(400).attr('width', 5).attr('height', 5).attr('x', function(d) {
return d[0] * 5;
}).attr('y', function(d) {
return d[1] * 5;
});
return rects.exit().remove();
};
return Grid;
})();
appendScript = function(src, cb) {
var script;
script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.onload = cb;
script.src = src;
return document.getElementsByTagName('head')[0].appendChild(script);
};
appendScript('http://code.jquery.com/jquery-1.8.2.min.js', function() {
return appendScript('http://cdnjs.cloudflare.com/ajax/libs/d3/2.10.0/d3.v2.min.js', function() {
return $(function() {
var g, world;
world = [[0, 1], [1, 1], [2, 1], [2, 2], [3, 3], [3, 2]];
g = new Grid(40, 40);
return game_loop(world, 5, g.render);
});
});
});
}).call(this);
<script src='https://raw.github.com/gist/3970282/23e9325966140a6923e3e0e409079996b342b3ee/gol.js'></script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment