Skip to content

Instantly share code, notes, and snippets.

@ne-sachirou
Last active December 24, 2015 02: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 ne-sachirou/6730101 to your computer and use it in GitHub Desktop.
Save ne-sachirou/6730101 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<!-- lisence: Public Domain -->
<meta charset="UTF-8">
<title>ASync Life game</title>
<style>
.container_20130928_1 {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-pack: center;
justify-content: center;
}
.container_20130928_1 > div {
box-shadow: 0 0 1px black;
margin: 0.1em;
padding: 0.5em;
}
.container_20130928_1 .canvas {
height: 400px;
width: 400px;
}
.container_20130928_1 .game {
display: block;
position: relative;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scale(2, 2);
transform: scale(2, 2);
}
</style>
<div class="container_20130928_1">
<button id="create_20130928_1">Create life games</button>
<p>It may be too heavy!</p>
</div>
<template id="game_template_20130928_1">
<div id="" class="holder">
<div>
<input type="number" class="ratio" value="1"/>%
<button class="spawn">spawn</button>
<button class="kill">kill</button>
</div>
<div class="canvas">
<canvas class="game" width="200" height="200"></canvas>
</div>
</div>
</template>
<script src="lifegame.js"></script>
<script>
(function(global) {
var view_sync, view_async;
if (!('content' in document.createElement('template'))) {
(function() {
var style = document.createElement('style');
style.textContent = 'template { display: none; }';
document.body.appendChild(style);
}());
Object.defineProperty(HTMLUnknownElement.prototype, 'content', {
/**
* @return {DocumentFragment|undefined}
*/
get: function() {
var fragment;
if (this.tagName !== 'TEMPLATE')
return;
fragment = document.createDocumentFragment();
Array.prototype.slice.call(this.childNodes).forEach(function(node) {
fragment.appendChild(node.cloneNode(true));
});
return fragment;
}
});
}
window.addEventListener('DOMContentLoaded', function(evt) {
document.getElementById('create_20130928_1').addEventListener('click', function(evt) {
document.getElementsByClassName('container_20130928_1')[0].innerHTML = '';
createCanvas('game_sync_20130928_1');
view_sync = runGame('game_sync_20130928_1', false, {
color: 'rgb(255, 100, 200)'
});
createCanvas('game_async_20130928_1');
view_async = runGame('game_async_20130928_1', true, {
color: 'rgb(100, 200, 255)'
});
});
});
/**
* @param {string} holder_id
*/
function createCanvas(holder_id) {
var holder = document.getElementById('game_template_20130928_1').content.cloneNode(true);
holder.querySelector('.holder').id = holder_id;
document.getElementsByClassName('container_20130928_1')[0].appendChild(holder);
}
/**
* @param {string} holder_id
* @param {boolean} is_async
* @param {Object.<string,Object>} options
* @return {ViewLifegame}
*/
function runGame(holder_id, is_async, options) {
var view = new ViewLifegame_20130928_1,
holder = document.getElementById(holder_id);
function getRatio() {
return parseFloat(holder.getElementsByClassName('ratio')[0].value) * 0.01;
}
function spawn() { view.cells.spawn(getRatio()); }
function kill() { view.cells.kill(getRatio()); }
holder.getElementsByClassName('spawn')[0].addEventListener('click', spawn);
holder.getElementsByClassName('kill')[0].addEventListener('click', kill);
view.init(holder.getElementsByClassName('game')[0], options);
view.cells.spawn(0.1);
view.run(is_async);
return view;
}
global.views_20130928_1 = [view_sync, view_async];
}(this.self || global));
</script>
/**
* @license Public Domain
*/
(function(global) {
'use strict';
if (! global.requestAnimationFrame) {
if (global.mozRequestAnimationFrame)
global.requestAnimationFrame = global.mozRequestAnimationFrame;
}
// {{{ ViewLifegame
/**
* @constructor
*/
function ViewLifegame() {
/** @type {HTMLCanvasElement} */
this.canvas = null;
/** @params {Object.<string,Object>} */
this.options = {};
/** @type {CanvasRenderingContext2D} */
this.context = null;
/** @type {Cells} */
this.cells = null;
}
ViewLifegame.prototype = {
/**
* @param {HTMLCanvasElement} canvas
* @params {Object.<string,Object>} options
* { color: color of cells }
* @return {ViewLifegame}
*/
init: function(canvas, options) {
this.canvas = canvas;
this.options = options;
this.context = canvas.getContext('2d');
this.cells = new Cells(canvas.width, canvas.height);
return this;
},
/**
* @param {boolean=} is_async false is default.
* @return {ViewLifegame}
*/
run: function(is_async) {
var animation = new Animation;
animation.add(function() {
this.draw();
if (! is_async)
this.cells.doNext();
}, this);
this.draw();
if (is_async)
this.cells.forEach(function(cell) { cell.run(); });
animation.start();
return this;
},
/**
* @private
* @return {ViewLifegame}
*/
draw: function() {
this.context.fillStyle = 'black';
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.context.fillStyle = this.options.color;
this.cells.forEach(function(cell, x, y) {
if (cell.is_alive)
this.context.fillRect(x, y, 1, 1);
}, this);
return this;
}
};
// }}}
// {{{ Cells
/**
* @param {number} width
* @param {number} height
*/
function Cells(width, height) {
var x = 0, y = 0;
/** @type {number} */
this.width = width;
/** @type {number} */
this.height = height;
/** @type {Array.<Array.<number>>} */
this.field = [ ];
for (x = 0; x < width; ++x) {
this.field[x] = [ ];
for (y = 0; y < height; ++y)
this.field[x][y] = new Cell(this, x, y, false);
}
}
Cells.prototype = {
/**
* @param {number} ratio
* @return {Cells}
*/
spawn: function(ratio) {
var i = 0,
number = Math.floor(this.width * this.height * ratio);
for (i = 0; i < number; ++i)
this.at(random(0, this.width), random(0, this.height)).is_alive = true;
return this;
},
/**
* @param {number} ratio
* @return {Cells}
*/
kill: function(ratio) {
var i = 0,
number = Math.floor(this.width * this.height * ratio);
for (i = 0; i < number; ++i)
this.at(random(0, this.width), random(0, this.height)).is_alive = false;
return this;
},
/**
* @return {Cells}
*/
doNext: function() {
var requests = [];
this.forEach(function(cell, x, y) {
var next = cell.next();
if (cell.is_alive !== next)
requests.push([x, y, next]);
}, this);
requests.forEach(function(request) {
this.at(request[0], request[1]).is_alive = request[2];
}, this);
return this;
},
/**
* @param {function(this:Object,Cell,x:number,y:number,Cells):(boolean|undefined)} fn
* @param {Object=} obj
* @return {Cells}
*/
forEach: function(fn, obj) {
var x = 0, y = 0,
width = this.width, height = this.height,
result;
if (obj === void 0)
obj = this;
loop: for (x = 0; x < width; ++x) {
for (y = 0; y < height; ++y) {
result = fn.call(obj, this.field[x][y], x, y, this);
if (result === false)
break loop;
}
}
return this;
},
/**
* @private
* @param {number} x
* @param {number} y
* @return {?Cell}
*/
at: function(x, y) {
var column;
x = (x + this.width) % this.width;
y = (y + this.height) % this.height;
column = this.field[x];
if (! column)
return null;
return column[y];
}
};
// }}}
// {{{ Cell
/**
* @param {Cells} cells
* @param {number} x
* @param {number} y
* @param {boolean} is_alive
*/
function Cell(cells, x, y, is_alive) {
this.cells = cells;
this.x = x;
this.y = y;
this.is_alive = is_alive;
}
Cell.prototype = {
/**
* @return {Cell}
*/
run: function() {
var animation = new Animation;
this.doNext();
animation.add(function() {
if (random(0, 10) === 0)
this.doNext();
}, this);
animation.start();
// setTimeout(this.run.bind(this), random(33, 333));
return this;
},
/**
* @return {Cell}
*/
doNext: function() {
this.is_alive = this.next();
return this;
},
/**
* @return {boolean}
*/
next: function() {
var adjacent_count = this.adjacent_count();
if (this.is_alive) {
switch (adjacent_count) {
case 0: case 1:
case 4: case 5: case 6: case 7: case 8:
return false;
case 2: case 3:
return true;
default:
throw new Error('Adjacent count is ' + adjacent_count + '.');
}
}
if (adjacent_count === 3)
return true;
return false;
},
/**
* @return {number}
*/
adjacent_count: function() {
return this.adjacent().reduce(function(sum, cell) {
if (cell.is_alive)
sum += 1;
return sum;
}, 0);
},
/**
* @return {Array.<Cell>}
* [ 0, 1, 2,
* 3, v, 4,
* 5, 6, 7 ]
*/
adjacent: function() {
var cells = this.cells, x = this.x, y = this.y;
return [
cells.at(x - 1, y - 1), cells.at(x, y - 1), cells.at(x + 1, y - 1),
cells.at(x - 1, y), cells.at(x + 1, y),
cells.at(x - 1, y + 1), cells.at(x, y + 1), cells.at(x + 1, y + 1) ];
}
};
// }}}
// {{{ Animation
function Animation() {
var instance;
instance = this;
Animation = function() { return instance; };
/** @type {boolean} */
this.is_started = false;
/** @type {Array.<{fn:function(),obj:Object}>} */
this.fns = [];
}
Animation.prototype = {
/**
* @return {Animation}
*/
start: function() {
function doNext() {
this.fns.forEach(function(fn) {
fn.fn.call(fn.obj);
});
requestAnimationFrame(doNext.bind(this));
}
if (this.is_started)
return;
this.is_started = true;
requestAnimationFrame(doNext.bind(this));
return this;
},
/**
* @param {function(this:Object)} fn
* @param {Object} obj
* @return {Animation}
*/
add: function(fn, obj) {
this.fns.push({fn: fn, obj: obj});
return this;
}
};
// }}}
/**
* @param {number} min
* @param {number} max
* @return {number}
*/
function random(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
global.ViewLifegame_20130928_1 = ViewLifegame;
}(this.self || global));
// vim:set foldmethod=marker:
@ne-sachirou
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment