9日目:タイマーとゲームオーバーの処理を追加する

9日目です。今回はオリジナルのTEN -10-と同様に時間制限を付けます。時間制限を付けることで発生するのがゲームオーバーの処理です。今回は、この時間制限の処理(以後タイマー処理)とゲームオーバーの処理を追加します。なお、今回変更した部分については●印をつけてあるので参考にしてください。

まず、タイマーの処理ですが、この機能を追加するにあたってオリジナルとは少し違う設定にします。オリジナルだと60秒の時間制限ですが、60秒だと簡単すぎるので30秒にします。また、ページが読み込まれたら、すぐにゲームが始まってしまうのもよくないので「最初の数字をクリックしたらタイマーがスタートする」事にします。
ということで改良にとりかかります。最初に時間を表示するための場所をspan要素を使って以下のように指定します。とりあえずスコアの右側に表示しておきます。オリジナルは画面下に表示していますが、まあいずれ下に配置することにします。今のところ、時間が表示されていればOKということにしておきます。

<div>SCORE : <span id="score">0</span> TIME : <span id="timeString">0</span></div>

次に時間を入れておくための変数を用意します。名前はtimeにしました。

var time = 0; // ●ゲームオーバーまでの時間

次にタイマーで時間を減算&表示するのですが、ブラウザのJavaScriptのタイマーはいくつかあり、定期的に処理するならsetInterval()が便利です。一度設定すれば指定した処理を定期的に呼び出してくれます。setTimeout()だと一度だけしか呼び出しませんので、こういう定期的に処理するならsetInterval()の方が便利です。ただ、ゲームクリア時などはタイマーを停止する必要があるのとゲームオーバーになった場合もタイマーを停止しなければいけません。停止しないとゲームクリアのメッセージが表示されている間も時間のカウントダウンが行われてしまってクリアしたのにゲームオーバーになってしまいます。
ということでタイマーを停止させるために必要なタイマーIDを保存する変数を以下のように用意しておきます。ちなみにsetInterval()メソッドの戻り値がタイマーIDになっています。

var timerID = null; // ●カウントダウンタイマーのIDを入れる変数

次に最初にクリックしたらタイマーがスタートする事にしたので最初の1回目のクリックかどうかを変数に入れておき判断します。ここでは以下のように変数firstClickFlagを用意しました。falseならクリックされていない、trueならクリックされたことにします。

var firstClickFlag = false; // ●最初にクリックしたかどうかのフラグ

次にステージを生成する際に以下のように秒数を指定します。また、この段階で秒数を表示しておきます。

time = 30; // ●30秒に設定する
document.getElementById("timeString").innerHTML = time + "秒"; // ●時間を表示する

ちなみにゲームを改造したりバイナリ解析して改造する場合は、こういう初期値をキーにして検索しさぐりあてます。30秒なら、そのまま30という数値か30という文字か、バイナリデータなら1Eという16進数になります。一般的には役に立たない技術ですが、何らかの事情によりゲームを解析したりする場合には使えます。

さて、本題からそれたので次の処理を追加します。最初にクリックしたらタイマーがスタートするようにします。これは2つの数字の合計が10かどうか調べる関数addNumberの先頭で調べます。というのもクリックされると、このaddNumber関数が呼び出されるからです。関数内でfirstClickFlagを調べてfalseならクリックされていないのでタイマーを開始させます。この時、変数firstClickFlagをtrueに変更しておきます。忘れると、ずっと初回クリックになってタイマーだけが大量に動くという凄く怖いオチになります。

// ●初めてのクリックかどうか調べる
if (firstClickFlag === false){
firstClickFlag = true;
// クリックされたらtrueにする
timerID = setInterval("displayTimer()", 1000); // 1秒ごと定期的にタイマー表示関数を呼び出す
}

次にsetInterval()で呼び出されるdisplayTimer関数です。displayTimer関数は1秒ごとに呼び出されるのでtime変数の内容を1つ減らします。0になったらゲームオーバーです。この場合はアラートダイアログを表示しておしまいにします。で、この時忘れてはいけいのがタイマーを止めることです。タイマーの停止は以下のようにして止めます。ちなみに一時停止はできず、完全に停止してしまいます。

clearInterval(timerID); // タイマーを停止する

あと、ゲームをクリアした時にもタイマーを止めます。止めたかは同じです。ただ、ゲームクリアして再度ボタンが表示されたらタイマーを再開しなければいけません。タイマーの一時停止はできないのですが、制限時間はtime変数に入れてあるので以下のようにするけでタイマーを再開することができます。

timerID = setInterval("displayTimer()", 1000); // ●1秒ごと定期的にタイマー表示関数を呼び出す

これで、ようやくオリジナルのTEN -10-に近くなりました。実際にプレイしてみると、まあ時間制限がなかった時よりも多少はマシになったかな〜という程度です。え〜残念ながら劇的に面白くなったとは言えません。
この段階でたいして面白くないということは、数字やグラフィックにして背景を豪華にしても、やはり面白くないはずです。ということで、次回は背景画像を追加したり数字を画像にしてみたいと思います。そんな事をしてもゲームは面白くならない、というのを証明(?)したいというのもあります。まあ、この段階でゲームとして面白いかどうかを詰めておくべきでしょう。

[サンプルを実行する]

[サンプルをダウンロード]

HTMLファイルの内容

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TEN</title>
<style>
input { width:48px; height: 48px; background-color: white; font-size: 24pt; }
#clearMsg { position : absolute; left: 10px; top: 200px; font-size: 18pt; color: red; visibility: hidden; }
</style>
</head>
<body>
<h1>TEN</h1>
<div>SCORE : <span id="score">0</span> TIME : <span id="timeString">0</span></div>
<div id="stage"></div>
<div id="msg"></div>
<div id="clearMsg">ステージクリア!</div>
<script src="main.js"></script>
</body>
</html>

JavaScriptファイル (main.js) の内容

// TEN HTML5 version
// (c) 2014 Cybermuse
// Arranger 古籏一浩
var score = 0;			// スコア(得点)を入れる変数
var count = 0;		// ボタンの合計を入れる変数
var prevNumber = 0;	// 直前にクリックした数字を入れる変数
var prevEle = null;		// 直前にクリックした要素を入れる変数
var time = 0;	// ●ゲームオーバーまでの時間
var timerID = null;	// ●カウントダウンタイマーのIDを入れる変数
var firstClickFlag = false;	// ●最初にクリックしたかどうかのフラグ
// 初期化処理
function init(){
	// ----- ステージを動的に生成する ------
	var w = 4;	// 横の数字の個数
	var h = 6;	// 縦の数字の個数
	var htmlString = "";
	for(var j=0; j<h; j++){
		for(var i=0; i<w; i++){
			htmlString = htmlString + '<input type="button">'; 
		}
		htmlString = htmlString + '<br>';
	}
	document.getElementById("stage").innerHTML = htmlString;
	// ------ ここまで ------
	var ele = document.querySelectorAll("input");	// クリックしたら処理するようにイベントを設定
	for(var i=0; i<ele.length; i++){	// 要素の数だけ繰り返す
		ele[i].onclick = addNumber;	// クリック時に呼び出す関数を指定する
	}
	count = ele.length;	// input要素の総数を入れる
	setRandomValue(ele);	// input要素の数を渡す
	time = 30;	// ●30秒に設定する
	document.getElementById("timeString").innerHTML = time + "秒";	// ●時間を表示する
}
// ランダムに値を設定する関数
function setRandomValue(ele){
	var len = ele.length;	// input要素の総数
	// input要素にランダムな値を設定する
	var num = [ ];
	for(var i=0; i<len; i+=2){	// 要素の数だけ繰り返す
		var n = Math.floor(Math.random() * 9) + 1;	// 1〜9までの数値をランダムに生成する
		num.push(n);
		num.push(10 - n);
	}
	// 配列要素をele.length回シャッフルする
	for(var i=0; i<len; i++){
		var p1 = Math.floor(Math.random() * len);	// input要素の数の範囲で乱数を発生させる
		var p2 = Math.floor(Math.random() * len);	// input要素の数の範囲で乱数を発生させる
		var n = num[p1];	// 2つの要素の内容を入れ替える
		num[p1] = num[p2];
		num[p2] = n;
	}
	// input要素に値を設定する
	for(var i=0; i<len; i++){	// 要素の数だけ繰り返す
		ele[i].value = num[i];	// 値を設定する
	}
}
// 2つの数字の合計が10かどうか調べる
function addNumber(){
	// ●初めてのクリックかどうか調べる
	if (firstClickFlag === false){
		firstClickFlag = true;	// クリックされたらtrueにする
		timerID = setInterval("displayTimer()", 1000);	// 1秒ごと定期的にタイマー表示関数を呼び出す
	}
	// 1個目のクリックの場合
	if (prevNumber === 0){
		prevEle = this;	// 要素の情報を変数に入れる
		prevEle.style.backgroundColor = "red";	// 背景を赤色にする
		prevNumber = parseInt(this.value);	// クリックされた要素の番号を変数に入れる
		return;	// 関数から抜ける
	}
	// 2個目のクリックの場合
	var total = prevNumber + parseInt(this.value);	// 直前の数値とクリックされた要素の数値を加算する
	if (total === 10){	// 合計が10の場合の処理
		this.style.visibility = "hidden";	// 現在の要素を非表示にする
		prevEle.style.visibility = "hidden";	// 前の要素を非表示にする
		prevNumber = 0;	// 数値を0にする
		document.getElementById("msg").innerHTML = "OK!";	// メッセージを表示する
		score = score + 1;	// スコア(得点)を追加する
		document.getElementById("score").innerHTML = score;	// スコア(得点)を表示する
		count = count - 2;	// 合計から2を引く
		if (count === 0){	// 全部消したかどうか調べる
			document.getElementById("clearMsg").style.visibility = "visible";	// ステージクリアメッセージを表示する
			clearInterval(timerID);	// ●タイマーを停止する
			setTimeout(function(){	// 2秒後に表示されているメッセージを消すためのタイマー
				document.getElementById("clearMsg").style.visibility = "hidden";	// ステージクリアメッセージを消す
				var ele = document.querySelectorAll("input");	// input要素を取得する
				setRandomValue(ele);	// 乱数値を設定する
				for(var i=0; i<ele.length; i++){
					ele[i].style.visibility = "visible";	// 表示する
					ele[i].style.backgroundColor = "";	// 透明にする
				}
				count = ele.length;	// input要素の総数を入れる
				document.getElementById("msg").innerHTML = "";	// メッセージをクリア
				timerID = setInterval("displayTimer()", 1000);	// ●1秒ごと定期的にタイマー表示関数を呼び出す
			}, 2000);
		}
		return;	// 関数から抜ける
	}
	// 合計が10でなかった場合
	prevNumber = 0;	// 数値を0にする
	prevEle.style.backgroundColor = "";	// 透明にする
	document.getElementById("msg").innerHTML = "合計が10じゃないよ!";	// メッセージを表示する
}
// ●タイマーの表示処理
function displayTimer(){
	time = time - 1;
	document.getElementById("timeString").innerHTML = time + "秒";
	if (time < 1){	// 時間が1より少なくなったらゲームオーバー
		clearInterval(timerID);	// タイマーを停止する
		alert("ゲームオーバー");
	}
}
init();	// 初期化処理を呼び出す