スライドパズル(進化)


前回はスライドパズルの基本部分を作成しました。
今回は前回のものに手を加えていき、よりゲームらしくしてみましょう。

まずは、前回3×3マスの1面だけだったものを、
3×3マス、4×3マス、4×4マスの合計3面を1つのゲームとしたステージ制の導入方法から説明します。


 1
 2
 3
 4
 5
 6
 7
 8
#define SX (ginfo_winx / numx.stage)	// ステージ毎の1マスの横幅
#define SY (ginfo_winy / numy.stage)	// ステージ毎の1マスの高さ

	numx = 3, 4, 4 // 第1ステージ、第2ステージ、第3ステージの横マス個数
	numy = 3, 3, 4 // 第1ステージ、第2ステージ、第3ステージの縦マス個数
	randomize
	gosub *game_start
	end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
*game_start
	stage = 0 // 現在のステージを初期化
	foreach numx
		space = 0
		clear = 0
		gosub *draw_tips
		gosub *shuffle
		gosub *draw_map
		gosub *game_main	// ステージ毎のループ
		gosub *game_clear	// ステージ毎のクリア処理
	loop
	dialog "Stages All Clear !\n\nThank you for Playing."
	return
マス数を格納した変数numxと変数numyを配列変数に変更して、配列要素番号用に変数stageを用意します。 また、ゲーム毎のループ処理である*game_mainラベル、クリア処理である*game_clearラベルを いずれも*game_startラベル内に移動してステージ数分繰り返すようにしましょう。 *game_startラベル内の9・10行目に移した*game_main*game_clearへのジャンプ処理は元のまま変更せず、 *game_startラベルへのジャンプ処理をステージ数分だけ繰り返す方法でも問題ありませんが、 この後に別処理を加えることを考えた時、まとめておいた方がわかりやすいので、 このタイミングで*game_startラベル内に移動しておきました。 さて、ステージ数を増やしただけでは、実際のステージに変更がありませんので、 以下のように変数numxnumyを使っている箇所全て、変数stageを要素番号とした配列に置き換えてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
*draw_tips
	buffer 1, ginfo_winx, ginfo_winy
	color 150, 255, 100 : boxf : color , , 255
	font msmincho, SY, 1
	repeat numx.stage * numy.stage
		pos cnt \ numx.stage * SX + (SX - SY) / 2, cnt / numx.stage * SY : mes strf("%02d", cnt + 1)
	loop
	buffer 2, ginfo_winx, ginfo_winy
	gcopy 1, , , ginfo_winx, ginfo_winy
	color 255
	repeat numx.stage + 1
		boxf cnt * SX - 2, -1, cnt * SX + 1, ginfo_winy
	loop
	repeat numy.stage + 1
		boxf -1, cnt * SY - 2, ginfo_winx, cnt * SY + 1
	loop
	gsel 0
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
*shuffle
	dim tip, numx.stage * numy.stage
	repeat numx.stage * numy.stage : tip.cnt = cnt : loop
	repeat length(tip) * length(tip)
		i = space, rnd(4)
		if i.1 = 0 : if space \ numx.stage = 0 : continue : else : space--
		if i.1 = 1 : if space / numx.stage = 0 : continue : else : space -= numx.stage
		if i.1 = 2 : if space \ numx.stage = numx.stage - 1 : continue : else : space++
		if i.1 = 3 : if space / numx.stage = numy.stage - 1 : continue : else : space += numx.stage
		tip.space = tip.space ^ tip.i
		tip.i     = tip.space ^ tip.i
		tip.space = tip.space ^ tip.i
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*draw_map
	redraw 0
	color 255, 255, 255 : boxf
	repeat numx.stage * numy.stage
		if cnt = space : continue
		pos cnt \ numx.stage * SX, cnt / numx.stage * SY
		gcopy 2, tip.cnt \ numx.stage * SX, tip.cnt / numx.stage * SY, SX, SY
	loop
	redraw 1
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
*game_main
	repeat
		wait 1
		stick key
		if key & 256 {
			i.0 = mousex / SX + mousey / SY * numx.stage
			i.1 = (i - 1 = space) & (i \ numx.stage ! 0)
			i.2 = (i - numx.stage = space) & (i / numx.stage ! 0)
			i.3 = (i + 1 = space) & (i \ numx ! numx.stage - 1)
			i.4 = (i + numx.stage = space) & (i / numx.stage ! numy.stage - 1)
			if i.1 | i.2 | i.3 | i.4 {
				tip.space = tip.space ^ tip.i
				tip.i     = tip.space ^ tip.i
				tip.space = tip.space ^ tip.i
				space = i
				gosub *draw_map
				gosub *clear_check
				if clear : break
			}
		}
	loop
	return
 1
 2
 3
 4
 5
 6
*clear_check
	clear = 1
	repeat numx.stage * numy.stage
		if tip.cnt ! cnt : clear = 0 : break
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
*game_clear
	repeat 8, 1
		wait 10
		gmode 3, SX, SY, cnt * 32
		pos space \ numx.stage * SX, space / numx.stage * SY
		gcopy 2, tip.space \ numx.stage * SX, tip.space / numx.stage * SY
	loop
	pos 0, 0 : gcopy 1, , , ginfo_winx, ginfo_winy
	dialog "Game Clear !"
	stage++ // ステージを先に進める
	return
結局、全てのラベル内に変数numxnumyが使われていたため、全てのラベル内が変更対象となりました。 変更内容はいずれも変数numxnumyを配列変数numx.stagenumy.stageに置き換え、 *game_startラベル内の10行目にstage++を新たに追加しただけです。 変更箇所は多数ありますが、同様の変更ばかりで大したことはありませんね。 ステージ制の導入処理は以上で完了しましたので、続いて、タイトル画面を作成してみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define NAME "Slide Pazzle" // タイトル名
#define SX (ginfo_winx / numx.stage)
#define SY (ginfo_winy / numy.stage)

	numx = 3, 4, 4
	numy = 3, 3, 4
	item = "Game Start", "Exit" // メニュー項目
	randomize
	gosub *menu
	end
いきなりゲームが開始するのを防ぐ為、 最初にジャンプするラベルを*game_startラベルから新しく作った*menuラベルに変更しました。 その他の変更点はタイトル画面で使用する定数と変数で、次に紹介するメニュー処理内で説明します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*menu
	index = 0 // 選択項目番号
	gosub *draw_menu
	repeat
		wait 5
		stick key
		if key & 2 : index-- : if index < 0 : index = length(item) - 1	// 上へ
		if key & 8 : index++ : if index >= length(item) : index = 0	// 下へ
		if key & 10 : gosub *draw_menu					// 上or下キーなら再描画
		// 決定ボタンはエンターキー
		if key & 32 {
			switch index
			// Game Start
			case 0:
				gosub *game_start
				gosub *draw_menu
				swbreak
			// Exit
			default:
				break
			swend
		}
	loop
	return

タイトル画面はカーソルキーで選択します。 とりあえず、「ゲーム開始」と「終了」の2つしか用意してないので、 現在の選択項目を示す変数indexは「index = 1 - index」のように0と1を切り替えるだけでも構いませんが、 将来項目を増やすことも考え、*menuラベルの7・8行目にあるように配列変数itemの要素数を項目数として、 一番上から一番下に、一番下から一番上に選択項目を移動できる作りにしました。 タイトル画面を描画する処理がまだありませんのでこのままでは動きません。 以下の*draw_menuラベルを追加してください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
*draw_menu
	// タイトル描画
	title NAME
	redraw 0
	color 255, 200, 255 : boxf : color , 200
	font msmincho, 64, 1
	pos , -100 : mes NAME // 横幅取得用の配置
	pos (ginfo_winx - ginfo_mesx) / 2, 30 : mes NAME
	// 選択項目描画
	font msmincho, 40, 1
	foreach item
		color (index = cnt) * 200, 100, (index ! cnt) * 200
		pos , -100 : mes item.cnt // 横幅取得用の配置
		pos (ginfo_winx - ginfo_mesx) / 2, (ginfo_winy - 230) / length(item) * cnt + 200 : mes item.cnt
	loop
	redraw 1
	return
タイトル(7・8行目)にしろ、選択項目(13・14行目)にしろ、同じ内容を2回も表示していますが、 こうしている理由はginfo_mesxに格納されるメッセージの横幅を取得するため、です。 ウィンドウの中央にメッセージを表示する為にメッセージ幅の取得が必要であり、 「(ウィンドウ幅−表示するもの)÷2」で横位置の表示基点座標を算出しています。 なお、1回目の表示は横幅を取得するためだけに配置していて、 表示されている必要はないので、上記のようにウィンドウ外または背景色でメッセージ表示させましょう。 12行目のcolor命令でわかりにくい式が入っていますが、 コレは現在の選択項目を赤色に、それ以外の項目を青色にするためのものです。 if文を使わない形にまとめる為に必要なことで、わかりやすさを優先して書くなら次のように置き換えられます。 「if index = cnt : color 200, 100, 0 : else : color 0, 100, 200」 14行目の選択項目表示縦位置は、本来「(ウィンドウ高さ÷選択項目数)」で上から順に均等に配置できるのに、 230引いたり200足したりしてるのは、タイトル部を開けて、下の空いた部分で均等配置するためのもの。 タイトル部にウィンドウ上端から200サイズ分確保しておいて、 その下から配置しているわけですが、均等サイズが230と30だけ多くなっているのは、 ウィンドウ下端にも30サイズ分の余白を空けておく為の差異です。 将来的に選択項目数が8個位までなら増えたとしても重なることなく配置することができます。 以上でタイトル画面およびメニューの追加方法についての説明を終わりまして、 今回最後の追加機能として手数制限について説明します。 今までは、絵柄を全て揃えてゲームクリアすることはできてもクリア失敗することはありませんでした。 移動可能手数を制限し、その回数内に揃えられなければクリア失敗とすることで、 より緊張感を持ってプレイしてもらえることでしょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
*game_start
	stage = 0
	foreach numx
		space = 0
		clear = 0
		moved = 0, 0			// 0:制限手数, 1:手数
		gosub *draw_tips
		gosub *shuffle
		gosub *draw_map
		gosub *game_main
		gosub *game_clear
		if clear = 2 : continue cnt	// ゲームオーバー時はもう1回
	loop
	dialog "Stages All Clear !\n\nThank you for Playing."
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
*shuffle
	dim tip, numx.stage * numy.stage
	repeat numx.stage * numy.stage : tip.cnt = cnt : loop
	repeat length(tip) * length(tip)
		i = space, rnd(4)
		if i.1 = 0 : if space \ numx.stage = 0 : continue : else : space--
		if i.1 = 1 : if space / numx.stage = 0 : continue : else : space -= numx.stage
		if i.1 = 2 : if space \ numx.stage = numx.stage - 1 : continue : else : space++
		if i.1 = 3 : if space / numx.stage = numy.stage - 1 : continue : else : space += numx.stage
		tip.space = tip.space ^ tip.i
		tip.i     = tip.space ^ tip.i
		tip.space = tip.space ^ tip.i
		moved++
	loop
	moved = moved / 10 * 20 // 制限手数を適度に緩和
	return
 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_main
	title strf("%s (残り:%d)", NAME, moved - moved.1)
	repeat
		wait 1
		stick key
		if key & 256 {
			i.0 = mousex / SX + mousey / SY * numx.stage
			i.1 = (i - 1 = space) & (i \ numx.stage ! 0)
			i.2 = (i - numx.stage = space) & (i / numx.stage ! 0)
			i.3 = (i + 1 = space) & (i \ numx ! numx.stage - 1)
			i.4 = (i + numx.stage = space) & (i / numx.stage ! numy.stage - 1)
			if i.1 | i.2 | i.3 | i.4 {
				tip.space = tip.space ^ tip.i
				tip.i     = tip.space ^ tip.i
				tip.space = tip.space ^ tip.i
				space = i
				moved.1++
				title strf("%s (残り:%d, 手数:%d)", NAME, moved - moved.1, moved.1)
				gosub *draw_map
				gosub *clear_check
				if clear : break
			}
		}
	loop
	return
 1
 2
 3
 4
 5
 6
 7
*clear_check
	clear = 1
	repeat numx.stage * numy.stage
		if tip.cnt ! cnt : clear = 0 : break
	loop
	if moved.1 >= moved : clear = 2 // 制限手数に達したらクリア失敗
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
*game_clear
	if clear = 1 {
		repeat 8, 1
			wait 10
			gmode 3, SX, SY, cnt * 32
			pos space \ numx.stage * SX, space / numx.stage * SY
			gcopy 2, tip.space \ numx.stage * SX, tip.space / numx.stage * SY
		loop
		pos 0, 0 : gcopy 1, , , ginfo_winx, ginfo_winy
		dialog "Game Clear !"
		stage++
	} else {
		dialog "Game Failed..."
	}
	return
新しく手数用に変数movedを作りました。 movedの要素0を制限手数、要素1をプレイヤーが動かした手数として使用しています。 また、ゲームクリア用変数clearが1の時は今まで通り「クリア」、2の時は「クリア失敗」として、 クリアできなかった場合は再度同じステージをプレイすべく*game_startラベル内のループを続行します。 制限手数は*shuffleラベル内で算出しており、まず実際に移動した回数を制限手数としてカウント、 最後に制限を若干緩めるために「10分の1倍してから20倍」しています。 最初から2倍と計算していないのは「手数の端数(1の位)をキレイになくすため」です。 難易度を下げたい場合は制限手数を緩め、 難易度を上げたい場合は制限手数を実際にシャッフルした回数に近くすると良いでしょう。 今回の変更は以上で終了です。 タイトルメニューはカーソルキーなのにパズルのスライドはマウス操作で、とバラバラであるのを統一したり、 元に戻すべき絵柄がどこなのかわかりやすくする等の改善については次回に行う予定です。 この章でまとめてみたスクリプトはコチラからダウンロードしてください。