ポンクソフト

15パズルの作成 - C言語とelで様々なゲームを作ろう

前ページ C言語とelで様々なゲームを作ろう TOP 次ページ

目次

  1. C言語とelで様々なゲームを作ろう
  2. Visual C++ .NET での設定
  3. テンプレートファイルの解説
  4. シューティングゲームの作成(チュートリアル)
  5. パックマン的ゲームの作成(チュートリアル)
  6. ブロック崩しの作成
  7. 15パズルの作成
  8. 横スクロールジャンピングゲームの作成
  9. オセロの作成
  10. 神経衰弱の作成
  11. 7ならべの作成
  12. テトリスの作成
  13. ぷよぷよの作成

ソース・プロジェクトファイル

ソースファイル
プロジェクトファイル(Visual C++ 6.0)

ゲームの説明

最初に出てくる絵をよく覚えておいて左クリックし、バラバラになった絵をマウスで移動して揃えればクリアです。

プログラムの解説

グローバル変数

DDOBJ pic;
パズルの完成画像です。サイズは 320 × 320。
int panel[16];
15パズルでは、画像を縦と横にそれぞれ4分割しますが、どこにどの画像が入っているかを表わす配列が panel です。各要素は 0 ~ 15 の値を取り、0 ~ 14 がパネルを分割した画像、15 がパネルを移動するための空きです。
配列の各要素がどの位置のパネルを表わすかは、以下の通り。
15puzzle_panel.gif

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;
今回はゲーム中の状態がひとつだけではなく、
の3つがありますので、その状態を管理する変数が status です。0 がゲーム開始前、1 がゲーム中、2 がゲームクリア時の状態を表わします。
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 は内部でマウスカーソルを消しているので、毎フレームにこの表示処理が必要になります。
前ページ C言語とelで様々なゲームを作ろう TOP 次ページ
このエントリーをはてなブックマークに追加 そっか0

このページに関するコメントをどうぞ

お名前:


kuv
このプログラムでは、連結判定の時はつながっているぷよごとに結合番号をつけ、消せる場合はelist配列の結合番号と同じ要素番号のところにフラグを立てる、という方法をとっています。
ですが、例えばぷよをわざとつながらないように置いていった場合、結合番号の最大数がelist配列の要素数をオーバーしてしまうのではないでしょうか?
それに対応するにはelist配列の要素数をフィールドの大きさと同じ6*13=78にしなければいけないと思うのですが、30にしているのには何か意図があるのでしょうか?
2017/07/11 14:52

しお
vectorではなくlistを使っているのはなぜなのでしょうか?
2017/05/07 19:06

surach
なぜ弾のX座標が sx = px + 24;で時機の中央になるんですか?
2017/05/05 11:09

何度やってもできないー^^
ファイルが見つかりませんとなるのですが・・・
どうすれば?
2017/01/20 17:23

ww
dxlibは外部から自分で導入するものです。しっかり導入しましたか?
2016/12/26 12:29

ww
IncludePathとLibraryPathはdxライブラリを導入したときのフォルダパスが間違っているため、dxlib.hがあるフォルダパスを設定しなおせば動きます。
2016/12/26 12:28

ww
MTdは、プロジェクトのプロパティの設定項目から変更できる。
Debugモードの設定が、違うようですね↓
2016/12/26 12:26

笑子
実行したところ、白い画面が出てきてしまいます。解決策は、あるのでしょうか?
2016/11/10 23:38


背景を挿入したいのですが,どこにどんな風にプログラムを加えればいいのでしょうか?
2016/09/24 21:44

華仙 学
オセロプログラムに関してです。
Visual Studio 2015を使ってます。このプログラムを打ち込んだところ、「'RuntimeLibrary'の不一致が検出されました。値'MTd_StaticDebug'がMDd_DynamicDebugの値'othello.obj'と一致しません」と出てしまいます。いろいろ調べていますがなかなか解決しません。何か解決策はあるでしょうか?
2016/08/17 21:12

残りを読む »