スライドパズル(基本構成)


まず、初期化の部分を考えます。

 1
 2
 3
 4
 4
 5
#define SX (ginfo_winx / numx)	// 1マスの横幅
#define SY (ginfo_winy / numy)	// 1マスの高さ

	numx = 3		// 横マスの個数
	numy = 3		// 縦マスの個数
	randomize
とりあえず、マップは「3×3」の合計9マスで考えます。 1行目・2行目には4行目・5行目に定義されたnumxnumyを使った1マスのサイズを求める定数式があります。 マス数が変わればマスサイズも変わるはずなのにマスサイズは定数、マス数は変数と分けてあるのは、 1マスのサイズが「ウィンドウサイズに対してマスの数分で割ったもの」という固定のものでよい為だからです。 定数SXSYを変数で使用しても問題はありませんが、 不変であるものをわざわざ変数で用意する必要もないので定数にしただけの話です。 それで、「なぜSXSYの式内容を括弧で括っているか?」と言うことですが、 #defineは「単なるスクリプト内のワード置き換えに過ぎず、括弧を外すことで問題が生じてしまうから」です。 コレについては後で問題となる箇所が出てくる時に説明しますので、 今は「定数式の場合は括弧有無により結果が変わってしまうことがある」とだけ思っていただき、 今回の場合は括弧が必要なのだとだけ覚えておいてください。 乱数のセットや、そもそも乱数を保持する配列変数の初期化を行っていません。 コレは問題毎の初期化では必要になる処理ですが、 ゲーム全体の初期化には必要ないことですので「ココでは行っていないだけ」です。
 1
 2
 3
*game_start
	space = 0		// 現在の空きマス位置
	clear = 0		// ゲームクリアフラグ
ゲームの初期化を行いましょう。 2行目のspaceがこのゲームのキーとなる空きマス位置を保持する変数です。 その他の初期化はルーチン毎に3つに分けて紹介していきます。。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
*draw_tips
	// 完成図
	buffer 1, ginfo_winx, ginfo_winy
	color 150, 255, 100 : boxf : color , , 255
	font msmincho, SY, 1
	repeat numx * numy
		pos cnt \ numx * SX + (SX - SY) / 2, cnt / numx * SY : mes strf("%02d", cnt + 1)
	loop
	// 補助線
	buffer 2, ginfo_winx, ginfo_winy
	gcopy 1, , , ginfo_winx, ginfo_winy
	color 255
	repeat numx + 1
		boxf cnt * SX - 2, -1, cnt * SX + 1, ginfo_winy // マス左右の枠線
	loop
	repeat numy + 1
		boxf -1, cnt * SY - 2, ginfo_winx, cnt * SY + 1 // マス上下の枠線
	loop
	gsel 0
	return
1つ目の初期化、マップチップの作成について説明します。 画像を用いて良いのですが見た目については今後においておくとして、 今は冒頭の完成図にあるような「単色塗り潰しと文字だけを用いた簡素なもの」にしておきます。 「3〜8行目の図」と「10〜18行目の枠線付きの図」との2つ用意してあるのは、 並び替え終了後の完成図を表示する場合に、元の画像には入ってないマス毎の区切り線は邪魔ですし、 元の画像である完成図はできるだけ元の状態のまま置いておきたいために分けてみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
*shuffle
	dim tip, numx * numy
	repeat numx * numy : tip.cnt = cnt : loop
	repeat length(tip) * length(tip)
		i = space, rnd(4) // [ReplaceTipNo], [0:left, 1:top, 2:right, 3:down]
		if i.1 = 0 : if space \ numx = 0 : continue : else : space--
		if i.1 = 1 : if space / numx = 0 : continue : else : space -= numx
		if i.1 = 2 : if space \ numx = numx - 1 : continue : else : space++
		if i.1 = 3 : if space / numx = numy - 1 : continue : else : space += numx
		tip.space = tip.space ^ tip.i
		tip.i     = tip.space ^ tip.i
		tip.space = tip.space ^ tip.i
	loop
	return
2つ目の初期化、各チップ位置のシャッフルについて説明します。 2行目で「各チップ位置に現在どのチップが格納されているか」を保持するtipをチップ数分用意しています。 3行目でそれぞれのチップ番号を1つずつ用意してからシャッフルを行うのですが、 シャッフルは単純に別のチップと入れ替えるやり方にしてしまうと 完成させることができないものができてしまう可能性もあるので、 実際の入替作業同様にして1つずつランダムに動かして入れ替えるようにしています。 5行目に入替先を決定しており、0は左側、1は上側、2は右側、3は下側が対象としています。 6行目の入替トラップは「左側と入れ替える場合に現在の空きマス位置が左端ではないこと」、 7行目の入替トラップは「上側と入れ替える場合に現在の空きマス位置が上端ではないこと」、 8行目の入替トラップは「右側と入れ替える場合に現在の空きマス位置が右端ではないこと」、 9行目の入替トラップは「下側と入れ替える場合に現在の空きマス位置が下端ではないこと」。 このトラップがないと、上端や下端の場合は配列要素外を参照してしまうことになりますし、 左端や右端の場合は一つ上の段または一つ下の段の逆端のチップ入替が可能となってしまいます。 これらのトラップでふるいにかけて問題がなければ10〜12行目にあるように入替を行います。 数値の場合はTipsで紹介してあるようにビット演算で2つの変数だけで相互入替が可能ですが、 わかりにくければ「A=C:A=B:B=C」と一時変数を用意して入替を行っても構いません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*draw_map
	redraw 0
	color 255, 255, 255 : boxf
	repeat numx * numy
		if cnt = space : continue
		pos cnt \ numx * SX, cnt / numx * SY
		gcopy 2, tip.cnt \ numx * SX, tip.cnt / numx * SY, SX, SY
	loop
	redraw 1
	return
3つ目の初期化、各マスの一斉描画について説明します。 5行目にあるように、現在の空きマスの場合だけ描画しないようにしておいて、 それ以外は現在の書くマス位置に入っているマップチップを描画すればOKです。
 1
 2
 3
 4
 5
 6
 7
 8
#define SX (ginfo_winx / numx)
#define SY (ginfo_winy / numy)

	numx = 3
	numy = 3
	randomize
	gosub *game_start
	stop
 1
 2
 3
 4
 5
 6
 7
*game_start
	space = 0		// 現在の空きマス位置
	clear = 0		// ゲームクリアフラグ
	gosub *draw_tips	// ステージ準備
	gosub *shuffle		// マス位置のランダム化
	gosub *draw_map		// 各マス描画
	return
ゲーム毎に初期化する*game_startラベル自体をサブルーチン化して、 各初期化も*game_startラベル内からジャンプするように紐付けしてあげれば準備完了です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
*game_main
	repeat
		wait 1
		stick key
		if key & 256 {
			i.0 = mousex / SX + mousey / SY * numx
			i.1 = (i - 1 = space) & (i \ numx ! 0)
			i.2 = (i - numx = space) & (i / numx ! 0)
			i.3 = (i + 1 = space) & (i \ numx ! numx - 1)
			i.4 = (i + numx = space) & (i / numx ! numy - 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
			}
		}
	loop
	return
ゲーム本体はこんな感じ。 空きマスの上下左右に隣接するチップをクリックすることで空きマスに動かすようにしました。 6行目の「mousex / SX + mousey / SY * numx」がクリックしたマス番号を算出するもの。 ココで冒頭に説明した「定数式に括弧が必要な場合」が出てきます。 #define#constと違って式の内容は予め計算されませんので、 6行目が「i.0 = mousex / (ginfo_winx / numx) + mousey / (ginfo_winy / numy) * numx」ではなく、 「i.0 = mousex / ginfo_winx / numx + mousey / ginfo_winy / numy * numx」だとすると、 結果が常に0となってしまい正確なマス番号を算出することができなくなってしまいます。 #constは予め計算しておくことができますが、可変なものを式内容に含めることはできませんので、 #defineをカッコ付きで使用している…というワケなのです。 落とし穴でもあるので#defineを使う場合は気をつけましょう。 7〜10行目が*shuffleラベルで行ったものと同様の移動防止トラップです。 入替に問題なければマス内容とチップ番号の入替を行って描画を行えば形として整いましたね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
#define SX (ginfo_winx / numx)
#define SY (ginfo_winy / numy)

	numx = 3
	numy = 3
	randomize
	gosub *game_start
	gosub *game_main
	stop
上のように*game_mainラベルにジャンプできるようにすれば手動の入替が可能となりますが、 このままでは揃えてもゲーム終了することはありませんのでゲームとしては成り立ちません。
 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
			i.1 = (i - 1 = space) & (i \ numx ! 0)
			i.2 = (i - numx = space) & (i / numx ! 0)
			i.3 = (i + 1 = space) & (i \ numx ! numx - 1)
			i.4 = (i + numx = space) & (i / numx ! numy - 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 // クリアフラグを一旦ONにしておく
	repeat numx * numy
		if tip.cnt ! cnt : clear = 0 : break // 元の並びでないならクリアフラグをOFFにして確認終了
	loop
	return
このようにすればメインループを抜けてゲームを終了することができるようになります。 ただし、コレでは「急に操作できなくなるだけに過ぎず、終了したのかがわかりにくい」という問題があります。 そこで、最後の処理としてエンディング(ゲーム終了を告げる処理)を追加してみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define SX (ginfo_winx / numx)
#define SY (ginfo_winy / numy)

	numx = 3
	numy = 3
	randomize
	gosub *game_start
	gosub *game_main
	gosub *game_clear
	stop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*game_clear
	repeat 8, 1
		wait 10
		gmode 3, SX, SY, cnt * 32
		pos space \ numx * SX, space / numx * SY
		gcopy 2, tip.space \ numx * SX, tip.space / numx * SY
	loop
	pos 0, 0 : gcopy 1, , , ginfo_winx, ginfo_winy
	dialog "Game Clear !"
	return
空きマスとなるチップを埋め込んで全てのマスを描画すると共に、チップの外枠を取り除いた元の状態を描画。 最後にクリアメッセージを表示するという簡易的なものにしてみましたが、コレで完成です。 まとめてみたスクリプトはコチラとなります。