15パズルの作成 - C言語とelで様々なゲームを作ろう
目次
- C言語とelで様々なゲームを作ろう
- Visual C++ .NET での設定
- テンプレートファイルの解説
- シューティングゲームの作成(チュートリアル)
- パックマン的ゲームの作成(チュートリアル)
- ブロック崩しの作成
- 15パズルの作成
- 横スクロールジャンピングゲームの作成
- オセロの作成
- 神経衰弱の作成
- 7ならべの作成
- テトリスの作成
- ぷよぷよの作成
ソース・プロジェクトファイル
ソースファイルプロジェクトファイル(Visual C++ 6.0)
ゲームの説明
最初に出てくる絵をよく覚えておいて左クリックし、バラバラになった絵をマウスで移動して揃えればクリアです。プログラムの解説
グローバル変数
DDOBJ pic;
パズルの完成画像です。サイズは 320 × 320。
int panel[16];
15パズルでは、画像を縦と横にそれぞれ4分割しますが、どこにどの画像が入っているかを表わす配列が panel です。各要素は 0 ~ 15 の値を取り、0 ~ 14 がパネルを分割した画像、15 がパネルを移動するための空きです。配列の各要素がどの位置のパネルを表わすかは、以下の通り。
Swap関数
void Swap(int i, int j)
{
int tmp = panel[i];
panel[i] = panel[j];
panel[j] = tmp;
}
引数でパネルの番号を2つ指定すると、その2つのパネルを入れ替える関数です。今回のプログラムでは、この入れ替え処理が良く行なわれるので関数化してあります。ウインドウ生成関数
pic = elDraw::LoadObject("pic.bmp");
完成画像をスプライトに読み込んでいる部分です。
for (i = 0; i < 16; i++) panel[i] = i;
パネルの初期状態として、各パネルの値をそれぞれ正しい画像、つまり完成したときの状態にしています。この状態からシャッフルして行きます。
for (i = 0; i < 1000; i++) {
for (int j = 0; j < 16; j++) if (panel[j] == 15) pn = j;
int x = pn % 4; int y = pn / 4;
int dir = rand() % 4;
switch (dir) {
case 0: if (x > 0) Swap(pn - 1, pn); break;
case 1: if (x < 3) Swap(pn, pn + 1); break;
case 2: if (y > 0) Swap(pn - 4, pn); break;
case 3: if (y < 3) Swap(pn, pn + 4); break;
}
}
パネルをシャッフルしている部分です。ランダムにパネルを当てはめると絶対に完成しないパズルができる可能性があるので、完成した状態からひとつずつシャッフルして行きます。内部の処理については以下の通り。
for (i = 0; i < 1000; i++) {
シャッフルの回数を表わすループです。ここでは 1000 回シャッフルしていますが、この数値を下げることによって難易度を下げることもできます。
for (int j = 0; j < 16; j++) if (panel[j] == 15) pn = j;
空きパネルを探して、その位置を pn に代入しています。
int x = pn % 4; int y = pn / 4;
(x, y) は空きパネルの座標です(それぞれ 0 ~ 3)。この、整数の割り算と剰余の組み合わせはゲームで非常に良く使われるテクニックです。
int dir = rand() % 4;
空きパネルと入れ替えるパネルの方向をランダムに決めています。0 が左、1 が右、2 が上、3 が下です。
switch (dir) {
case 0: if (x > 0) Swap(pn - 1, pn); break;
case 1: if (x < 3) Swap(pn, pn + 1); break;
case 2: if (y > 0) Swap(pn - 4, pn); break;
case 3: if (y < 3) Swap(pn, pn + 4); break;
}
入れ替える方向に実際にパネルがあるかどうか調べ、パネルがあるときのみ Swap 関数を呼び出し、パネルを入れ替えています。メイン画面
static int status = 0;
今回はゲーム中の状態がひとつだけではなく、- ゲーム開始前に完成画像を確認する状態
- 実際のゲーム中の状態
- ゲームをクリアして完成画像が出る状態
if (MouseCL) {
マウスがクリックされたときの処理を行なっています。MouseCL は el の変数で、マウスの左ボタンがクリックされたら TRUE になります。
if (status == 0) status = 1;
ゲーム開始前にマウスをクリックしたらゲームスタートになるので status に 1(ゲーム中)を代入しています。
x = MousePX / 80;
y = MousePY / 80;
(MousePX, MousePY) はマウスの座標を表わす el の変数です。ひとつのパネルの大きさが 80 × 80 で、それをマウスの座標から割ることによって、(x, y) がクリックされたパネルの座標(それぞれ 0 ~ 3)を表わします。
int pn = y * 4 + x;
pn はクリックされたパネルの番号を表わします。
if (x > 0 && panel[pn - 1] == 15) Swap(pn - 1, pn);
左端のパネルをクリックしたのではなく、なおかつ左が空きパネルの場合、そのパネルとクリックされたパネルを交換します。
if (x < 3 && panel[pn + 1] == 15) Swap(pn, pn + 1);
if (y > 0 && panel[pn - 4] == 15) Swap(pn - 4, pn);
if (y < 3 && panel[pn + 4] == 15) Swap(pn, pn + 4);
右、上、下についても同じ処理を行ないます。
for (i = 0; i < 16; i++) if (panel[i] != i) break;
パズルが完成した場合、panel の添え字と値が全て等しくなるのでそれをチェックしています。揃っていない場合は、そこでループを抜けます。
if (i == 16) {
MESG("クリア!");
status = 2;
}
i が 16、つまりループを最後まで抜けなかった場合、パズルが完成したとみなし、メッセージを出し、ステータスを 2(ゲームクリア)にしています。MESG は ダイアログでメッセージを出す el の関数です。
if (status == 1) {
for (i = 0; i < 16; i++) {
x = (panel[i] % 4) * 80;
y = (panel[i] / 4) * 80;
if (panel[i] < 15)
elDraw::Layer((i % 4) * 80, (i / 4) * 80, pic, x, y, x + 80, y + 80);
}
ゲーム中の場合、配列 panel に入っている適切なパネルを表示しています。panel の値が 15 の場合は空きパネルなので何もしません。
} else {
elDraw::Layer(0, 0, pic, 0, 0, 320, 320);
}
ゲーム中でない、つまりゲーム開始前またはゲームクリア後の場合、完成画像を表示します。
if (status == 0) SHOW(74, 155, "この絵をよく覚えて左クリック!");
ゲーム開始前に出すメッセージです。
ShowCursor(TRUE);
マウスカーソルを表示する el の関数です。el は内部でマウスカーソルを消しているので、毎フレームにこの表示処理が必要になります。