画面に数字を表示し、それらを暗算またはそろばんや電卓を使って足し算して計算し、答え合わせをします。
問題の桁数や表示の時間を調節できるようにします。
<html> <head> <title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> </body> </html> |
メモ帳などテキストエディタに基本のHTMLを記入します。 |
<html>
<head>
<title>フラッシュナンバー</title>
</head>
<body>
<h3>フラッシュナンバー</h3>
<hr>
<form name="flash"> |
まず必要な機能を考えましょう。 問題となる数値の表示、問題の桁数の表示、 問題の間隔(秒数)、問題のスタート、解答の表示などです。 それらを画面上に組み込むためにフォームを使います。 解答の表示ボタンについては、スタートボタンを利用することにします。 |
<html> <head> <title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> <form name="flash"> |
スクリプトで初期設定を作りましょう。 配列変数setを宣言し、3つの値を設定します。 set[0]は問題の桁数。 |
<html> <head> <title>フラッシュナンバー</title> </head> <body> <h3>フラッシュナンバー</h3> <hr> <form name="flash"> |
まずは問題を表示するための流れを考えます。 スタートボタンを押し、ユーザー関数を呼び出して始まります。 2つのユーザー関数を用意します。 スタートボタンにもonClickを組み込みます。 初期設定で用意した3つの配列関数はユーザー関数f_startの中へ移動します。 |
(省略)
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_countでは、乱数を発生させ、変数rに入れています。 変数rはフォームに表示します。 |
(省略)
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_clearを用意するのは、問題終了後の処理を後で増やすためです。 「count++」は「count = count + 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; |
タイマーが二重に起動しないように、問題スタート後はスタートボタンを押せないようにします。 終了時には、また押せるように元に戻しましょう。 フォーム(flash)のボタン(b_start)に対して、disabledというプロパティに値を書き換えます。 disabledは、そのフォームの部品が使用不可の状態を表しており、値がtrueなら使用不可、falseなら使用可能になります。 条件判断文では、trueは真、falseは偽と呼ばれます。条件が成立しているか、していないかという意味です。 |
(省略)
function f_clear() {
clearInterval(timerID);
document.flash.number.value = "";
document.flash.b_start.disabled = false; |
問題終了後に最後の問題が残ってしまいますので、表示を削除します。 次にスタートボタンを解答ボタンに変更します。 |
(省略) flag = 0; |
スタートボタンを解答ボタンに変更したことを記録して、処理を分岐させる必要があります。 まずはその準備段階として、初期設定に関数flagを用意して、最初の状態を「0」と定めます。 次にスタートボタンを押されたときの処理を2つに分けます。 そして、ユーザー関数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に入力された解答を取り込みます。 条件式「s != ""」の「!=」は条件の否定ですので、「変数sが空ではない時」という意味になります。空の場合には答え合わせをせずに答えを表示します。 答えの表示が終わったら、ボタンをスタートに戻し、関数flagも0に戻します。 |
ここまでで、基本的な作りはできあがりました。後は課題や問題点を考えて、改良を重ねます。
・問題の文字の大きさ(文字色や背景色も検討)
・設定項目の見やすさ、使いやすさ(レイアウトの見直し、操作の簡便化)
・成績の記録を表示
・問題の表示間隔に空白が必要か(数字を連続で表示するより、一旦空白を差し挟むべきか)
・桁数を厳密に揃える必要があるか
・問題に0が表示される
(省略) <head> <title>フラッシュナンバー</title> <style type="text/css"> #number { font-size: 60px; font-weight: bold; text-align: right; } </style> </head> <body> |
CSSを使って問題の数値を見やすく調整しましょう。 まず、フォームの中の表示項目にidを追加します。 |
(省略) <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>
<title>フラッシュナンバー</title>
<style type="text/css">
#number {
font-size: 60px;
font-weight: bold;
text-align: right;
}
.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を掛けるのはそのまま記述できますが、最後に足す値が問題です。 最後の加算は桁数によって変わります。 その前に桁を揃えるために乗数を使いました。 |
(省略) </form> |
解答の記録を履歴として画面に表示します。 まず、フォームの下に表示する場所を取ります。 次に、スクリプトの初期設定に関数seisekiを加えます。この時点では空っぽです。 表示や更新の処理はユーザー関数f_kotaeの中で行います。 手順の最後に関数seisekiに対して、関数tと関数kotaeを付け加えますが、その前に半角スペースを入れておきます。こうしないと、前の答えと繋がってしまいます。 |
(省略) //初期設定 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です。 ユーザー関数f_kotaeの中で、答え合わせをする最初に関数cnt2を加算します。 表示については関数tを再利用します。 画面への出力に関数tを加えます。 |
(省略)
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つのタイマーが動きます。 そこで、setTimeout関数を使って、タイマーの流れを2段階に整理します。 まず、ユーザー関数f_startの中にあるsetInterval関数の行を削除します。(緑字) 次にユーザー関数f_countにsetTimeout関数を起きます。これで数値の表示から指定の時間後に次の処理に移ります。次の処理は空白を表示しますので、その間、数値を表示することになるのです。 ユーザー関数f_blankを新たに追加します。 これで、数値の表示と空白の表示を交互に時間を置きながら実行する流れができました。 |
(省略) blank = 100; (省略) function f_blank() { //空白表示 document.flash.number.value = ""; timerID = setTimeout('f_count()',blank); } (省略) |
あと少し、流れを整理しましょう。 まず、ユーザー変数f_blankの中で100/1000秒(0.1秒)の間隔を指定していますが、後で変更しやすいように、初期設定にその数値を起きましょう。 変数blankに置き換えて、初期設定で100と指定します。 |
(省略)
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;
}
(省略) |
表示される数値にカンマを付けましょう。 数値をカンマ付きの文字列に変換する必要があり、また繰り返し使えるようにユーザー関数にします。 正規表現は、数値や文字列を記号化して検索したり置き換えを効率よく実行する仕組みです。 問題の数値はユーザー関数f_countで表示していますので、表示の行でユーザー関数getCommaを呼び出します。 このとき、関数rの中身は数値のままだということが重要で、その後の関数rは数値のまま計算に利用できます。 答えの表示にもカンマが必要になりますので、ユーザー関数f_kotaeの関数kotaeを表示する3個所にもユーザー関数getCommaを記述します。 |
今後の改良がしやすいように、一旦これで完成とします。
あとは、設定項目を固定して手動での変更ができないように改良を加えていきます。
こちらの改良版では、設定変更をメニューで簡便化して誰でも簡単に間違いなく操作できるようにします。
その代わりにメニューで対応しない設定はできなくなってしまいますので、自由度を重視して練習する場合は、第1版を利用すると良いでしょう。
(省略) <form name="flash"> |
問題設定の3つの<input>タグを<select>タグに変更し、規定の設定内容から選択できるようにします。 桁数は1~12まで、問題数は5~100まで、秒数は2~0.05秒まで用意します。 |
(省略) 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); } } (省略) |
問題を開始する前に3つのカウントダウンを入れてからスタートするように変更します。 カウントダウン用のタイマーが必要になります。変数countがあるのでこれを借用します。 ユーザー関数f_start内にある変数countを削除(緑字)して、タイマー処理の位置に下げます。初期値は3に変更します。これがカウントダウンの最初の値となります。 ユーザー関数f_countdownを新たに作ります。 カウントダウンの表示テキストは初期設定に、 配列変数で3つのテキストを用意します。 |
(省略)
<head>
<title>フラッシュナンバー</title>
<style type="text/css">
#number {
font-size: 60px;
font-weight: bold;
text-align: right;
padding: 5px; |
CSSで、デザインを再調整しましょう。 問題の表示に枠線を追加し、背景に色を付けました。 文字のサイズや、ボタンのサイズも調整しておくと良いでしょう。 |
(省略) 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); } (省略) |
問題表示の文字色と、開始前カウントの文字色を変更しましょう。 カウントダウンは薄いグレーにして、問題が始まると黒に戻します。 初期値には、使用個所が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() { |
エンターキーでボタンが押せるように、フォーカスを置きます。 命令focusでフォームのボタンを指定するとカーソルがボタンに移動します。 ユーザー関数number_format内で、HTMLを呼び出した直後にフォーカスをボタンに置きます。 |
(省略) </form> <hr>
<p>【使い方】</p>
<p>問題設定の、桁数、問題数、表示の秒数を設定してスタートボタンを押すと、3秒後に始まります。</p>
<p>問題終了後は、解答表示ボタンを押すと答えが表示されます。このとき、問題表示枠に答えを入力してから解答表示ボタンを押すと答え合わせができます。</p>
<hr>
<div id="f_seiseki"></div> (省略)
フォームの下に簡単な説明文を入れます。第三者が使うことを意識して使い方を記述しておきましょう。
セレクトメニュー版の完成です。
HTML5が普及するようになれば、コンボボックスが使えるようになりますので、第1版と第2版を統合した<input>タグが使えるようになります。