12日目:ゲームオーバー後の不具合解消と重複処理のまとめ

12日目です。今回はゲームオーバー後の不具合解消と重複している部分の処理のまとめてみます。ゲームオーバー後の不具合というのは、ゲームオーバーになった後でも数字をクリックできてしまうということです。つまり、ゲームオーバーなのにゲームオーバーになっていなくてゲームが続行できてしまうという凄い間抜けな事になっています。ちなみに、こういう間抜けな事はアマチュアが作ったゲームに限らずプロが作ったゲームでも発生することがあります。聞いた中で結構間抜けだったのは、セガのバーチャファイター2の初期バージョンのROMでは、投入したコインの数を忘れる(カウントしてなかったかクリアしてない)という具合で、無料でプレイできるというオチがあったそうで。また、バグではなく故意にゲームオーバー後/デモ画面などでプレイできるタイトーの影の伝説などもあります。

さて、ゲームオーバー後にゲームがプレイできないようにするためにフラグを用意します。ここではgameFlagという変数を用意しました。これがtrueならゲーム中、falseならゲーム中ではな事を示すようにします。最初の時点ではtrueにしておきます。まだ、ゲーム開始していないのにtrueというのはどうしてかと思う人がいるかもしれません。これはクリックしたらゲームが開始するようになっているためです。もし、falseにしてしまうとクリックしてもゲーム中ではないと判定されゲームがスタートしません。
このクリックされた時に判定する処理を関数addNumberの先頭で以下のように調べます。

if (gameFlag === false){ return; } // ゲーム中でない場合は以後の処理をしない

次にゲームオーバーになった時に以下のようにタイマー停止処理部分にgameFlagをfalseにします。これで、以後数字をクリックしても処理されずゲームがプレイできなくなります。

gameFlag = false; // ●ゲーム停止をフラグで示す。falseならゲーム中ではない
clearInterval(timerID); // タイマーを停止する
alert("ゲームオーバー");

以上でゲームオーバー後の不具合の解消は終わりです。次に重複処理をまとめます。まず、都合によりステージを動的に生成する部分を関数init内から独立させcreateStage関数にしました。これは、将来的に任意の個数の数字を生成する可能性があるためです。初期化する関数内にあると結局のところ固定した数になってしまうためです。分離することでステージをクリアするたびに縦横の数字を増やしたり減らしたりすることが可能になります。
次に数値をクリックして10の場合に表示していたOK!というメッセージと10でなかった場合のメッセージを表示しないようにします。これはオリジナルのTEN -10-になかった機能なので、とりあえずオリジナルに合わせて削除しておきます。基本的にはデバッグ用でしたので正常に動作するようになったら要らない処理です。
ステージクリアのメッセージの非表示処理や数値の表示処理はクリックされた時に処理する関数addNumber内にありましたが、これをランダムに値を設定する関数setRandomValueの最後に移動させます。本当はこの表示処理はsetRandomValueから独立させたいところですが、今回は入れ込んだ状態にとどめておきます。
今回の変更と修正はこれで終わりです。例によって変更があった部分は●印をつけてあります。なお、コメント部分の修正に関しては特に印はつけてありません。

次回はcanvas要素を使って数字を表示してみたいと思います。

[サンプルを実行する]

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

HTMLファイルの内容

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TEN</title>
<style>
#stage { width : 200px; height : 320px; background-image: url(images/bg.png); }
.num { width:48px; height: 48px; display: inline-block; }
#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="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;	// 最初にクリックしたかどうかのフラグ
var gameFlag = true;	// ●ゲーム中かどうかのフラグ。trueならゲーム中。
// 初期化処理
function init(){
	createStage();	// ステージを生成する
	var ele = document.querySelectorAll(".num");	// クリックしたら処理するようにイベントを設定
	for(var i=0; i<ele.length; i++){	// 要素の数だけ繰り返す
		ele[i].onclick = addNumber;	// クリック時に呼び出す関数を指定する
	}
	count = setRandomValue(ele);	// div要素の数を渡す。戻り値は要素の総数
	time = 30;	// 30秒に設定する
	document.getElementById("timeString").innerHTML = time + "秒";	// 時間を表示する
}
// ●ステージを動的に生成する
function createStage(){
	var w = 4;	// 横の数字の個数
	var h = 6;	// 縦の数字の個数
	var htmlString = "";
	for(var j=0; j<h; j++){
		for(var i=0; i<w; i++){
			htmlString = htmlString + '<div class="num"></div>'; 	// div要素で生成
		}
		htmlString = htmlString + '<br>';
	}
	document.getElementById("stage").innerHTML = htmlString;
}
// ランダムに値を設定する関数
function setRandomValue(ele){
	var len = ele.length;	// 要素の総数
	// img要素にランダムな値を設定する
	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);
	}
	// 配列要素をシャッフルする
	for(var i=0; i<len; i++){
		var p1 = Math.floor(Math.random() * len);	// 要素の数の範囲で乱数を発生させる
		var p2 = Math.floor(Math.random() * len);	// 要素の数の範囲で乱数を発生させる
		var n = num[p1];	// 2つの要素の内容を入れ替える
		num[p1] = num[p2];
		num[p2] = n;
	}
	// 要素に値を設定する
	for(var i=0; i<len; i++){	// 要素の数だけ繰り返す
		ele[i].value = num[i];	// 値を設定する
		ele[i].style.backgroundImage = "url(images/"+num[i]+".png)";	// 画像を表示する
	}
	// ●表示処理
	document.getElementById("clearMsg").style.visibility = "hidden";	// ステージクリアメッセージを消す
	for(var i=0; i<len; i++){	// 要素の数だけ繰り返す
		ele[i].style.backgroundImage = "url(images/"+num[i]+".png)";	// 画像を表示する
		ele[i].style.visibility = "visible";	// 表示する
		ele[i].style.backgroundColor = "";	// 透明にする
	}
	return ele.length;	// 要素の数を返す
}
// 2つの数字の合計が10かどうか調べる
function addNumber(){
	// ●ゲーム中かどうかを調べる
	if (gameFlag === false){ return; }	// ゲーム中でない場合は以後の処理をしない
	// 初めてのクリックかどうか調べる
	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にする
		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秒後に表示されているメッセージを消すためのタイマー
				var ele = document.querySelectorAll(".num");	// div.num要素を取得する
				count = setRandomValue(ele);	// 乱数値を設定する
				timerID = setInterval("displayTimer()", 1000);	// 1秒ごと定期的にタイマー表示関数を呼び出す
			}, 2000);
		}
		return;	// 関数から抜ける
	}
	// 合計が10でなかった場合
	prevNumber = 0;	// 数値を0にする
	prevEle.style.backgroundColor = "";	// 透明にする
}
// タイマーの表示処理
function displayTimer(){
	time = time - 1;
	document.getElementById("timeString").innerHTML = time + "秒";
	if (time < 1){	// 時間が1より少なくなったらゲームオーバー
		gameFlag = false;	// ●ゲーム停止をフラグで示す。falseならゲーム中ではない
		clearInterval(timerID);	// タイマーを停止する
		alert("ゲームオーバー");
	}
}
init();	// 初期化処理を呼び出す