前のcanvasでライフゲームでは単純にライフゲームを動かしてみました。
今回はボタンの追加や、スピードの変更といった機能を追加してみます。
デモ
今回の完成形はこんな感じ。
See the Pen Untitled by hukinotou (@webspace) on CodePen.
コーディング
HTML
前回同様のcanvas
タグに加えて、速度や操作用のボタンなどを追加します。
HTML
<div style="display: flex;">
<div>
<canvas id="canvas" width="400" height="400" style="border: solid 1px #999;"></canvas>
</div>
<div style="padding-left: .5em;">
<p>
<input type="range" id="speed" min="1" max="40">
<span id="speed_txt"></span>
</p>
<p>
<input type="button" id="reset" value="リセット">
<input type="button" id="start" value="スタート">
<input type="button" id="stop" value="停止">
<input type="button" id="step" value="ステップ実行">
</p>
<p>
生存数:<span id="active"></span>
</p>
<p>
世代数:<span id="generation"></span>
</p>
</div>
</div>
Javascript
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();
});
簡単な説明
Javascriptの簡単な説明をつけておきます。
変数について
timer
、generation
、speed
の3つを追加しました。
Javascript
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つ。
Javascript
this.$e = $("#generation"); // 世代表示部のエレメント
this.g = 0; // 世代数
prototype(内部メソッド)
prototype(内部メソッド)として3つ。
increment
:世代を1増やすreset
:世代数を0に戻すshow
:世代数を表示する
Speed
メンバ変数
オブジェクトのメンバ変数は3つ。
Javascript
this.$e = $("#speed"); // 速度のスライドバーのエレメント
this.s = this.val(); // 速度の値
prototype(内部メソッド)
prototype(内部メソッド)として2つ。
change
:スライドを変更したときの処理show
:速度を表示するval
:速度の値を取得するspeed
:速度を計算する
おまけとして、現在activeなセルの数も表示するようにしてみました。
[AD]