フラッシュ暗算(完成)


今回行うのはレベル制の導入とスコアの導入、ランキング対応の処理です。

早速、レベル制について変更していきましょう。
レベルは2節で導入したステージよりも更にもう1つ大きな括りのことを指しており、
初級レベル3ステージ、中級レベル3ステージ、上級レベル3ステージに分類します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
	dim num, 3, 3     // 1次元目はステージ、2次元目はレベル
	num.0.0 = 3, 4, 5 // 初級レベル
	num.0.1 = 5, 6, 7 // 中級レベル
	num.0.2 = 7, 8, 9 // 上級レベル
	koma = 50, 35, 20 // レベル毎に表示時間を変更
	objsize 100, 25
	onkey gosub *push_key
	randomize
	mmload "error.wav", 0
	mmload "correct.wav", 1
レベル毎に異なっている点は、出題する数字の個数と表示速度。 初級は1問につき3つ・4つ・5つの数字を0.5秒刻みで、 中級は1問につき5つ・6つ・7つの数字を0.35秒刻みで、 上級は1問につき7つ・8つ・9つの数字の数字を0.2秒刻みで出題します。 配列num、配列komaを、レベルを管理する変数lvで次のように切り替えます。
 1
 2
 3
 4
 5
 6
 7
 8
*game_init
	answer = 0, 0
	dim question, num.stage.lv
	foreach question
		question.cnt = rnd(9) + 1
		answer.1 += question.cnt
	loop
	return
 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.lv + "個
	pos 10, 50 : mes strf("%1.2f 秒 / コマ", 0.01 * koma.lv)
	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.lv
	loop
	return
上記の変更だけで新たにレベルが導入されたわけですが、 当然ながらコレだけでは枠組みを作成したに過ぎず、レベルを切り替えるための仕組みがありません。 クリアする度に上位のレベルが追加されていくというのも面白いと思いますが、 ココでは初めから初級・中級・上級の全てをゲーム開始前の画面で選択できるようにしましょう。
 1
 2
 3
 4
 5
 6
*init
	clear
	pos  10, 10 : combox lv, , "初級\n中級\n上級"
	pos  10, 40 : button gosub "開始", *click_button
	objsel 0
	countdown
レベル機能の追加は以上で、続いてはスコア機能の追加を行います。 スコアは正解した時に増加するポイントのことで、変数scoreで管理します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
*game_main
	stage = 0
	truth = 0
	score = 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
23
24
25
*game_judge
	truth += (answer.0 = answer.1) * click
	score += answer * (answer.0 = answer.1) // 「if answer.0 = answer.1 : score += answer」と同じ
	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, 70 : mes "スコア " + score
	pos 10, 90 : 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秒で答えても、10秒ギリギリまで悩んで導き出しても同じ配点と言うのは腑に落ちないので、 解答時間が早いほど高得点になるように変更したいと思います。 解答中は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 : i = count * 30 - cnt : break // 変数iにカウント残をセット
		if count ! 0 & cnt \ 30 = 0 {
			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 i // カウント残を返す
 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
*game_judge
	truth += (answer.0 = answer.1) * click
	score += stat * answer * (answer.0 = answer.1) // 「if answer.0 = answer.1 : score += stat * answer」と同じ
	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, 70 : mes "スコア " + score
	pos 10, 90 : 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
countdownreturn時に数値をセットすることによってstatに入れることが出来ます。 countdownは元々関数ではなく命令として使用しており、statを参照するやり方ではなかった為、 今回の変更で*game_judgeラベル内以外のcountdownを使用している箇所には何の影響もありませんが、 気を付けなければならないことが一点あります。 *game_judgeラベル内3行目のstat*game_answerラベル内のcountdownで得られた値なので問題ありませんが、 *game_judgeラベル内5行目のclear以降のstatclear内部のfont命令実行結果のものです。 もし、clear以降でscorestatの値をセットしようとしても正常にはセットできませんので、 タイミングや順番には気を付ける様にしてください。 それでは最後の追加機能としてスコアのランキング対応処理を追加しましょう。 既存ランキング読込と描画、今回のスコアとの比較と更新、ランキング保存処理が一度に増えるので、 レベルやスコアの追加のように2、3行の追加や変更で済むものではありません。 ランキングデータの読み込みから順に考えて行きましょう。 ランキングはスコアの登録はもちろん、取得者の情報も保存しておきたいですよね。 ココでは名前とスコアの間をタブで区切ったテキストを1データとしたフォーマットで行います。 名前とスコアの分割はさておき、1行1データとするので、 1データ分の取出と保存はメモリノートパッド命令で簡単に行えます。 メモリノートパッド命令はこのランキング機能でしか使用しないので、メモリノートパッド命令使用前のお約束、 notesel命令の実行はスクリプト内で一度しか通過しない箇所に書いておけばOKです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
	dim num, 3, 3 
	num.0.0 = 3, 4, 5
	num.0.1 = 5, 6, 7
	num.0.2 = 7, 8, 9
	koma = 50, 35, 20
	name = "player" // ランキングに保存するプレイヤー初期名
	notesel ranking // 全ランキングデータ保持変数をメモリノートパッドの対象に設定
	objsize 100, 25
	onkey gosub *push_key
	randomize
	mmload "error.wav", 0
	mmload "correct.wav", 1
	gosub *rank_load // ランキングデータ読込
ランキングデータの読み込みは次のようにします。
 1
 2
 3
 4
 5
 6
 7
 8
 9
*rank_load
	exist "flash.rnk"
	if strsize = -1 {
		ranking = "ultimate\t30000\nsatoshi\t22000\nhspbc\t17000\nmiddle\t13000\nlecture\t9500\nflash\t6800"
		notesave "flash.rnk"
	} else {
		noteload "flash.rnk"
	}
	return
まずランキングデータファイルが存在するかをチェックし、 存在しなければ内部で予め作成したデフォルトのランキングを使用、 存在するならランキングデータファイルを変数に読み込むようにします。 2行目、5行目、7行目のflash.rnkというのがランキング格納先のファイルで、 拡張子rnkは独自のものですが、内容は単なるテキストデータですのでメモ帳等で編集可能です。 実際にランキング等のセーブデータを扱う場合は利用者に直接ファイル編集されてしまうことを考慮して 暗号化して保存、変数に読み込む際に復号化する処理を入れておくことをオススメします。 尚、上記のように同一のテキストをスクリプトに直接記述しておくのは好ましくありませんので、 「#define SIZE 250」と同様に定数をスクリプト始めに定義してソレを使うように変更しましょう。
 1
 2
#define FILE "flash.rnk"
#define SIZE 250
 1
 2
 3
 4
 5
 6
 7
 8
 9
*rank_load
	exist FILE
	if strsize = -1 {
		ranking = "ultimate\t30000\nsatoshi\t22000\nhspbc\t17000\nmiddle\t13000\nlecture\t9500\nflash\t6800"
		notesave FILE
	} else {
		noteload FILE
	}
	return
現段階では、存在しないランキングファイルを新たに作成するだけですが、 この作成したランキングデータの描画をレベル選択するゲーム開始前の画面で行ってみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
*rank_draw
	font msmincho, 30
	color 255
	pos  50, 100 : mes "ランク"
	pos 180, 100 : mes "名前"
	pos 450, 100 : mes "スコア"
	font msmincho, 25
	repeat notemax
		noteget tmp, cnt
		color , 255
		line 50, cnt * 50 + 140, 540, cnt * 50 + 140
		color , , 255
		pos  50, cnt * 50 + 150 : mes strf("%4d", cnt + 1)
		pos 180, cnt * 50 + 150 : mes strmid(tmp, 0, instr(tmp, , "\t"))
		pos 450, cnt * 50 + 150 : mes strf("%6d", int(strmid(tmp, instr(tmp, , "\t") + 1, strlen(tmp) - instr(tmp, , "\t") - 1)))
	loop
	return
上記の*rank_drawラベルを新規に追加し、*initラベルを次のように変更してください。
 1
 2
 3
 4
 5
 6
 7
*init
	clear
	pos  10, 10 : combox lv, , "初級\n中級\n上級"
	pos  10, 40 : button gosub "開始", *click_button
	gosub *rank_draw // ランキングデータ描画
	objsel 0
	countdown
ランキングデータの読込と一覧描画の処理は以上で完了です。 次に、今回取得したスコアとランキングデータを比較して、 今回のスコアがランクインしているならランキングを上書き更新する処理を追加します。 比較は次のような処理を追加しましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*rank_check
	i = notemax // ランク外の順位を付けておく
	repeat notemax
		noteget tmp, cnt
		if score >= int(strmid(tmp, instr(tmp, , "\t") + 1, strlen(tmp) - instr(tmp, , "\t") - 1)) { // ランクイン
			i = cnt
			break
		}
	loop
	return
一時変数iを今回の順位用変数とみなし、現在のランクインデータのスコア部分を抽出、比較を行います。 比較対象ランクのスコアより今回分スコアの方が高いなら、新ランクを一時変数に保存しておき、 別途新たに追加する下記の*rank_entryラベル内で今回スコアの再表示とランキング情報表示の他、 ランクインしている場合のみ登録ボックスを、ランク外ならメッセージのみをその下側に表示します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
*rank_entry
	clear
	pos 10, 10 : mes "総スコア " + score
	pos 10, 30 : mes "順位   " + strf("%d位", i + 1)
	pos 10, 50
	if i < notemax {
		mes "名前"
		pos 75, 50 : input name, 100, 20, 8
	} else {
		mes "ランク入りしませんでした…。"
	}
	pos 10, 80 : button gosub "終了", *click_button
	countdown
	return
ランクインしている場合のみ、新たに追加するランキング保存処理*rank_saveにて、 ランキング変数rankingに今回分のスコアと入力名を追加挿入し、 前回まで6位に位置していたランキングデータを削除しておきます。 更新後のランキングも更新前と代わらず6件としているのは、 単純に「表示可能ランク数が6件程度だから」であり、それ以外に深い意味はありません。 もし、表示文字サイズを小さくしたり、詰めて表示すれば更に多くのランキングを扱えます。
 1
 2
 3
 4
 5
 6
 7
*rank_save
	if i < notemax {
		noteadd name + "\t" + score, i // 今回の順位位置に追加
		notedel notemax - 1            // 最下位は削除してランク数は変更なし
		notesave FILE
	}
	return
「既存のランキングデータに追加挿入して、最下位データを削除する」だけしか行っていないので、 ランキングファイルflash.rnkを直接編集し、ランキングで件数を増やした場合、 表示件数及び取り扱い件数も増えますし、逆に減らして保存した場合はその件数分しか取り扱えません。 ランキングデータに7件以上入っていても上位6件しか表示しない、 5件以下しかない場合の登録は最下位ランキングは削除しない、等のトラップはご自分で設定してください。 コレら*rank_check*rank_entry*rank_saveラベルをゲーム終了後の処理位置に結びつければ ランキングの比較から更新登録、保存処理まで完成しますね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
*game_main
	stage = 0
	truth = 0
	score = 0
	foreach num
		gosub *game_init
		gosub *game_draw
		gosub *game_answer
		gosub *game_judge
	loop
	gosub *rank_check // ランキングチェック
	gosub *rank_entry // ランキング更新
	gosub *rank_save  // ランキング保存
	goto *init
以上でフラッシュ暗算ゲームはとりあえず完成しますので終了としますが、コレが全てではありません。 数字の桁数を増やしたり、左右に2つ同時に表示するのも良いでしょう。 いろいろと手を加える等改良を繰り返し、より一層面白さを引き立てるゲームにしてみください。 完成したスクリプトはコチラからどうぞ。