前のcanvasでライフゲームでは単純にライフゲームを動かしてみました。
今回はボタンの追加や、スピードの変更といった機能を追加してみます。
記事の目次
デモ
今回の完成形はこんな感じ。
デモページ
コーディング
HTML
前回同様シンプルにcanvas
タグだけあればOKです。
<canvas id="canvas" width="400" height="400" style="border: solid 1px #999;"></canvas>
Javascript
jQuery(function($) { var WIDTH = 80; var HEIGHT = WIDTH; var SIZE = 5; var timer = null; var field; var canvas; var generation; var speed; var Generation = function(){ this.$e = $("#generation"); this.g = 0; this.show(); }; Generation.prototype = { increment: function(){ this.g++; }, reset: function(){ this.g = 0; }, show: function() { this.$e.text(this.g); } }; var Speed = function(){ this.$e = $("#speed"); this.s = this.val(); this.show(); }; Speed.prototype = { change: function(){ var s = this.val(); if (this.s === s) { return; } this.s = s; this.show(); if (timer === null) { return; } start(); }, show: function(){ $("#speed_txt").text(this.speed() + "ms"); }, val: function(){ return this.$e.val(); }, speed: function(){ return 25 * this.val(); } }; var Field = function(width, height){ this.width = width; this.height = height; this.field; this.active; this.init(); }; Field.prototype = { init: function(){ this.active = 0; this.field = []; for (var y = 0; y < this.height; y++) { this.field[y] = []; for (var x = 0; x < this.width; x++) { this.set(x, y, null); } } }, randomize: function(){ for (var y = 0; y < this.height; y++) { for (var x = 0; x < this.width; x++) { var val = random(1, 10) == 1 ? 1 : 0; this.set(x, y, val); this.active += this.val(x, y); } } }, reset: function(){ this.active = 0; for (var y = 0; y < this.height; y++) { for (var x = 0; x < this.width; x++) { this.set(x, y, 0); } } }, next: function(){ this.active = 0; var tempField = $.extend(true, {}, this.field); for (var y = 0; y < this.height; y++) { for (var x = 0; x < this.width; x++) { var n = this.neighbor(x, y, tempField); var val = this.fate(tempField[y][x], n); this.set(x, y, val); this.active += this.val(x, y); } } }, neighbor: function(x, y, field) { var n = 0; for (var s = -1; s < 2; s++) { if (y + s < 0 || y + s > this.height - 1) { continue; } for (var t = -1; t < 2; t++) { if (s == 0 && t == 0) { continue; } if (x + t < 0 || x + t > this.width - 1) { continue; } if (field[y + s][x + t] == 1) { n++; } } } return n; }, fate: function(mine, neighbor){ // 生存 if (mine == 1 && (neighbor == 2 || neighbor == 3)) { return 1; } // 誕生 if (mine == 0 && neighbor == 3) { return 1; } // 過疎, 過密 return 0; }, val: function(x, y){ return this.field[y][x]; }, set: function(x, y, v){ this.field[y][x] = v; } }; var Canvas = function(ctx, field, size) { this.ctx = ctx; this.field = field; this.size = size; }; Canvas.prototype = { clear: function(){ this.ctx.clearRect(0, 0, this.field.width * this.size, this.field.height * this.size); }, draw: function() { this.clear(); this.ctx.fillStyle = 'rgb(0, 0, 0)'; for (var y = 0; y < this.field.height; y++) { for (var x = 0; x < this.field.width; x++) { if (this.field.val(x, y) == 0) { continue; } this.ctx.fillRect(x * this.size, y * this.size, this.size, this.size); } } } }; var init = function() { var $canvas = $("#canvas"); var ctx = $canvas.get(0).getContext('2d'); field = new Field(WIDTH, HEIGHT); field.randomize(); canvas = new Canvas(ctx, field, SIZE); generation = new Generation(); speed = new Speed(); bind(); }; var bind = function() { var $e = $("#speed"); $e.mousemove(function(){ speed.change(); }); $e.change(function(){ speed.change(); }); $("#reset").click(reset); $("#start").click(start); $("#stop").click(stop); $("#step").click(step); }; var draw = function() { canvas.draw(); generation.show(); $("#active").text(field.active); }; var lifegame = function() { generation.increment(); field.next(); draw(); } var reset = function() { generation.reset(); field.randomize(); draw(); }; var start = function(){ $("#start").prop("disabled", true); $("#stop").prop("disabled", false); $("#step").prop("disabled", true); if (timer !== null) { clearInterval(timer); } timer = setInterval(lifegame, speed.speed()); }; var stop = function() { $("#start").prop("disabled", false); $("#stop").prop("disabled", true); $("#step").prop("disabled", false); clearInterval(timer); timer = null; }; var step = function() { if (timer !== null) { return; } lifegame(); }; var random = function(min, max) { return min + Math.floor(Math.random() * (max + 1)); }; init(); draw(); }); [/code] <h2>簡単な説明</h2> Javascriptの簡単な説明をつけておきます。 <h3>変数について</h3> <code>timer</code>、<code>generation</code>、<code>speed</code>の3つを追加しました。 var WIDTH = 80; // 横のセル数 var HEIGHT = WIDTH; // 縦のセル数 var SIZE = 5; // セルサイズ var timer = null; // setInterval用 var field; // 各セルの状態管理用 var canvas; // canvas制御用 var generation; // 世代数管理用 var speed; // 表示スピード管理用
メソッドについて
追加したメソッドは5つ。
bind
:各ボタンなどの部品へのイベントのバインドreset
:fieldを初期化start
:ライフゲームを開始するstop
:ライフゲームを停止するstep
:ライフゲームをステップ実行する
Generationオブジェクト
メンバ変数
オブジェクトのメンバ変数は2つ。
this.$e = $("#generation"); // 世代表示部のエレメント this.g = 0; // 世代数
prototype(内部メソッド)
prototype(内部メソッド)として3つ。
increment
:世代を1増やすreset
:世代数を0に戻すshow
:世代数を表示する
Speed
メンバ変数
オブジェクトのメンバ変数は3つ。
this.$e = $("#speed"); // 速度のスライドバーのエレメント this.s = this.val(); // 速度の値
prototype(内部メソッド)
prototype(内部メソッド)として2つ。
change
:スライドを変更したときの処理show
:速度を表示するval
:速度の値を取得するspeed
:速度を計算する
おまけとして、現在activeなセルの数も表示するようにしてみました。