神経衰弱の作成 - C/C++言語とDXライブラリでゲーム作成入門
目次
- C/C++言語とDXライブラリでゲーム作成入門
- シューティングゲームの作成(チュートリアル)
- ドットイートゲームの作成(チュートリアル)
- 15パズルの作成
- 神経衰弱の作成
- オセロの作成
- オブジェクト指向を活用したシューティングゲーム
今回の目的
- トランプゲームの作成
- 構造体の使い方
ソースファイル
今回の講座のソースを全て含んだプロジェクトファイル(Visual C++ 2010)を以下に置いておきます。suijaku.zip
ゲームの説明
左クリックでカードをめくり数字が同じカードを当てて行きます。裏のカードが無くなればクリアです。
パズル画像の作成
「suijaku」というプロジェクトを作成し、以下のような画像を作成し、プロジェクトフォルダに置きます。カード画像
ひとつ40×60ピクセルで左が表、右が裏となるように横に並べ「card.png」という名前で保存します。
マーク画像
ひとつ28×28ピクセルで左からスペード・ハート・ダイヤ・クラブの順に並べ「mark.png」という名前で保存します。
ナンバー画像
ひとつ28×28ピクセルで左からA,2,3,4,5,6,7,8,9,10,J,Q,Kの順に並べ「number.png」という名前で保存します。
プログラムの入力・実行
プロジェクトに「suijaku.cpp」という名前の新規ソースファイルを追加し以下のプログラムを入力してください。
#include "DxLib.h"
// 場のカードデータ
struct {
int mark;
int number;
bool reverse;
} cards[4][13];
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
int turns = 0;
int ox, oy, nx, ny;
int remains = 52;
int start_time, elapsed_time;
bool mouse_flag = false;
int graph_cards[2], graph_marks[4], graph_numbers[13];
ChangeWindowMode(TRUE);
DxLib_Init();
SetDrawScreen(DX_SCREEN_BACK);
LoadDivGraph("card.png", 2, 2, 1, 40, 64, graph_cards);
LoadDivGraph("mark.png", 4, 4, 1, 28, 28, graph_marks);
LoadDivGraph("number.png", 13, 13, 1, 28, 28, graph_numbers);
// シャッフル
bool deal[4][13] = {};
for (int y = 0; y < 4; y++) for (int x = 0; x < 13; x++) {
int mk, no;
do {mk = GetRand(3); no = GetRand(12);} while (deal[mk][no]);
cards[y][x].mark = mk;
cards[y][x].number = no;
cards[y][x].reverse = true;
deal[mk][no] = true;
}
start_time = GetNowCount();
while (!ProcessMessage()) {
ClearDrawScreen();
// カードめくり処理
if (GetMouseInput() & MOUSE_INPUT_LEFT) {
if (!mouse_flag) {
mouse_flag = true;
if (turns == 2) {
cards[oy][ox].reverse = true;
cards[ny][nx].reverse = true;
turns = 0;
} else {
GetMousePoint(&nx, &ny);
nx = (nx - 60) / 40;
ny = (ny - 96) / 64;
if (nx >= 0 && nx <= 12 && ny >= 0 && ny <= 3 && cards[ny][nx].reverse) {
cards[ny][nx].reverse = false;
if (turns == 0) {
ox = nx; oy = ny;
turns = 1;
} else {
if (cards[ny][nx].number == cards[oy][ox].number) {
remains -= 2;
turns = 0;
} else turns = 2;
}
}
}
}
} else mouse_flag = false;
// 場のカード表示
for (int y = 0; y < 4; y++) for (int x = 0; x < 13; x++) {
int x2 = x * 40 + 60;
int y2 = y * 64 + 96;
DrawGraph(x2, y2, graph_cards[cards[y][x].reverse], FALSE);
if (!cards[y][x].reverse) {
DrawGraph(x2 + 6, y2 + 5, graph_marks[cards[y][x].mark], FALSE);
DrawGraph(x2 + 6, y2 + 32, graph_numbers[cards[y][x].number], FALSE);
}
}
// メッセージ表示
int color = GetColor(255, 255, 255);
if (remains <= 0) DrawString(8, 8, "クリア!", color);
else elapsed_time = (GetNowCount() - start_time) / 1000;
DrawFormatString(8, 450, color, "残り %d枚 経過時間 %d秒", remains, elapsed_time);
ScreenFlip();
}
DxLib_End();
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。プログラムの解説
カードデータ構造体
struct {
int mark;
int number;
bool reverse;
} cards[4][13];
52枚のカードデータです。場には以下のように配置されます。cards[0][0] | cards[0][1] | ・・・ | cards[0][11] | cards[0][12] |
cards[1][0] | cards[1][1] | ・・・ | cards[1][11] | cards[1][12] |
cards[2][0] | cards[2][1] | ・・・ | cards[2][11] | cards[2][12] |
cards[3][0] | cards[3][1] | ・・・ | cards[3][11] | cards[3][12] |
mark | カードのマーク。0:スペード 1:ハート 2:ダイヤ 3:クラブ |
number | カードのナンバー。0~12でA~K。 |
reverse | 裏返ったカードならばtrue |
変数
int turns = 0;
未確定のカードをめくった状態を表します。0ならめくっていない状態、1なら1枚めくった状態、2なら2枚めくった状態となります。既にペアになって確定したカードはカウントしません。
int ox, oy, nx, ny;
未確定のカードを裏返したとき、(ox, oy)が1枚目、(nx, ny)が2枚目の座標になります。
int remains = 52;
裏返っている残りカード数。
int start_time, elapsed_time;
start_timeはゲーム開始時刻、elapsed_timeは経過時間になります。シャッフル
bool deal[4][13] = {};
for (int y = 0; y < 4; y++) for (int x = 0; x < 13; x++) {
int mk, no;
do {mk = GetRand(3); no = GetRand(12);} while (deal[mk][no]);
cards[y][x].mark = mk;
cards[y][x].number = no;
cards[y][x].reverse = true;
deal[mk][no] = true;
}
カードを場にランダムに配る部分です。deal配列は既に配ったカードのチェック用です。メインループ
start_time = GetNowCount();
まずメインループ開始前に、GetNowCount関数によってゲーム開始時刻をstart_timeに格納します。
if (turns == 2) {
cards[oy][ox].reverse = true;
cards[ny][nx].reverse = true;
turns = 0;
左クリックしたときに未確定のカードを2枚めくっていれば、それは外れなので裏返しにします。
GetMousePoint(&nx, &ny);
nx = (nx - 60) / 40;
ny = (ny - 96) / 64;
if (nx >= 0 && nx <= 12 && ny >= 0 && ny <= 3 && cards[ny][nx].reverse) {
cards[ny][nx].reverse = false;
クリックした位置がカードの範囲内にあり、その場にあるカードが裏返しならば、めくる処理をします。
if (turns == 0) {
ox = nx; oy = ny;
turns = 1;
} else {
if (cards[ny][nx].number == cards[oy][ox].number) {
remains -= 2;
turns = 0;
} else turns = 2;
}
カードを1枚めくった状態ならば(ox, oy)にめくったカードの座標を代入します。カードを2枚めくった状態ならばカードのナンバーを比較して、同じならば残りカード数を2枚減らします。