7ならべの作成 - C言語とelで様々なゲームを作ろう
目次
- C言語とelで様々なゲームを作ろう
- Visual C++ .NET での設定
- テンプレートファイルの解説
- シューティングゲームの作成(チュートリアル)
- パックマン的ゲームの作成(チュートリアル)
- ブロック崩しの作成
- 15パズルの作成
- 横スクロールジャンピングゲームの作成
- オセロの作成
- 神経衰弱の作成
- 7ならべの作成
- テトリスの作成
- ぷよぷよの作成
ソース・プロジェクトファイル
ソースファイルプロジェクトファイル(Visual C++ 6.0)
ゲームの説明
トランプの7ならべです。左クリックでカードを場にだし、右クリックでパスです。コンピュータ3人と対戦します。本当の7ならべとは違い、場に7が最初から出ていて、ジョーカーはありません(プログラムを簡単にするため)。プログラムの解説
構造体
struct {
int top;
int end;
} ba[4] = {6, 6, 6, 6, 6, 6, 6, 6};
場に出されている先頭のカードと末尾のカードの数字を持ちます。ba[0] がスペード、ba[1] がハート、ba[2] がダイヤ、ba[3] がクローバーの行を表わします。top が先頭、end が末尾のカードの数字で、1 ~ K をそれぞれ 0 ~ 12 で表わします。例えば、ハートの行が [6][7][8][9][10][J] と並んでいた場合、ba[1].top は 5、ba[1].end は 10 となります。今回は最初に7を並べておくので、初期値として top と end 両方に 6 を入れておきます。
struct {
int num;
bool pass;
int mark[12];
int no[12];
} pl[4];
各プレイヤーの情報を持つ構造体です。pl[0] が人間、pl[1] ~ pl[3] がそれぞれコンピュータ1~コンピュータ3の情報を持ちます。各メンバの意味は以下の通り。- num : 手持ちのカード枚数。
- pass : プレイヤーがパスをしたら true になるフラグ。
- mark : 手持ちのカードのマーク。現在持っている枚数分のデータを持つ。
- no : 手持ちのカードの数字。現在持っている枚数分のデータを持つ。
定数・グローバル変数
DDOBJ bmpCard, bmpMark, bmpNumber;
カード(表と裏)、マーク(4種類)、番号(13種類)のスプライトオブジェクトです。使用画像は神経衰弱の時と同じですが、裏向きのカードは今回使いません。![suijaku_bmp.gif](suijaku_bmp.gif)
ウインドウ生成関数 画像読み込み処理
bmpCard = elDraw::LoadObject("card.bmp");
bmpMark = elDraw::LoadObject("mark.bmp");
bmpNumber = elDraw::LoadObject("number.bmp");
カード・マーク・番号の各画像をスプライトに読み込んでいます。ウインドウ生成関数 シャッフル処理
カードをシャッフルして各プレイヤーに配る処理を行なう部分です。
int mk, no, cn, pn;
シャッフル用のワーク変数で、mk はカードのマーク、no はカードの数字、cn は各プレイヤーの何枚目のカードを配っているか、pn はプレイヤー番号を表わします。
int ck[4][13] = {0};
シャッフルをして、配り終えたカードをチェックする配列です。配り終えたカードの配列要素には 1 が入ります。
for (mk = 0; mk < 4; mk++) ck[mk][6] = 1;
7のカードは配らないので、先にチェックしておきます。
for (pn = 0; pn < 4; pn++) {
プレイヤーを4人分ループします。
for (cn = 0; cn < 12; cn++) {
最初は各プレイヤーに 12 枚ずつ配るので、12 回ループします。
do {mk = rand() % 4; no = rand() % 13;} while (ck[mk][no] == 1);
配るカードのマーク (mk) と番号 (no) をランダムでひとつ選んでいます。配り終えていないカードが選択されるまでループします。
pl[pn].mark[cn] = mk;
pl[pn].no[cn] = no;
プレイヤーに選ばれたカードを配ります。
ck[mk][no] = 1;
配り終えたチェックを入れます。
pl[pn].num = 12;
pl[pn].pass = false;
各プレイヤーの初期値として、持っているカード枚数を 12 枚とし、パスフラグを false とします。CardPrint カードを表示する関数
void CardPrint(int x, int y, int no, int mark)
(x, y) の位置に、数字が no、マークが mark のカードを表示する関数です。
elDraw::Layer(x, y, bmpCard, 0, 0, 40, 64);
elDraw::Layer(x + 6, y + 5, bmpMark, mark * 28, 0, mark * 28 + 28, 28);
elDraw::Layer(x + 6, y + 33, bmpNumber, no * 28, 0, no * 28 + 28, 26);
まずカードの土台を描画して、その上にマークと数字を描画します。CardPut 指定されたカードを場に出す関数
bool CardPut(int pn, int cn)
番号 pn プレイヤーの手持ちから cn 番目のカードを場に出す関数です。戻り値として、実際に場に出すことができれば true、出せなければ false を返します。
bool ret = true;
ret は戻り値です。まず最初はカードを出せると仮定しておきます。
int mk = pl[pn].mark[cn];
mk には出そうとするカードのマークが入ります。
if (pl[pn].no[cn] == ba[mk].top - 1) ba[mk].top--;
pl[pn].no[cn] は出そうとするカードの数字です。この数字が場の先頭の数字-1ならばカードを出し、場の先頭の数字をひとつ減らします。
else if (pl[pn].no[cn] == ba[mk].end + 1) ba[mk].end++;
出そうとするカードの数字が場の末尾の数字+1ならばカードを出し、場の末尾の数字をひとつ増やします。
else ret = false;
場に出せなければ戻り値を false とします。
if (ret) {
for (int i = cn + 1; i < pl[pn].num; i++) {
pl[pn].no[i - 1] = pl[pn].no[i];
pl[pn].mark[i - 1] = pl[pn].mark[i];
}
pl[pn].num--;
}
ret が true の場合、つまりカードを場に出せた場合に、出したカード以降のカードをひとつ前に詰める処理を行い、手持ちのカード数をひとつ減らします。メイン画面 変数
static int nowpl = 0;
現在のプレイヤー番号が入る変数です。
static float wait = 0;
コンピュータの処理は一瞬で終わるので、どのコンピュータがどんなカードを出したかは分かりにくくなります。そこでコンピュータの番では時間待ちを行なっているのですが、それを管理する変数が wait です。
char* pname[4] = {"You", "Com1", "Com2", "Com3"}; // プレイヤー名
各プレイヤーの名前です。メイン画面 人間の番
if (MouseCL) {
マウスが左クリックされたときは、カードを場に出す処理を行ないます。
int xs = (640 - pl[0].num * 40) / 2;
xs には、手持ちのカードの先頭の x 座標が入ります。640 は画面の横幅です。これから全部のカードを足した幅を引き、2 で割ることによってカードを中央に表示しています。
if (MousePX >= xs && MousePX < xs + pl[0].num * 40 &&
MousePY >= 384 && MousePY < 384 + 64) {
マウスが手持ちのカードの上に乗っているとき、if 文内の処理を行ないます。
cn = (MousePX - xs) / 40;
cn に何番目のカードをクリックしたかが入ります。
if (CardPut(0, cn)) {
pl[0].pass = false;
nowpl++;
}
クリックしたカードが場に置ければ、パスフラグをクリアして、次のプレイヤーの番にします。
} else if (MouseCR) {
pl[0].pass = true;
nowpl++;
}
マウスが右クリックされたときは、パスフラグをセットして、次のプレイヤーの番にします。メイン画面 コンピュータの番
wait += FrameTime;
if (wait > 0.3) {
wait = 0;
まず wait にフレーム時間を足し、0.3 秒以上になれば wait をクリアしてからコンピュータの処理を行ないます。FrameTime は el のシステム変数で、各フレームの経過秒数を保持しています。
int ck[12] = {0};
ck は手持ちのカードからどのカードが出せないかをチェックする配列です。出せないカードの要素には 1 が入ります。
pl[nowpl].pass = true;
まず仮にパスフラグをセットしておきます。
for (int i = 0; i < pl[nowpl].num; i++) {
カードを場に出せるかを、手持ちのカード全てにおいてチェックするループです。
do cn = rand() % pl[nowpl].num; while (ck[cn]);
出すカードをランダムで選びます。チェック済みでないカードが選ばれるまでループします。
ck[cn] = 1;
出すカードをチェックします。
if (CardPut(nowpl, cn)) {
pl[nowpl].pass = false;
break;
}
選んだカードが場に置ければ、パスフラグをクリアし、ループを抜けます。
if (++nowpl >= 4) nowpl = 0;
次のプレイヤーにし、4人目まで行ったら1人目に戻します。メイン画面 カード表示処理
for (int mk = 0; mk < 4; mk++) {
for (int no = 0; no < 13; no++) {
if (no >= ba[mk].top && no <= ba[mk].end)
CardPrint(60 + no * 40, 64 + mk * 64, no, mk);
}
}
場の先頭以上で末尾以下の数字のカードを CardPrint 関数によって表示しています。
for (cn = 0; cn < pl[0].num; cn++) {
CardPrint((640 - pl[0].num * 40) / 2 + cn * 40, 384, pl[0].no[cn], pl[0].mark[cn]);
}
人間の手持ちのカードを表示している部分です。(640 - pl[0].num * 40) / 2 の計算によってカードを中央にセンタリングして表示しています。メイン画面 プレイヤー情報表示処理
for (i = 0; i < 4; i++) {
int x = 8 + i * 100;
SHOW(x, 8, pname[i]);
SHOW2(x, 24, "残り%d枚", pl[i].num);
if (pl[i].pass) SHOW(x, 40, "Pass!");
}
各プレイヤーの名前・残りカード枚数・今回パスをしたかどうかを画面に表示している部分です。メイン画面 勝利者チェック処理
for (i = 0; i < 4; i++) {
if (pl[i].num <= 0) {
MESG("%s Win!!", pname[i]);
elDraw::Exit();
}
}
プレイヤーの残り枚数が0になったら、プレイヤーの名前と勝利メッセージを表示して終了します。