フラッシュ暗算(改善)


今回は前回までに問題のあった箇所、変更したほうが良い箇所の改善を行います。

まずは、ゲーム起動して1回目の1問目だけしか「入力ボックスにフォーカスが当たっていない」問題について。
2問目以降もオブジェクトを全て消した後に表示させているので、
「なぜ1回目の1問目だけはフォーカスが当たっているのか?」という疑問が沸きますが、
とりあえず「オブジェクト表示させた後に入力ボックスにフォーカスを当てる」ことで問題は解決できるので
以下の7行目のように追加修正しておきましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
*game_answer
	color 255, 255, 255 : boxf : color
	font msmincho, 15
	pos 10, 10 : mes "合計値を入力してください。"
	pos 10, 40 : input answer, 100, 20
	pos 10, 70 : button gosub "解答", *click_button
	objsel 0
	font msmincho, SIZE / 3 * 2
	click = 0
	repeat 10, 1
		wait 100
		if click : break
		redraw 0
		color 255, 255, 255 : boxf (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2 : color
		circle (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2, (ginfo_winx + SIZE) / 2, (ginfo_winy + SIZE) / 2
		color 255, 255, 255
		circle (ginfo_winx - SIZE + 15) / 2, (ginfo_winy - SIZE + 15) / 2, (ginfo_winx + SIZE - 15) / 2, (ginfo_winy + SIZE - 15) / 2, 0
		pos (ginfo_winx - SIZE / 3) / 2, (ginfo_winy - SIZE / 3 * 2) / 2
		mes 10 - cnt
		redraw 1
	loop
	font msmincho, 15
	return
続いて、[開始]ボタンや[次へ]ボタンを押すといきなり問題が始まってしまう点について修正しましょう。 ボタンを「キーボードのタブキー+スペースキーで押している人」もいるとは思いますが、 「マウスでボタンをクリックする人」の割合の方が多いと思います。 マウスでボタンを押した直後に問題が始まってしまうと、 数字入力するキーボードにまで操作移行する時間分を捨ててしまうようなものなので、 解答者にしてみれば「なんだかなぁ…」と思ってしまうのではないでしょうか? そこでボタンを押してから問題が表示されるまでの間の準備時間として、 解答時にあるカウントダウンを表示させるように変更したいと思いますが、 *game_answerラベルにあるカウントダウンと同じ処理を追加するのは冗長なので、 カウントダウン処理をユーザー定義命令としてモジュールにしてしまいましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define SIZE 250

#module
#deffunc countdown int count, local i
	click@ = 0
	i = ginfo_winx - SIZE@, ginfo_winy - SIZE@, ginfo_winx + SIZE@, ginfo_winy + SIZE@
	font msmincho, SIZE@ / 3 * 2
	repeat , 1
		if click@ ! 0 | cnt = count : break
		if count {
			redraw 0
			color 255, 255, 255 : boxf i.0 / 2, i.1 / 2
			if count - cnt > 3 : color : else : color 255
			circle i.0 / 2, i.1 / 2, i.2 / 2, i.3 / 2
			pos (ginfo_winx - SIZE@ / 3 * 2) / 2, (ginfo_winy - SIZE@ / 3 * 2) / 2
			mes count - cnt
			color 255, 255, 255
			circle (i.0 + 15) / 2, (i.1 + 15) / 2, (i.2 - 15) / 2, (i.3 - 15) / 2, 0
			pos (ginfo_winx - ginfo_mesx) / 2, (ginfo_winy - ginfo_mesy) / 2
			mes count - cnt
			redraw 1
		}
		wait 100
	loop
	return
#global

	num = 3, 4, 5
	koma = 50
	objsize 100, 25
	randomize
	mmload "error.wav", 0
	mmload "correct.wav", 1
グローバル(メインスクリプト)側の変数を直に参照するのはモジュールとしてどうかとは思いますが、 ボタンを押したことを通知する変数clickごと上記のように移植します。 ループの終了条件は6行目のrepeatで回数設定せずに、 7行目の「ボタンクリックされた」か「ループカウンタがループ回数になった時」という条件で行っています。 コレはカウントダウン処理を行う他、*game_judgeラベル内のボタンが押されるまで無限にループする処理も 同時に出来る設計にするためこのような作りにしています。 上記のカウントダウンモジュール内では変数clickの他、定数SIZEも使用しているので、 メインスクリプトの1行目「#define SIZE 250」の次の行にでも入れます。 そして、このcountdoun命令を使って*game_drawラベルを以下のように書き換えてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
*game_draw
	clrobj
	countdown 3 // 3秒間の準備時間
	font msmincho, SIZE
	foreach question
		color 255, 255, 255 : boxf : color , 150
		wait 10
		pos (ginfo_winx - SIZE / 2) / 2, (ginfo_winy - SIZE) / 2
		mes question.cnt
		wait koma
	loop
	return
そして既存のカウントダウン処理を行っている*game_answerラベルと*game_judgeラベルも書き換えてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*game_answer
	color 255, 255, 255 : boxf : color
	font msmincho, 15
	pos 10, 10 : mes "合計値を入力してください。"
	pos 10, 40 : input answer, 100, 20
	pos 10, 70 : button gosub "解答", *click_button
	objsel 0
	countdown 10 // 10秒間のカウントダウンを行う
	font msmincho, 15
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
*game_judge
	clrobj
	color 255, 255, 255 : boxf : color
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 60 : button gosub "次へ", *click_button
	font msmincho, SIZE, 1
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	if answer.0 ! answer.1 {
		color 255
		mes "×"
		mmplay 0
	} else {
		color , , 255
		mes "○"
		mmplay 1
	}
	countdown // ボタンが押されるまで無限ループ
	stage++
	return
続いて、現在何問目で数字が幾つ表示されるかわかりにくい点について改善しましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
*game_draw
	clrobj
	color 255, 255, 255 : boxf : color
	font msmincho, 15
	pos 10, 10 : mes "第 " + (stage + 1) + " / " + length(num) + " 問目"
	pos 10, 30 : mes "表示数 " + num.stage + "個 
	pos 10, 50 : mes strf("%1.2f 秒 / コマ", 0.01 * koma)
	countdown 3
	font msmincho, SIZE
	foreach question
		color 255, 255, 255 : boxf : color , 150
		wait 10
		pos (ginfo_winx - SIZE / 2) / 2, (ginfo_winy - SIZE) / 2
		mes question.cnt
		wait koma
	loop
	return
現在何問目であるかは変数stageを参照することでわかりますし、 全部で何問あるかは配列変数numの総要素数、表示される個数はnum.stageでわかります。 今回は固定で変わることがありませんが、数字の表示時間「0.5秒」も参考に載せると上記のようになります。 また、今回は3問だけしかないので途中経過を含めても 0%、約33%、50%、約66%、100%のうちのいずれかにしかなりませんが、 正解率はどれくらいなのかを結果表示画面で表示するように以下の2ラベルのように変更します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*game_main
	stage = 0
	truth = 0 // 正解率算出用
	foreach num
		gosub *game_init
		gosub *game_draw
		gosub *game_answer
		gosub *game_judge
	loop
	goto *init
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
*game_judge
	truth += answer.0 = answer.1	// 正解なら1を足す「if answer.0 = answer.1 : truth++」と一緒
	stage++				// 率を算出するために個の位置に移動しておく
	clrobj
	color 255, 255, 255 : boxf : color
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / stage)
	pos 10, 80 : button gosub "次へ", *click_button
	font msmincho, SIZE, 1
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	if answer.0 ! answer.1 {
		color 255
		mes "×"
		mmplay 0
	} else {
		color , , 255
		mes "○"
		mmplay 1
	}
	countdown
	return
*game_mainラベル内のゲーム開始前に初期化しておいた変数truthを使って、 *game_judgeラベル内で正解率を算出・表示しているわけですが、 *game_judgeラベル最後のreturn前にステージ数を増やしていた変数stageを3行目に移動したのは、 8行目で率を表示するを入れているため、0除算してしまわないようにするための処置です。 別にreturn前に変数stageの値を増やすやり方のままで、 8行目を「pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / (stage + 1))」 としても良いのですが、移動させても問題ないことから先にステージ数を増やしておきました。 むしろ、この段階で既に解答し終えているわけなので、0除算回避のためだけでなく、 他にも何らかの情報を表示するなら先にステージ数を増やしておくほうが望ましいですね。 解答した答えが間違いだった時にどこを間違えたのかわかるようにするため、 結果表示の段階で「先程表示した数字の一覧を表示する」ようにも変更してみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
24
25
26
27
28
29
*game_judge
	truth += answer.0 = answer.1
	stage++
	clrobj
	color 255, 255, 255 : boxf : color
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / stage)
	pos 10, 80 : button gosub "次へ", *click_button
	pos 80, 30 : mes "("
	pos length(question) * 30 + 75, 30 : mes ")"
	foreach question
		if cnt : pos cnt * 30 + 75, 30 : mes "+"
		pos cnt * 30 + 90, 30 : mes question.cnt
	loop
	font msmincho, SIZE, 1
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	if answer.0 ! answer.1 {
		color 255
		mes "×"
		mmplay 0
	} else {
		color , , 255
		mes "○"
		mmplay 1
	}
	countdown
	return
上記の10〜15行目のように追加修正することで「正解:21(7+7+7)」という感じで 出題した数字が正解の右側に出題順で一覧表示されるようになります。 出題数が変わっても表示位置は自動的に調整されますが、 このままでは19個以上表示するなら間隔を調整する等の工夫が必要になると言うことを覚えておきましょう。 続いて、時間切れでボタンが押せなかった場合は不正解とすべき点について。 「解答時間として制限を設ける」と言う設定であったのでコレで良かったのかもしれませんが、 時間内に[解答]ボタンが押せなかったとしても、 「入力ボックスに正解となる数字を入力してさえいれば正解となる」のは考えものですね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
24
25
26
27
28
29
30
*game_judge
	truth += (answer.0 = answer.1) * click
	stage++
	clrobj
	color 255, 255, 255 : boxf : color
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / stage)
	pos 10, 80 : button gosub "次へ", *click_button
	if click = 0 : pos 90, 10 : mes "( 時間切れ )" // 時間切れであることを示す
	pos 80, 30 : mes "("
	pos length(question) * 30 + 75, 30 : mes ")"
	foreach question
		if cnt : pos cnt * 30 + 75, 30 : mes "+"
		pos cnt * 30 + 90, 30 : mes question.cnt
	loop
	font msmincho, SIZE, 1
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	if answer.0 ! answer.1 | click = 0 {
		color 255
		mes "×"
		mmplay 0
	} else {
		color , , 255
		mes "○"
		mmplay 1
	}
	countdown
	return
時間切れの場合に入力値を0などに変更するというやり方だと、 自分が入力した数字も表示させる時等に問題が生じかねないので、 *game_judgeラベル内の2箇所、それも元のスクリプトに「 * click」を追加する方法にしています。 こうすることでボタンが押されていなければ入力値に0を掛けるので絶対に不正解となりますし、 入力値に対しては何の操作も行っていないので修正が必要な箇所もありません。 上記の変更で「解答入力とボタン押下の2処理を時間内に行わなければならない」様になりましたが、 キーボード操作からマウス操作に持ち替える時間を考慮してないので、コレについても改良が必要ですね。 まず、ラベルのないスクリプト始めの部分を以下のように書き換えます。
 1
 2
 3
 4
 5
 6
 7
	num = 3, 4, 5
	koma = 50
	objsize 100, 25
	onkey gosub *push_key
	randomize
	mmload "error.wav", 0
	mmload "correct.wav", 1
当然、上記の変更だけでは移動先ラベルがありませんのでエラーになります。 キーが押された時に移動するラベルを次のように作成してください。
 1
 2
 3
*push_key
	if wparam = 13 : gosub *click_button // エンターキーが押されたらボタン押下と同じ効果とする
	return
この変更で、ゲーム途中のボタンを押す箇所はエンターキーを押すことでも先に進めるようになりますが、 ゲーム開始直後の画面にあるボタンだけはstopで停止しているのでエンターキーで進むことが出来ません。
 1
 2
 3
 4
*init
	clear
	pos 10, 10 : button gosub "開始", *click_button
	countdown // ボタン押下またはエンターキーで先に進むよう変更
上記のように*initラベルを変更することでマウスレス(マウス不要)となり、 全ての操作をキーボードだけで素早く行えるようになりますね。 コレで万事メデタシ…かと思いきや、全てのキーを押した時に*push_keyラベルにジャンプしてしまうのです。 実はgosub命令は完全に元の処理位置に戻るのではなく、元の処理位置の次から再開する仕様なので、 ウェイト中に戻ると、ウェイトの待ち時間が1秒だろうが1分だろうが、無視されてしまいます。 今回の場合、数字を入力しようとキーを押した瞬間、countdown命令内の「wait 100」が消滅してしまうのです。 数字キーを押しっぱなしにしようものなら、約10秒の制限時間が1〜2程度で終わってしまいますので、 countdown命令を次のように変更して、急速に制限時間が消滅する問題を回避する必要があります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#deffunc countdown int count, local i
	click@ = 0
	i = ginfo_winx - SIZE@, ginfo_winy - SIZE@, ginfo_winx + SIZE@, ginfo_winy + SIZE@
	font msmincho, SIZE@ / 3 * 2
	repeat , 30
		if click@ ! 0 | cnt / 30 = count : break
		if count ! 0 & cnt \ 30 = 0 { // 30ループ(約1秒)に1回だけ画面書換
			redraw 0
			color 255, 255, 255 : boxf i.0 / 2, i.1 / 2
			if count - cnt / 30 > 3 : color : else : color 255
			circle i.0 / 2, i.1 / 2, i.2 / 2, i.3 / 2
			pos (ginfo_winx - SIZE@ / 3 * 2) / 2, (ginfo_winy - SIZE@ / 3 * 2) / 2
			mes count - cnt / 30
			color 255, 255, 255
			circle (i.0 + 15) / 2, (i.1 + 15) / 2, (i.2 - 15) / 2, (i.3 - 15) / 2, 0
			pos (ginfo_winx - ginfo_mesx) / 2, (ginfo_winy - ginfo_mesy) / 2
			mes count - cnt / 30
			redraw 1
		}
		await 33
	loop
	return
1ループ=1秒ではなく、30回ループさせて1秒(1つのウェイトが33ミリ秒)とすることにより、 キーが押されることで消滅するウェイトも最大で33ミリ秒、変更前の約3分の1で済みます。 キーを押し続けたとしても、変更前よりは随分とマシになったことが実感できますね。 本来、割込命令のジャンプ先でウェイトを掛けるのは好ましくありませんが、 「エンターキー以外のキー押し続け」によるウェイト消滅を緩和させるるなら、 下記のようにエンターキー以外が押された場合に余分にウェイトを設けるやり方もありですね。
 1
 2
 3
*push_key
	if wparam = 13 : gosub *click_button : else : wait 1 // エンターキー以外は余分にウェイトを掛ける
	return
ウェイトよりもキー割り込みの方が早い場合、時間が一向に進まなくなってしまいますので、 30ミリ秒(wait 3)程度に抑えるようにしてください。 尚、1つのウェイト時間を短くして繰り返し回数を増やすほど消滅するウェイトが減ってマシになりますが、 ウェイト時間を短くすればするほど処理数が増えて処理時間数が増大してしまうので、 1秒以上で1カウントされるという本末転倒な結果になるのでコレ位が妥当ではないでしょうか。 それでは、表示や動作についての変更は以上で終わり、最後にリファクタリングを2点だけ行いましょう。 リファクタリングとは「外部的な振る舞いに変更なく、内部的な改善を行う」というもの。 既に冗長なカウントダウン処理をモジュール化していますが、 この他にも似たような処理を繰り返している無駄と思える部分がありますね。 1つ目は、当節で何度も出てきた*game_judgeラベルの「○×表示」と「音を鳴らす」部分。 1箇所しかないので冗長と言うまではないかもしれませんが、正誤でほとんど同じ処理しかしていません。 今は「○×色設定」「○×表示」「音を鳴らす」の3処理しか行ってませんが、 今後何らかの処理が増えるかもしれないということを考えた時、 正誤それぞれパラメータが違うだけの同じ処理を2つずつ書いていかなければなりませんので、 条件分岐を行わず1つの式で簡潔にまとめてみます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*game_judge
	truth += (answer.0 = answer.1) * click
	stage++
	clrobj
	color 255, 255, 255 : boxf : color
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / stage)
	pos 10, 80 : button gosub "次へ", *click_button
	if click = 0 : pos 90, 10 : mes "( 時間切れ )"
	pos 90, 30 : mes "("
	pos length(question) * 30 + 85, 30 : mes ")"
	foreach question
		if cnt : pos cnt * 30 + 85, 30 : mes "+"
		pos cnt * 30 + 100, 30 : mes question.cnt
	loop
	font msmincho, SIZE, 1
	tmp = "×○"
	color (answer.0 ! answer.1 | click = 0) * 255, , (answer.0 = answer.1) * click * 255
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	mes strmid(tmp, (answer.0 = answer.1) * click * 2, 2)
	mmplay (answer.0 = answer.1) * click
	countdown
	return
もう1つの無駄と思える処理は、画面クリア時の処理。 「オブジェクトの全クリア」「背景色での塗り潰し」「前景色に戻す」「フォントサイズを戻す」 上記の4処理を様々な箇所で見かけますのでこれらを1つのモジュール命令に置き換えましょう。
 1
 2
 3
 4
 5
#deffunc clear
	clrobj
	color 255, 255, 255 : boxf : color
	font msmincho, 15
	return
上記のclear命令をモジュール内に追加して、以下の様に修正してください。
 1
 2
 3
 4
*init
	clear
	pos 10, 10 : button "開始", *game_main
	stop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
*game_draw
	clear
	pos 10, 10 : mes "第 " + (stage + 1) + " / " + length(num) + " 問目"
	pos 10, 30 : mes "表示数 " + num.stage + "個 
	pos 10, 50 : mes strf("%1.2f 秒 / コマ", 0.01 * koma)
	countdown 3 // 3秒間の準備時間
	font msmincho, SIZE
	foreach question
		color 255, 255, 255 : boxf : color , 150
		wait 10
		pos (ginfo_winx - SIZE / 2) / 2, (ginfo_winy - SIZE) / 2
		mes question.cnt
		wait koma
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
*game_answer
	clear
	pos 10, 10 : mes "合計値を入力してください。"
	pos 10, 40 : input answer, 100, 20
	pos 10, 70 : button gosub "解答", *click_button
	objsel 0
	countdown 10
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
*game_judge
	truth += (answer.0 = answer.1) * click
	stage++
	clear
	pos 10, 10 : mes "入力:" + answer.0
	pos 10, 30 : mes "正解:" + answer.1
	pos 10, 50 : mes "正答率 " + strf("%2.2f %", 100.0 * truth / stage)
	pos 10, 80 : button gosub "次へ", *click_button
	if click = 0 : pos 90, 10 : mes "( 時間切れ )"
	pos 90, 30 : mes "("
	pos length(question) * 30 + 85, 30 : mes ")"
	foreach question
		if cnt : pos cnt * 30 + 85, 30 : mes "+"
		pos cnt * 30 + 100, 30 : mes question.cnt
	loop
	font msmincho, SIZE, 1
	tmp = "×○"
	color (answer.0 ! answer.1 | click = 0) * 255, , (answer.0 = answer.1) * click * 255
	pos (ginfo_winx - SIZE) / 2, (ginfo_winy - SIZE) / 2
	mes strmid(tmp, (answer.0 = answer.1) * click * 2, 2)
	mmplay (answer.0 = answer.1) * click
	countdown
	return
今回の改善で、ほぼゲームとして完成したも同然ですね。 今回で使われたスクリプトはコチラからどうぞ。