c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Cosense で公表中です。

Programming は GitHub で開發中です。

JavaScriptで同期的life game

追記20130928
本題の非同期版を書いた。
非同期的 (async) life game (JavaScript) http://c4se.hatenablog.com/entry/2013/09/28/004018

前回のは未だ開発を続けてるけど。Ruby 2.1に成って、keyword引数の仕様に機能が追加されるから、多分修正が必要だと思ふ。12/25には2.1でちゃうし。
cf. Dart風のautomatic field initializationをRubyhttp://c4se.hatenablog.com/entry/2013/09/23/075129
まぁissue呉れ(〃l _ l) (乞食
cf. Issues · ne-sachirou/AutoAttrInit.rb https://github.com/ne-sachirou/AutoAttrInit.rb/issues

(〃l _ l) 其れでJavaScriptで普通のlife gameをやった訳だが
何人目だよ (l _ l〃)
(〃l _ l) 此れが目的なのではなくて、此れを元に改造する計画が有るの。
HTMLのtitleに"Sync"って付いてるのが其れかね (l _ l〃)
(〃l _ l) 黙れよ

demo







実装

Opera 16 (Chromium, V8), Firefox 23, IE 10 で目視で動作確認をした。

index.html

<!DOCTYPE html>
<!-- lisence: Public Domain -->
<meta charset="UTF-8">
<title>Sync Life game</title>
<style>
#game {
  position: relative;
  -webkit-transform-origin: 0 0;
  transform-origin: 0 0;
  -webkit-transform: scale(2, 2);
  transform: scale(2, 2);
}
</style>
<div>
  <button id="spawn0.01">spawn 1%</button>
  <button id="spawn0.05">spawn 5%</button>
  <button id="spawn0.1">spawn 10%</button>
  <button id="spawn0.3">spawn 30%</button>
  <button id="spawn0.5">spawn 50%</button>
</div>
<div>
  <canvas id="game" width="200" height="200"></canvas>
</div>
<script src="lifegame.js"></script>
<script>
(function(global) {

var view = new ViewLifegame;

document.getElementById('spawn0.01').
  addEventListener('click', function(evt) { view.cells.spawn(0.01); });
document.getElementById('spawn0.05').
  addEventListener('click', function(evt) { view.cells.spawn(0.05); });
document.getElementById('spawn0.1').
  addEventListener('click', function(evt) { view.cells.spawn(0.1); });
document.getElementById('spawn0.3').
  addEventListener('click', function(evt) { view.cells.spawn(0.3); });
document.getElementById('spawn0.5').
  addEventListener('click', function(evt) { view.cells.spawn(0.5); });
view.init(document.getElementById('game')).run();
document.getElementById('spawn0.1').click();

}(this.self || global));
</script>

lifegame.js

/**
 * @license Public Domain
 */

(function(global) {
'use strict';

if (! global.requestAnimationFrame) {
  if (global.mozRequestAnimationFrame)
    global.requestAnimationFrame = global.mozRequestAnimationFrame;
}

/**
 * @constructor
 */
function ViewLifegame() {
  /** @type {HTMLCanvasElement} */
  this.canvas = null;
  /** @type {CanvasRenderingContext2D} */
  this.context = null;
  /** @type {Cells} */
  this.cells = null;
}

ViewLifegame.prototype = {
  /**
   * @param {HTMLCanvasElement} canvas
   * @return {ViewLifegame}
   */
  init: function(canvas) {
    this.canvas = canvas;
    this.context = canvas.getContext('2d');
    this.cells = new Cells(canvas.width, canvas.height);
    return this;
  },

  /**
   * @return {ViewLifegame}
   */
  run: function() {
    function doNext(tick) {
      this.cells.doNextAll();
      this.draw();
      requestAnimationFrame(doNext);
    }
    doNext = doNext.bind(this);

    this.draw();
    requestAnimationFrame(doNext);
    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 = 'rgb(255, 100, 200)';
    this.cells.forEach(function(v, x, y) {
      if (v)
        this.context.fillRect(x, y, 1, 1);
    }, this);
    return this;
  }
};

/**
 * @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] = 0;
  }
}

Cells.prototype = {
  /**
   * @param {number} ratio
   * @return {Cells}
   */
  spawn: function(ratio) {
    var i = 0, iz = 0;

    for (i = 0, iz = Math.floor(this.width * this.height * ratio); i < iz; ++i)
      this.set(random(0, this.width), random(0, this.height), 1);
    return this;
  },

  /**
   * @return {Cells}
   */
  doNextAll: function() {
    var requests = [ ];

    this.forEach(function(v, x, y) {
      var next = 0;

      next = this.next(x, y);
      if (this.at(x, y) !== next)
        requests.push([x, y, next]);
    }, this);
    requests.forEach(function(elm) { this.set(elm[0], elm[1], elm[2]); }, this);
    return this;
  },

  /**
   * @param {function(this:Object,0|1,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 {0|1|null}
   */
  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];
  },

  /**
   * @private
   * @param {number} x
   * @param {number} y
   * @param {0|1} value
   * @return {Cells}
   */
  set: function(x, y, value) {
    this.field[x][y] = value;
    return this;
  },

  /**
   * This method has no effect, just show the next state of the place.
   *
   * @private
   * @param {number} x
   * @param {number} y
   * @return {0|1}
   */
  next: function(x, y) {
    var adjacent_count = 0;

    adjacent_count = this.adjacent(x, y).reduce(function(sum, v) { return sum + v; });
    if (this.at(x, y)) {
      switch (adjacent_count) {
        case 0: case 1:
          return 0;
        case 2: case 3:
          return 1;
        case 4: case 5: case 6: case 7: case 8:
          return 0;
        default:
          throw new Error('Adjacent count is ' + adjacent_count + '.');
      }
    } else {
      if (adjacent_count === 3)
        return 1;
      return 0;
    }
  },

  /**
   * @private
   * @param {number} x
   * @param {number} y
   * @return {Array.<0|1|null>}
   *   [ 0, 1, 2,
   *     3, v, 4,
   *     5, 6, 7 ]
   */
  adjacent: function(x, y) {
    return [
      this.at(x - 1, y - 1), this.at(x, y - 1), this.at(x + 1, y - 1),
      this.at(x - 1, y),                        this.at(x + 1, y),
      this.at(x - 1, y + 1), this.at(x, y + 1), this.at(x + 1, y + 1) ];
  }
};

/**
 * @param {number} min
 * @param {number} max
 * @return {number}
 */
function random(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

global.ViewLifegame = ViewLifegame;

}(this.self || global));