[Labyrinthe Noir]>[Top]>[こども工作教室]>

「フラッシュナンバー」のソースと解説

基本(第1版)

プログラムの説明

画面に数字を表示し、それらを暗算またはそろばんや電卓を使って足し算して計算し、答え合わせをします。
問題の桁数や表示の時間を調節できるようにします。

保存ファイルの準備

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr>
</body> </html>

メモ帳などテキストエディタに基本のHTMLを記入します。
ファイル名は「flash.html」で保存します。

表示フォームの作成

まず必要な機能を考えましょう。問題となる数値の表示、問題の桁数の表示、 問題の間隔(秒数)、問題のスタート、解答の表示などです。
それらを画面上に組み込むためにフォームを作りましょう。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> <form name="flash">
<input type="text" name="number" size="10">
<input type="button" name="b_start" value="スタート">
問題設定:
<input type="text" name="set1" size="3" value="2">桁
<input type="text" name="set2" size="3" value="10">問
<input type="text" name="set3" size="5" value="1">秒
</form>
</body> </html>

<form>タグを用意して、name属性にflashと名前を付けます。
入力欄は3つ、問題の数値、桁数、間隔です。これら<input>タグにも名前を付けておきます。このように入出力を繰り返すフォームには名前が必要です。
スタートボタンも用意して、これにも名前を付けておきます。スタート後に誤って押してしまわないように、スタート後は押せないようにしたいと思いますので、名前を付けて制御ができるようにしておきます。

解答の表示ボタンについては、スタートボタンを利用することにします。

【この時点の flash.html を別枠で表示】

初期値を設定する

スクリプトで初期設定を作りましょう。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> <form name="flash">
<input type="text" name="number" size="10">
<input type="button" name="b_start" value="スタート">
問題設定:
<input type="text" name="set1" size="3" value="2">桁
<input type="text" name="set2" size="3" value="10">問
<input type="text" name="set3" size="5" value="1">秒
</form> <script type="text/javascript"> //初期設定 set = new Array(); set[0] = document.flash.set1.value; set[1] = document.flash.set2.value; set[2] = document.flash.set3.value; </script> </body> </html>

フォームから問題設定の初期値をもらってきて変数へ入れておきます。

配列変数setを宣言し、3つの値を設定します。それぞれが何を意味するか覚えておく必要があります。

set[0]は問題の桁数。
set[1]は問題数。
set[2]は表示間隔。

問題を表示する準備

問題を表示するための流れを作りましょう。スタートボタンを押すと問題が始まるようにします。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> <form name="flash">
<input type="text" name="number" size="10">
<input type="button" name="b_start" value="スタート" onClick="f_start()">
問題設定:
<input type="text" name="set1" size="3" value="2">桁
<input type="text" name="set2" size="3" value="10">問
<input type="text" name="set3" size="5" value="1">秒
</form> <script type="text/javascript"> //初期設定 set = new Array(); function f_start() { //初期値 set[0] = document.flash.set1.value; set[1] = document.flash.set2.value; set[2] = document.flash.set3.value; } </script> </body> </html>


ユーザー関数f_start()を作成し、初期設定で用意した3つの配列関数をその中へ移動します。また、スタートボタンからもonClickで呼び出せるようにします。

問題を一定間隔で表示する

問題を一定間隔で呼び出すためのタイマーを作ります。

function f_start() {
	//初期値
	set[0] = document.flash.set1.value;
	set[1] = document.flash.set2.value;
	set[2] = document.flash.set3.value;
	//タイマー起動
	timerID = setInterval('f_count()',set[2]*1000);
}

function f_count() {
	//乱数の生成
	var r = Math.random();
	r = Math.floor(r * Math.pow(10,set[0]));
	//数値の表示
	document.flash.number.value = r;
}

ユーザー関数f_start()の最後でタイマーを起動します。
このとき、set[2]には秒数が入っているため、これを1000倍にします。関数setInterval()では1/1000秒単位で間隔を設定する必要があるためです。

次にユーザー関数f_countを用意します。
1行目で乱数を発生させ、変数rに入れています。
2行目では、桁数を決めるために10の倍数を掛けます。乱数は0から1未満の小数なので、10倍すると0から10未満の数となります。2桁の場合、10の2乗で100未満となり、3桁なら10の3乗で1000未満となります。乗数は関数pow()に2つの引数を当てて計算します。nのx乗ならば、pow(n,x)と表記します。その計算後にfloor()で切り捨てをして整数にします。
3行目では、変数rをフォームに表示しています。

【この時点の flash.html を別枠で表示】

問題を一定回数表示する

先ほどのままでは問題が終わらないので、次に一定回数で止まるように手を加えます。

function f_start() {
	//初期値
	set[0] = document.flash.set1.value;
	set[1] = document.flash.set2.value;
	set[2] = document.flash.set3.value;
	//タイマー起動
	timerID = setInterval('f_count()',set[2]*1000);
	count = 0;
}

function f_count() {
	count++;
	if (set[1] < count) {
		f_clear();
	} else {
		//乱数の生成
		var r = Math.random();
		r = Math.floor(r * Math.pow(10,set[0]));
		//数値の表示
		document.flash.number.value = r;
	}
}

function f_clear() {
	//タイマー停止
	clearInterval(timerID);
}

まず、ユーザー関数f_start()に、変数countを用意します。
次に、ユーザー関数f_count()で、変数countに1を足して回数を記録します。
そして、if文で、set[1]に入れた問題数を超えたらユーザー関数f_clearに飛んで、タイマーを終了させます。
else文の中に元のスクリプトを入れて、終了時は実行されないようにしておきましょう。

ここで新たにユーザー関数f_clearを用意するのは、問題終了後の処理を後で増やすためです。

「count++」は「count = count + 1」の省略形です。

【この時点の flash.html を別枠で表示】

スタートボタンの表示変更(1)

タイマーが二重に起動しないように、問題スタート後はスタートボタンを押せないようにします。
終了時には、また押せるように元に戻します。

function f_start() {
	//初期値
	set[0] = document.flash.set1.value;
	set[1] = document.flash.set2.value;
	set[2] = document.flash.set3.value;
	//タイマー起動
	timerID = setInterval('f_count()',set[2]*1000);
	count = 0;
	document.flash.b_start.disabled = true;
} function f_count() { count++; if (set[1] < count) { f_clear(); } else { //乱数の生成 var r = Math.random(); r = Math.floor(r * Math.pow(10,set[0])); //数値の表示 document.flash.number.value = r; } } function f_clear() { //タイマー停止 clearInterval(timerID); document.flash.b_start.disabled = false;
}

フォーム(flash)のボタン(b_start)に対して、disabledというプロパティに値を書き換えます。
ユーザー関数f_startではtrueという値を、ユーザー関数f_clearではfalseという値を与えています。

disabledは、そのフォームの部品が使用不可の状態を表しており、値がtrueなら使用不可、falseなら使用可能になります。

条件判断文では、trueは真、falseは偽と呼ばれます。条件が成立しているか、していないかという意味です。
プロパティの状態を表す場合、その状態であるのか、ないのかという意味になります。
このように正か非かを表す定数は変数に入れることも可能で、ブーリアン型(論理型)の定数や変数と言います。

問題終了直後の表示

問題終了後に最後の問題が残ってしまいますので、表示を削除します。

function f_clear() {
	clearInterval(timerID);
	document.flash.number.value = "";
	document.flash.b_start.disabled = false;
document.flash.b_start.value = "解答表示";
}

タイマー停止後、ユーザー関数f_clearで空の文字列を表示させます。

次にスタートボタンを解答ボタンに変更します。
同じボタンを使うためこのままではスタートボタンの処理は変わりません。

ボタンの状態を記録する

スタートボタンを解答ボタンに変更したことを記録して、処理を分岐させる必要があります。

flag = 0;
function f_start() { if (flag == 0) { //初期値 set[0] = document.flash.set1.value; set[1] = document.flash.set2.value; set[2] = document.flash.set3.value; //タイマー起動 timerID = setInterval('f_count()',set[2]*1000); count = 0; document.flash.b_start.disabled = true; } else { f_kotae(); } } (省略) function f_clear() { //タイマー停止 clearInterval(timerID); document.flash.number.value = ""; document.flash.b_start.disabled = false; document.flash.b_start.value = "解答表示"; flag = 1; }

まずは準備段階として、初期設定に関数flagを用意して、最初の状態を「0」と定めます。

次にスタートボタンを押されたときの処理を2つに分けます。
関数flagが「0」の時は、今までの処理を実行し、それ以外の時は、新たにユーザー関数f_kotaeの中で処理を行うことにします。

そして、ユーザー関数f_clearでは、ボタンの表示を変更したので、関数flagも変更して、状況が変わったことを記録します。

このように変化する状況を記録したものをフラグと呼びます。

解答の記録を取る

解答の表示をする前に、画面に表示された数値を全て加算して、記録を残します。

function f_start() {
	if (flag == 0) {
		//初期値
		set[0] = document.flash.set1.value;
		set[1] = document.flash.set2.value;
		set[2] = document.flash.set3.value;
		//タイマー起動
		timerID = setInterval('f_count()',set[2]*1000);
		count = 0;
		document.flash.b_start.disabled = true;
		kotae = 0;
	} else {
		f_kotae();
	}
}

function f_count() {
	count++;
	if (set[1] < count) {
		f_clear();
	} else {
		//乱数の生成
		var r = Math.random();
		r = Math.floor(r * Math.pow(10,set[0]));
		//数値の表示
		document.flash.number.value = r;
		kotae += r;
	}
}

ユーザー関数f_startの処理に、関数kotaeを用意します。

ユーザー関数f_countでは、問題の数値が入っている変数rがあるので、これを変数kotaeと足して入れ直しています。

「kotae += r」は「kotae = kotae + r」の省略形です。

解答の表示

解答の表示方法としては、ただ答えを表示することもできますが、問題の表示欄に答えを入力することで答え合わせができるようにしてみましょう。

function f_kotae() {
	var s = document.flash.number.value;
	if (s != "") {
		//答え合わせ
		if (s == kotae) {
			alert("正解です!!");
		} else {
			alert("答えは" + kotae + "でした。");
		}
	} else {
		//入力なし
		alert("答えは" + kotae + "です。");
	}
	//初期化
	document.flash.b_start.value = "スタート";
	flag = 0;
	document.flash.number.value = "";
}

新たにユーザー関数f_kotaeを用意します。

まずは変数sに入力された解答を取り込みます。
次のif文で、入力されたかどうかを確認して分岐します。

条件式「s != ""」の「!=」は条件の否定ですので、「変数sが空ではない時」という意味になります。空の場合には答え合わせをせずに答えを表示します。

答えの表示が終わったら、ボタンをスタートに戻し、関数flagも0に戻します。
解答は次に備えて空白で消しておきましょう。

【この時点の flash.html を別枠で表示】

基本機能の完成と検討課題

ここまでで、基本的な作りはできあがりました。後は課題や問題点を考えて、改良を重ねます。

・問題の文字の大きさ(文字色や背景色も検討)
・設定項目の見やすさ、使いやすさ(レイアウトの見直し、操作の簡便化)
・成績の記録を表示
・問題の表示間隔に空白が必要か(数字を連続で表示するより、一旦空白を差し挟むべきか)
・桁数を厳密に揃える必要があるか
・問題に0が表示される

問題の文字サイズを調整

CSSを使って問題の数値を見やすく調整しましょう。

<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> <style type="text/css"> #number { font-size: 60px; font-weight: bold; text-align: center; } </style> </head> <body>
<h3>フラッシュナンバー</h3>
<hr>
<form name="flash">
<input type="text" name="number" size="10" id="number">
<input type="button" name="b_start" value="スタート" onClick="f_start()">
問題設定:
<input type="text" name="set1" size="3" value="2">桁
<input type="text" name="set2" size="3" value="10">問
<input type="text" name="set3" size="5" value="1">秒
</form>

まず、フォームの中の表示項目にidを追加します。
そして、CSSのための<style>タグを使って文字サイズ、文字の太さ、配置を右に設定します。

フォームのレイアウトを調整

<body>
<h3>フラッシュナンバー</h3>
<hr>
<form name="flash">
<p align="center"><input type="text" name="number" size="10" id="number"></p>
<p align="center"><input type="button" name="b_start" value="スタート" onClick="f_start()"></p>
<p align="center">問題設定:
<input type="text" name="set1" size="3" value="2">桁
<input type="text" name="set2" size="3" value="10">問
<input type="text" name="set3" size="5" value="1">秒</p>
</form>

フォームを<p>タグで3つに分轄し、中央に行揃えを行います。

ボタンや設定項目の大きさを調整

<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> <style type="text/css"> #number { font-size: 60px; font-weight: bold; text-align: center; } .button { font-size: 14px; } .setting { font-size: 14px; text-align: right; } </style> </head> <body>
<h3>フラッシュナンバー</h3>
<hr>
<form name="flash">
<p align="center"><input type="text" name="number" size="10" id="number"></p>
<p align="center"><input type="button" name="b_start" value="スタート" onClick="f_start()" class="button"></p>
<p align="center">問題設定:
<input type="text" name="set1" size="3" value="2" class="setting">桁
<input type="text" name="set2" size="3" value="10" class="setting">問
<input type="text" name="set3" size="5" value="1" class="setting">秒</p>
</form>

フォームのボタンや設定項目の数字を大きくします。

ボタンにはbuttonクラスを、設定項目にはsettingクラスを割り当てます。

CSSで、buttonクラスには文字サイズを設定します。
settingクラスには文字サイズと行揃えを左に設定します。

問題の桁数を揃える方法を考える

まずは問題の数値の桁数を揃えるために必要な方法を考えてみましょう。

4桁の場合、乱数で出来た値は0~0.99999999....の値を1000倍にして切り捨て、0~9999の値にして表示しています。
このとき、0~999までの数字は要件を満たしませんので、これらを除外して再計算をさせる必要があります。

再計算をさせるとなると、条件判断をして計算をもう一度実行しますが、それでもまた再計算が必要になる可能性もあります。
これが多く繰り返されると表示が遅くなる原因となります。

それでは、一回の計算で実行する方法を考えてみましょう。
除外したい値は全体の1割の部分です。逆に言うと、必要な部分は9割(90%)と言うことになります。
それならば、乱数の値に0.9を掛けてから4桁にすると0~8999までの値が取れます。これに1000を足すと1000~9999の値になります。

最小値と最大値を実際に計算式を作って検証すると判りやすくなります。以下の表は計算の手順に沿って、左から順に値の変化を見ています。実際の乱数は小数点以下16桁です。

  random() *10000 *0.9 +1000 floor()
最小値 0 0 0 1000 1000
最大値 0.99999999 9999.9999 8999.9991 9999.9991 9999

このように実際に計算をしてみると、正しい値の範囲が取れることが判ります。数値の検証はプログラムの精度を確認する上でも重要な作業です。
注意点としては、最後に切り捨てをすることと、計算の途中は桁数の大きな値のまま使うことが重要です。
実際に0.9を掛けるというのは大ざっぱな部分があり、その時点で最終桁は9ではなく1になっています。これが誤差に繋がる部分ですが、余分に桁数があるため、切り捨てるとその誤差は消えてなくなっているのです。
このような計算に誤差は付きものですが、それを小さくすることで精度を保つのです。

ブラウザのほとんどは2進数で計算をしているため、簡単な除算(割り算)で誤差が発生します。
除算をするときは、出来るだけ最後にするなどして、途中で誤差を出さないように工夫が必要です。
途中で誤差が出ると、それが大きくなって行くためです。最終的な誤差はほとんど回避不能です。

この他の方法として、桁数の回数分一桁の乱数を作って、文字列として繋げるという方法もあります。仕組みは単純ですが、スクリプトにすると意外と行数が必要になります。

問題の桁数を揃える

計算式が分かったので、これをスクリプトにします。

function f_count() {
	count++;
	if (set[1] < count) {
		f_clear();
	} else {
		//乱数の生成
		var r = Math.random();
		r = Math.floor(r * Math.pow(10,set[0]) * 0.9 + Math.pow(10,set[0]-1));
		//数値の表示
		document.flash.number.value = r;
		kotae += r;
	}
}

0.9を掛けるのはそのまま記述できますが、最後に足す値が問題です。

最後の加算は桁数によって変わります。3桁の場合は100、4桁の場合は1000です。

その前に桁を揃えるために乗数を使いました。こちらは3桁で1000、4桁で10000です。これと比較して1桁少ないことが判ります。乗数から-1をすると桁数が1つ減ることになります。

解答と履歴の表示(1)

解答の記録を履歴として画面に表示します。

(省略)

</form>
<hr> <div id="f_seiseki"></div> <script type="text/javascript"> //初期設定 set = new Array(); flag = 0; seiseki = ""; (省略) function f_kotae() { var s = document.flash.number.value; var t = ""; if (s != "") { //答え合わせ if (s == kotae) { alert("正解です!!"); t = "○"; } else { alert("答えは" + kotae + "でした。"); t = "×"; } } else { //入力なし alert("答えは" + kotae + "です。"); } //初期化 document.flash.b_start.value = "スタート"; flag = 0; document.flash.number.value = ""; //成績の履歴 seiseki += " " + t + kotae; document.getElementById("f_seiseki").innerHTML = "【履歴】" + seiseki; }

まず、フォームの下に表示する場所を取ります。
<hr>タグで区切って、<div>タグにidを付けます。
後で、このidに対して、HTMLを出力します。

次に、スクリプトの初期設定に関数seisekiを加えます。この時点では空っぽです。

表示や更新の処理はユーザー関数f_kotaeの中で行います。
まず、関数tを用意します。
これは正解のときと不正解のときに○か×を表示するために使います。
答え合わせをしない場合は、関数tは空のままです。

手順の最後に関数seisekiに対して、関数tと関数kotaeを付け加えますが、その前に半角スペースを入れておきます。こうしないと、前の答えと繋がってしまいます。
そして、関数seisekiをidがf_seisekiの部分に対してHTML形式で出力しています。

解答と履歴の表示(2)

成績の履歴が増えると正解の数を確認するのが手間になります。
問題数と正解数を記録して表示することにしましょう。

//初期設定
set = new Array();

flag = 0;
seiseki = "";
cnt1 = 0;
cnt2 = 0;

(省略)

function f_kotae() {
	var s = document.flash.number.value;
	var t = "";
	if (s != "") {
		//答え合わせ
		cnt2++;
		if (s == kotae) {
			alert("正解です!!");
			t = "○";
			cnt1++;
		} else {
			alert("答えは" + kotae + "でした。");
			t = "×";
		}
	} else {
		//入力なし
		alert("答えは" + kotae + "です。");
	}
	//初期化
	document.flash.b_start.value = "スタート";
	flag = 0;
	document.flash.number.value = "";
	//成績の履歴
	seiseki += " " + t + kotae;
	if (cnt2 > 0) {
		t = " [" + cnt1 + "/" + cnt2 + "]";
	}
	document.getElementById("f_seiseki").innerHTML = "【履歴】" + seiseki + t;
}

初期設定に関数cnt1と関数cnt2を加えます。初期値は0です。
関数cnt1は正解数を記録し、関数cnt2は問題数を記録することとします。

ユーザー関数f_kotaeの中で、答え合わせをする最初に関数cnt2を加算します。
次に正解の場合に関数cnt1を加算します。

表示については関数tを再利用します。
if文で関数cnt2が0より大きい場合に、正解数と問題数を文字列として関数tに入れ換えます。

画面への出力に関数tを加えます。

【この時点の flash.html を別枠で表示】

数値の表示後に空白を入れる(1)

問題の数値が切り替わる瞬間に空白を挟みます。
空白時間が長いほど、問題の切り替えを感じやすくなりますが、ここでは0.1秒で設定してみましょう。

function f_start() {
	if (flag == 0) {
		//初期値
		set[0] = document.flash.set1.value;
		set[1] = document.flash.set2.value;
		set[2] = document.flash.set3.value;

		timerID = setInterval('f_count()',set[2]*1000);
		count = 0;
		document.flash.b_start.disabled = true;
		kotae = 0;
		//タイマー処理
		f_count();
	} else {
		f_kotae();
	}
}

function f_count() {
	count++;
	if (set[1] < count) {
		f_clear();
	} else {
		//乱数の生成
		var r = Math.random();
		r = Math.floor(r * Math.pow(10,set[0]) * 0.9 + Math.pow(10,set[0]-1));
		//数値の表示
		document.flash.number.value = r;
		kotae += r;

		timerID = setTimeout('f_blank()',set[2]*1000);
	}
}

function f_blank() {
	//空白表示
	document.flash.number.value = "";
	timerID = setTimeout('f_count()',100);
}

function f_clear() {
	//タイマー停止
	clearInterval(timerID);
	document.flash.number.value = "";
	document.flash.b_start.disabled = false;
	document.flash.b_start.value = "解答表示";
	flag = 1;
}

これまではsetInterval関数を使っていましたが、これでは連続的に1つのタイマーが動きます。
しかし、空白時間を別のタイマーで動かす場合、連続タイマーでは2つの時間を1秒以内でタイミングよく動かし続けることは難しいでしょう。

そこで、setTimeout関数を使って、タイマーの流れを2段階に整理します。

まず、ユーザー関数f_startの中にあるsetInterval関数の行を削除します。(青字抹消)
そして、ユーザー関数f_countの呼び出しを処理の最後に加えます。これは各変数の設定が終わってから次の処理に移りたいからです。

次にユーザー関数f_countにsetTimeout関数を起きます。これで数値の表示から指定の時間後に次の処理に移ります。次の処理は空白を表示しますので、その間、数値を表示することになるのです。

ユーザー関数f_blankを新たに追加します。
まず、空白を表示します。この行はユーザー関数f_clearから移動してきます。
そして、もう1つのsetTimeout関数を置いて、0.1秒後にユーザー関数f_countを呼び出すようにします。

これで、数値の表示と空白の表示を交互に時間を置きながら実行する流れができました。

数値の表示後に空白を入れる(2)

あと少し、流れを整理しましょう。
まず、ユーザー変数f_blankの中で100/1000秒(0.1秒)の間隔を指定していますが、後で変更しやすいように、初期設定にその数値を起きましょう。

blank = 100;

(省略)

function f_blank() {
	//空白表示
	document.flash.number.value = "";
	timerID = setTimeout('f_count()',blank);
}

変数blankに置き換えて、初期設定で100と指定します。

数値の表示後に空白を入れる(3)

function f_count() {
	count++;
	if (set[1] < count) {
		f_clear();
	} else {
		//乱数の生成
		var r = Math.random();
		r = Math.floor(r * Math.pow(10,set[0]) * 0.9 + Math.pow(10,set[0]-1));
		//数値の表示
		document.flash.number.value = r;
		kotae += r;

		timerID = setTimeout('f_blank()',set[2]*1000);
	}
}

function f_blank() {
	//空白表示
	document.flash.number.value = "";
	if (set[1] == count) {
		f_clear();
	} else {
		timerID = setTimeout('f_count()',blank);
   }
}

ユーザー関数f_countにあるif文の分岐処理をユーザー関数f_blankの中に移動します。これにより、最後に一回多くユーザー関数f_countに入ることがなくなりますし、空白表示後、すぐに終了することになります。

変数countが丁度設定回数と同じになりますので、if文の条件式は「<」から「==」に変更が必要です。

数値にカンマを付ける

表示される数値にカンマを付けましょう。

function f_count() {
	count++;
	//乱数の生成
	var r = Math.random();
	r = Math.floor(r * Math.pow(10,set[0]) * 0.9 + Math.pow(10,set[0]-1));
	//数値の表示
	document.flash.number.value = getComma(r);
	kotae += r;
    
   timerID = setTimeout('f_blank()',set[2]*1000);
}

function f_kotae() {
	var s = document.flash.number.value;
	var t = "";
	if (s != "") {
		//答え合わせ
		cnt2++;
		if (s == kotae) {
			alert("正解です!!");
			t = "○";
			cnt1++;
		} else {
			alert("答えは" + getComma(kotae) + "でした。");
			t = "×";
		}
	} else {
		//入力なし
		alert("答えは" + getComma(kotae) + "です。");
	}
	//初期化
	document.flash.b_start.value = "スタート";
	flag = 0;
	document.flash.number.value = "";
	//成績の履歴
	seiseki += " " + t + getComma(kotae);
	if (cnt2 > 0) {
		t = " [" + cnt1 + "/" + cnt2 + "]";
	}
	document.getElementById("f_seiseki").innerHTML = "【履歴】" + seiseki + t;
}

function getComma(num) {
	num = new String(num).replace(/,/g, "");
	while(num != (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
	return num;
}

数値をカンマ付きの文字列に変換する必要があり、また繰り返し使えるようにユーザー関数にします。
今回は細かく解説はしませんが、効率よく変換させるために、関数replace()で正規表現を使っています。

正規表現は、数値や文字列を記号化して検索したり置き換えを効率よく実行する仕組みです。
JavaScript特有の仕組みではなく、他のプログラムでも利用されています。

問題の数値はユーザー関数f_countで表示していますので、表示の行でユーザー関数getCommaを呼び出します。

このとき、関数rの中身は数値のままだということが重要で、その後の関数rは数値のまま計算に利用できます。

答えの表示にもカンマが必要になりますので、ユーザー関数f_kotaeの関数kotaeを表示する3個所にもユーザー関数getCommaを記述します。

完成(第1版)

今後の改良がしやすいように、一旦これで完成とします。
あとは、設定項目を固定して手動での変更ができないように改良を加えていきます。

【この時点の flash.html を別枠で表示】

改良(第2版)

こちらの改良版では、設定変更をメニューで簡便化して誰でも簡単に間違いなく操作できるようにします。
その代わりにメニューで対応しない設定はできなくなってしまいますので、自由度を重視して練習する場合は、第1版を利用すると良いでしょう。

セレクトメニューに変更

<form name="flash">
<p align="center"><input type="text" name="number" size="10" id="number"></p>
<p align="center"><input type="button" name="b_start" value="スタート" onClick="f_start()" class="button"></p>
<p align="center">問題設定: <select name="set1" class="setting"> <option value="1">1</option> <option value="2" selected>2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8">8</option> <option value="9">9</option> <option value="10">10</option> <option value="11">11</option> <option value="12">12</option> </select><select name="set2" class="setting"> <option value="5">5</option> <option value="10" selected>10</option> <option value="15">15</option> <option value="20">20</option> <option value="30">30</option> <option value="40">40</option> <option value="50">50</option> <option value="100">100</option> </select><select name="set3" class="setting"> <option value="2">2</option> <option value="1" selected>1</option> <option value="0.5">0.5</option> <option value="0.4">0.4</option> <option value="0.3">0.3</option> <option value="0.2">0.2</option> <option value="0.1">0.1</option> <option value="0.05">0.05</option> </select> 秒</p> </form>

問題設定の3つの<input>タグを<select>タグに変更し、規定の設定内容から選択できるようにします。

桁数は1~12まで、問題数は5~100まで、秒数は2~0.05秒まで用意します。

カウントダウン

問題を開始する前に3つのカウントダウンを入れてからスタートするように変更します。

cd_text = new Array("①","②①","③②①");

(省略)

function f_start() {
	if (flag == 0) {
		//初期値
		set[0] = document.flash.set1.value;
		set[1] = document.flash.set2.value;
		set[2] = document.flash.set3.value;

		count = 0;
		document.flash.b_start.disabled = true;
		kotae = 0;
		//タイマー処理
		count = 3;
		f_countdown();
	} else {
		f_kotae();
	}
}

function f_countdown() {
	count--;
	document.flash.number.value = cd_text[count];
	if (count == 0) {
		timerID = setTimeout('f_blank()',1000);
	} else {
		timerID = setTimeout('f_countdown()',1000);
	}
}

カウントダウン用のタイマーが必要になります。変数countがあるのでこれを借用します。

ユーザー関数f_start内にある変数countを削除(緑字)して、タイマー処理の位置に下げます。初期値は3に変更します。これがカウントダウンの最初の値となります。
そして、ユーザー関数f_countを呼び出していた部分をユーザー関数f_countdownに変更します。

ユーザー関数f_countdownを新たに作ります。
この中で変数countから1を引きます。そして、カウントダウン用表示を行い、変数countが0になるまで1秒ごとに処理を繰り返し、0になったときに問題の表示を始めます。
ここでは、ユーザー関数f_countを呼び出さずにf_blankを呼び出して、一旦空白を入れてから開始しています。

カウントダウンの表示テキストは初期設定に、 配列変数で3つのテキストを用意します。

問題枠のデザイン調整(1)

CSSで、デザインを再調整しましょう。

<head>
<meta charset="utf-8">
<title>フラッシュナンバー</title> <style type="text/css"> #number { font-size: 60px; font-weight: bold; text-align: center; padding: 5px;
border: 5px solid #999999;
background-color: #FFFFCC;

} .button { font-size: 14px; } .setting { font-size: 14px; text-align: right; } </style> </head> <body>

問題の表示に枠線を追加し、背景に色を付けました。

文字のサイズや、ボタンのサイズも調整しておくと良いでしょう。
font-sizeで値を設定します。

問題枠のデザイン調整(2)

問題表示の文字色と、開始前カウントの文字色を変更しましょう。

color_text = "#000000";
color_count = "#CCCCCC";

(省略)

function f_countdown() {
	count--;
	document.flash.number.style.color = color_count;
	document.flash.number.value = cd_text[count];
	if (count == 0) {
		timerID = setTimeout('f_blank()',1000);
	} else {
		timerID = setTimeout('f_countdown()',1000);
	}
}

function f_count() {
	count++;
	//乱数の生成
	var r = Math.random();
	r = Math.floor(r * Math.pow(10,set[0]) * 0.9 + Math.pow(10,set[0]-1));
	//数値の表示
	document.flash.number.style.color = color_text;
	document.flash.number.value = getComma(r);
	kotae += r;

	timerID = setTimeout('f_blank()',set[2]*1000);
}

カウントダウンは薄いグレーにして、問題が始まると黒に戻します。
まずは、初期値として変数color_textとcolor_countで色番号を指定しておきます。
この位置にまとめておくと、後から色を変更したい場合に判りやすくなります。

初期値には、使用個所が1つしかない値であっても、後々のメンテナンスや、他人が改良しやすいように変数に置き換えてまとめておくと良いでしょう。
それぞれに注釈を付けておけば、誰が見ても判りやすく簡単に手直しができます。

ユーザー関数f_countdownの中に変数color_countの値をCSSに適用する行を入れます。
表示の前に色を適用します。

色を戻すのはユーザー関数f_countの中になります。
こちらも同様に表示の前に適用します。

初期設定の追加

初期値を設定した後に、画面の初期化をする処理を組み入れておきます。

number_format();

function number_format() {
	//初期化
	document.flash.reset();
	document.flash.number.value = "";
	document.flash.number.style.color = color_text;
	document.flash.b_start.disabled = false;
}

表示枠の空白、文字色の設定、ボタンの状態を初期の状態に設定し直します。
このあたりはブラウザによっても対応が違うため、主要なブラウザでテストをして確認します。

また、 フォームの初期化をする命令にreset()がありますので、これも最初に実行しておきます。
フォームのリセットボタンを押したのと同じ動作になります。

設定をしなくても本来は初期状態になってるはずですが、スクリプトの動作途中でページを更新したときに、途中で変更した設定がそのまま残ってしまうことがあるからです。
特に、ボタンの状態が元に戻らないと、次の問題を始めることができなくなってしまいます。

ユーザー関数にまとめておけば、処理の終わる度に初期化をすることもできるようになります。

利用者が思わぬことをやることがあります。
できるだけ、そのような時にも対応できるように様々な失敗や誤操作に対応しておくことも重要です。

エンターキーによる誤動作をなくす

(省略)

<form name="flash" onSubmit="return false;">
<p align="center"><input type="text" name="number" size="10" id="number"></p>
<p align="center"><input type="button" name="b_start" value="スタート" onClick="f_start()" class="button"></p>
<p align="center">問題設定:
(省略)

フォームは、入力項目にカーソルがあるときにエンターキー(リターンキー)を押すと、送信ボタンを押したことになりフォームを送信してしまいます。
このフォームでは送り先の設定をしていないため、このフォームのあるHTMLを送り先としてしまうため、再読込(更新)してしまいます。

そこで、このような不要な送信を実行しないようにする必要があります。

<form>タグの中にonSubmitでイベント処理を行います。
これはフォームの送信が発生したときに呼び出されて、送信を中止させる内容になっています。

returnは、それに続く値を返します。ここではフォームに返しています。値はtrueかfalseのどちらかで、trueなら送信許可、falseなら送信中止となります。

ボタンをフォーカスする

エンターキーでボタンが押せるように、フォーカスを置きます。

function number_format() {
//初期化
document.flash.reset();
document.flash.number.value = "";
document.flash.number.style.color = color_text;
document.flash.b_start.disabled = false;
document.flash.b_start.focus();
}
function f_clear() {
//タイマー停止
clearInterval(timerID);
document.flash.b_start.disabled = false;
document.flash.b_start.value = "解答表示";
document.flash.b_start.focus();
flag = 1;
}

命令focusでフォームのボタンを指定するとカーソルがボタンに移動します。

ユーザー関数number_format内で、HTMLを呼び出した直後にフォーカスをボタンに置きます。
ユーザー関数f_clearでは、ボタンの表記を変更後にフォーカスを置いています。

説明文の挿入

</form>

<hr>
<p>【使い方】</p>
<p>問題設定の、桁数、問題数、表示の秒数を設定してスタートボタンを押すと、3秒後に始まります。</p>
<p>問題終了後は、解答表示ボタンを押すと答えが表示されます。このとき、問題表示枠に答えを入力してから解答表示ボタンを押すと答え合わせができます。</p>

<hr>
<div id="f_seiseki"></div>

フォームの下に簡単な説明文を入れます。第三者が使うことを意識して使い方を記述しておきましょう。

完成(第2版)

セレクトメニュー版の完成です。
HTML5が普及するようになれば、コンボボックスが使えるようになりますので、第1版と第2版を統合した<input>タグが使えるようになります。

【この時点の flash.html を別枠で表示】

戻る