画像(logo)

HOME/[C言語DXライブラリ]ブロックパズルの作り方 目次/六日目 ブロック消去

広告

[C言語 DXライブラリ ブロックパズルの作り方]
六日目 ブロック消去

広告

↓2016年02月29日発売↓

12歳からはじめる ゼロからのC言語 ゲームプログラミング教室

目次へ戻る

ブロック消去

今回はブロックの種類を増やすのと横一列に揃ったブロックを消去していきます。

ブロックの種類を増やす

ブロックの種類を増やします。

int blocks[BLOCK_HEIGHT * 6][BLOCK_WIDTH * 4] = {
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0},
	{0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},

	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,2,2,0,0,0,2,0,0,2,0,0,0,2,2,0},
	{0,0,2,0,0,2,2,0,0,2,2,0,0,2,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},

	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,3,3,0,0,3,3,0,0,0,3,0,0,3,0,0},
	{0,3,0,0,0,0,3,0,0,3,3,0,0,3,3,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},

	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,4,0,4,0,0,4,4,0,0,0,0,0,4,4,0},
	{0,4,4,4,0,0,4,0,0,4,4,4,0,0,4,0},
	{0,0,0,0,0,0,4,4,0,4,0,4,0,4,4,0},

	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,5,0,0,0,0,0,0,0,5,0,0,0,5,0,0},
	{0,5,5,0,5,5,5,0,5,5,0,0,5,5,5,0},
	{0,5,0,0,0,5,0,0,0,5,0,0,0,0,0,0},

	{0,6,0,0,0,0,0,0,0,6,0,0,0,0,0,0},
	{0,6,0,0,6,6,6,6,0,6,0,0,6,6,6,6},
	{0,6,0,0,0,0,0,0,0,6,0,0,0,0,0,0},
	{0,6,0,0,0,0,0,0,0,6,0,0,0,0,0,0}
};

そのまま2次元配列を拡張するカタチでブロックの回転状態とともにブロックの種類を6種類に増やしてみました。

という事で今回からこのブロックの元の2次元配列からランダムにブロックを取り出してみたいと思います。

void my_make_block(){
	int x,y;

	if(make_block_flag == 1){
		block_id = (rand() % 6);
		for(y=0;y<BLOCK_HEIGHT;y++){
			for(x=0;x<BLOCK_WIDTH;x++){
				block[y][x] = blocks[(block_id * BLOCK_HEIGHT) + y][x];
			}
		}
		make_block_flag = 0;
	}	
}

ブロック生成の「my_make_block()」を変更しました。

グローバル変数として現在のブロックの種類を表す変数

int block_id;

を用意しておきます。

ランダムにブロックを発生させるので乱数を使います。

block_id = (rand() % 6);

今回6種類のブロックになるので発生させる値は「0〜5」までの6つになりますね。

乱数の種を作る「srand((unsigned)time(NULL));」文は初期化の「my_init_var()」に設置しました。

そして発生させた乱数値を「block_id」に保存します。

あとは「block_id」の値を使ってさきほどの2次元配列「blocks」からブロックを読み出すだけです。

for(y=0;y<BLOCK_HEIGHT;y++){
	for(x=0;x<BLOCK_WIDTH;x++){
		block[y][x] = blocks[(block_id * BLOCK_HEIGHT) + y][x];
	}
}

回転状態を表す「x」の位置はそのままでブロックの種類を表す「y」の位置だけ「ブロックの種類番号 × ブロックの高さ」という計算でその特定の場所からブロック情報を読み出す事ができます。

これでランダムにブロックを発生させる事ができました。

消去

続いて今回のメインテーマ、ブロック消去処理をやっていきます。

消去する列を探す

まずは消去すべき横一列にブロックが揃った列を探します。

void my_search_line(){
	int i,j;

	for(i=0;i<STAGE_HEIGHT - 3;i++){
		clear_line_point[i] = 0;
	}

	for(i=0;i<STAGE_HEIGHT - 3;i++){
		for(j=3;j<STAGE_WIDTH - 3;j++){
			if(stage[i][j] == 0){
				clear_line_point[i] = 1;
				break;
			}
		}
	}

	for(i=0;i<STAGE_HEIGHT - 3;i++){
		if(clear_line_point[i] == 0){
			clear_flag = 1;
			break;
		}
	}
}

横一列ブロックが揃っている列を探す「my_search_line()」です。

グローバル変数に揃った列の場所を入れる為の変数

int clear_line_point[20];

とクリア状態かどうかのフラグ

int clear_flag;

を用意します。

「clear_line_point」は「stage[23][18]」の内側だけを調べるので要素数は「20」となっております。

まずは「clear_line_point[20]」を初期化

for(i=0;i<STAGE_HEIGHT - 3;i++){
	clear_line_point[i] = 0;
}

そして「stage[23][18]」の内側だけを調べるようなカタチで横一列ブロックが揃っている列を探します。

for(i=0;i<STAGE_HEIGHT - 3;i++){
	for(j=3;j<STAGE_WIDTH - 3;j++){
		if(stage[i][j] == 0){
			clear_line_point[i] = 1;
			break;
		}
	}
}

ここで横一列が揃っている状態というのは逆に言えば空白を表す「0」がその列に一つもない状態の事なので「0」が一つでもあれば「clear_line_point[i] = 1;」というように目印をつけて次の列へというように全ての列を探索していきます。

結果「clear_line_point[i] = 0」の列が消去すべき列になるというワケです。

そしてその結果を調べてクリア状態に入るかどうかを「clear_flag」へ格納します。

for(i=0;i<STAGE_HEIGHT - 3;i++){
	if(clear_line_point[i] == 0){
		clear_flag = 1;
		break;
	}
}

これでクリアすべき列の探索はおしまいです。

ブロック固定の「my_fix_block()」にこちらの関数を加えます。

void my_fix_block(){
	
	my_collision_bottom();

	if(collision_flag != 0){
		my_save_block();
		my_search_line();
		if(clear_flag == 0){
			my_init_var2();
		}	
	}
}

「my_save_block()」の後に加えるトコロに注意です。

そして前回まではそのまま「my_init_var2()」によりブロック座標等を元に戻しておりましたが今回からクリア処理が入るので少し違った分岐をします。

広告

クリア状態

クリアすべき列がわかったのですぐにクリア処理に入りたいトコロですが列が消えている事がわかるように少しの間がほしいと思います。

そしてそのクリアしている間というのはキー入力処理やブロック再生成処理など、もろもろの処理を禁止にしなければいけません。

なのでメインにて通常時とクリア状態時の処理を分岐します。

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
	ChangeWindowMode(TRUE);
	DxLib_Init();
	SetDrawScreen(DX_SCREEN_BACK);

	my_init_var();

	while(ProcessMessage() == 0){
		ClearDrawScreen();

		switch(game_state){
		case 0:
			game_state = 5;
			break;
		case 5:
			if(clear_flag == 0){
				my_make_block();
				my_gameover();
				my_get_key();
				my_move_block();
				my_draw_back();
				my_draw_variable();
				my_draw_block();
				my_draw_stage();
				my_fix_block();
				my_fall_block();
			}
			else{
				my_clear_line();
				my_draw_back();
				my_draw_variable();
				my_draw_stage();
			}
			
			if(gameover_flag == 1){
				game_state = 10;
			}
			break;
		case 10:
			my_draw_back();
			my_draw_block();
			my_draw_stage();
			my_ed();
			break;
		default:
			break;
		}

		ScreenFlip();
	}

	DxLib_End();
	return 0;
}

さきほどの「clear_flag」を使って分岐させております。

それではクリア状態時の処理を見ていきます。

クリアして表示

揃った列をクリアして入れ替え処理を行う「my_clear_line()」になります。

ちょっと長いので前半、後半に分けて説明します。

まずは揃った列の部分をクリアして表示したいので、その列を空白を表す「0」に置き換えます。

void my_clear_line(){
	int i,j;
	int remain_line_point[20] = {0};
	int remain_line_index = 0;

	if(clear_count < 12){
		for(i=0;i<STAGE_HEIGHT - 3;i++){
			if(clear_line_point[i] == 0){
				stage[i][clear_count + 3] = 0;
			}
		}
		clear_count++;
	}
	else{
		/*クリアした場所の入れ替え処理*/
	}
}

さきほどクリアすべき列は「clear_line_point」に保存したのでこれを調べます。

for(i=0;i<STAGE_HEIGHT - 3;i++){
	if(clear_line_point[i] == 0){
		stage[i][clear_count + 3] = 0;
	}
}

「clear_line_point[i] == 0」の場所がクリアすべき列になりますね。

そこを空白を表す「0」に置き換えます。

今回は列の端から一つずつ消えるようにしてみました。

そしてクリアしている時間をカウントする為の「clear_count」を用意してお好みの間クリア状態にします。

この時のクリアしている間のメインの描画は「my_draw_stage()」のみになるので注意です。

クリアした場所の入れ替え処理

あとは後始末としてクリアした場所の入れ替え処理を行っていきます。

さきほどの「else」部分のみ抜粋です。

for(i=STAGE_HEIGHT - 4;i >= 0;i--){
	if(clear_line_point[i] != 0){
		remain_line_point[remain_line_index] = i;
		remain_line_index++;
	}
}

remain_line_index = 0;
for(i=STAGE_HEIGHT - 4;i >= 0;i--){
	for(j=3;j<STAGE_WIDTH - 3;j++){
		stage[i][j] = stage[remain_line_point[remain_line_index]][j];
	}
	remain_line_index++;
}

clear_flag = 0;
clear_count = 0;
my_init_var2();

今度は残すべき列を変数「int remain_line_point[20]」に保存します。

for(i=STAGE_HEIGHT - 4;i >= 0;i--){
	if(clear_line_point[i] != 0){
		remain_line_point[remain_line_index] = i;
		remain_line_index++;
	}
}

どういう事かと言いますと例えば次の列番号があったとして

[0][1][2][3][4][5][6][7][8][9]

この中の[1]と[4]が消去すべき列だとしてそれを消去して詰めた状態

[0][2][3][5][6][7][8][9]

になるように「remain_line_point[20]」に保存しなおしているような作業をしております。

残す列「clear_line_point[i] != 0」の列が見つかれば「remain_line_point」に保存、「remain_line_index++」と添え字数を一つ進めます。

残す列が「remain_line_point」に全て保存されたトコロで入れ替えていきます。

remain_line_index = 0;
for(i=STAGE_HEIGHT - 4;i >= 0;i--){
	for(j=3;j<STAGE_WIDTH - 3;j++){
		stage[i][j] = stage[remain_line_point[remain_line_index]][j];
	}
	remain_line_index++;
}

「remain_line_point」には残すべき列の番号が頭から保存されているので少しややこしいですが「stage[23][18]」の内側の下の方から入れ替えていけば入れ替え処理は完了です。

これでクリア状態は終了したので必要なフラグ等を元に戻して、

clear_flag = 0;
clear_count = 0;
my_init_var2();

クリア状態を解除します。

画像(cdxb_6_1)

クリア!!!

ここまでの中間ソースになります。

中間ソース5

次回は回転処理を加えたいと思います。

次回

最終日 ブロック回転

□ページの先頭へ□

□目次へ戻る□

□HOME□

広告

↓2017年06月16日発売↓

やさしいC 第5版 (「やさしい」シリーズ)

新品価格
¥2,700から
(2017/5/1 13:05時点)

↓2014年08月09日発売↓

新・明解C言語 入門編 (明解シリーズ)

新品価格
¥2,484から
(2017/5/1 13:08時点)

↓2016年02月20日発売↓

新・解きながら学ぶC言語

新品価格
¥2,160から
(2017/5/1 13:10時点)

↓2017年02月11日発売↓

C言語プログラミング基本例題88 88

新品価格
¥3,024から
(2017/5/1 13:12時点)

↓2016年12月15日発売↓

Cの絵本 第2版 C言語が好きになる新しい9つの扉

新品価格
¥1,490から
(2017/5/1 13:13時点)

↓2017年02月08日発売↓

新・明解C言語で学ぶアルゴリズムとデータ構造 (明解シリーズ)

新品価格
¥2,700から
(2017/5/1 13:15時点)