画像(logo)

HOME/[JAVA言語]ブロックパズルの作り方 目次/六日目 ブロック消去

[JAVA言語 ブロックパズルの作り方]
六日目 ブロック消去

広告

↓2017年04月19日発売↓

カラー図解 Javaで始めるプログラミング 知識ゼロからの定番言語「超」入門 (ブルーバックス)

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

目次へ戻る

ブロック消去

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

ブロックの種類を増やす

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

private int blocks[][] = {
	{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(){
	if(make_block_flag == 1){
		block_id = rnd.nextInt(6);
		for(int y=0;y<BLOCK_HEIGHT;y++){
			for(int x=0;x<BLOCK_WIDTH;x++){
				block[y][x] = blocks[(block_id * BLOCK_HEIGHT) + y][x];
			}
		}
		make_block_flag = 0;
	}
}

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

現在のブロックの種類を表す変数

private int block_id;

を用意しておきます。

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

import java.util.Random;

こちらをインポートした上で

private Random rnd;

乱数のオブジェクトを用意

block_id = rnd.nextInt(発生させたい乱数の範囲);

のように使います。

「rnd.nextInt(10)」なら「0〜9」までの乱数、「rnd.nextInt(50)」なら「0〜49」までの乱数が発生します。

block_id = rnd.nextInt(6);

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

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

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

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

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

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

消去

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

消去する列を探す

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

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

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

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

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

揃った列の場所を入れる為の変数

private int clear_line_point[] = new int[STAGE_HEIGHT - 3];

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

private int clear_flag;

を用意します。

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

まずは「clear_line_point」を初期化

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

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

for(int i=0;i<STAGE_HEIGHT - 3;i++){
	for(int 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(int 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()」によりブロック座標等を元に戻しておりましたが今回からクリア処理が入るので少し違った分岐をします。

広告

クリア状態

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

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

なのでゲームループにて通常時とクリア状態時の処理を分岐します。

@Override
public void handle(long time){
	gc.clearRect(0,0,640,480);
	key.calc_key_count();

	switch(game_state){
	case 0:
		game_state = 5;
		break;
	case 5:
		if(clear_flag == 0){
			my_make_block();
			my_gameover();
			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;
	}
}

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

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

クリアして表示

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

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

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

void my_clear_line(){
	int remain_line_point[] = new int[20];
	int remain_line_index = 0;

	if(clear_count < 12){
		for(int 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(int 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(int 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(int i=STAGE_HEIGHT - 4;i >= 0;i--){
	for(int 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」に保存します。

for(int 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」に保存しなおしているような作業をしております。

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

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

remain_line_index = 0;
for(int i=STAGE_HEIGHT - 4;i >= 0;i--){
	for(int 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」の内側の下の方から入れ替えていけば入れ替え処理は完了です。

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

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

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

画像(jb_6_1)

クリア!!!

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

中間ソース5

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

次回

最終日 ブロック回転

□ページの先頭へ□

□目次へ戻る□

□HOME□

広告

↓2016年06月25日発売↓

新・明解Java入門 (明解シリーズ)

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

↓2017年04月18日発売↓

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

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

↓2017年05月17日発売↓

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

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

↓2017年04月04日発売↓

Java 第2版 入門編 ゼロからはじめるプログラミング (プログラミング学習シリーズ)

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