言語村 トップページ






皇帝流
テトリスの作り方
トップページ




皇帝流テトリス





皇帝流テトリスの作り方

 その5へ戻る


その6 ブロックを動かそう



すかじい うおっほん。
今回は、ブロックをボタンで動かしてみよう。
ひろちゃん皇帝 ということは、今回が最終回!?


すかじい
いや、いや、まだまだ追加しなければいけない処理があるので
今回が最後というわけでは無い。
でも、今回が一番のヤマ場であることも確かじゃ。


りな姫 ブロックを動かす部分が一番難しいの?


すかじい
いや、ブロックを動かす処理自体はそう難しいものではない。
むしろ、ブロックを表示する前の準備段階が非常にややこしいのじゃ。
この準備がしっかり行われていれば、後はそれを利用するだけなのじゃ。

最初のころ話したと思うが、表示されている画面というのは、
いろいろな処理が行われた後の、最後の結果が表示されているんじゃ。


ひろちゃん皇帝
すかじい、ここがいまいち解らないんだけど、どういうこと?


すかじい
そうじゃな。図で説明しよう。

説明の図

どうじゃ。解るかな。


ひろちゃん皇帝
ブロックを置いた後に、なんでまた消しちゃうの?


すかじい
動かす前に一度ブロックを消しておかないと、
次にブロックが動いた時、前の位置には
前のブロックが表示されたままになってしまうぞ。

「ブロックを消す処理」を実際に表示している画面で行うと、
ブロックが消えてしまった状態が表示されてしまうことになる。
こうなるとブロックが点滅しているように見えてしまうんじゃ。


リナ姫
そのために、「仮の画面」を作っているのね。


すかじい
そうじゃ。
あと、ブロックが動かせるかどうかの判断を行いやすいのもある。
実際の画面で行うと、どれが動かせるブロックで
どれが動かせないブロックかの判断が難しいじゃろ。
つまり、仮の画面を作る理由は2番と5番の処理を
簡単に行う為なんじゃよ。

とにかく、今回のソースを見てみようかの。
前回と同じく、JavaScript部分のみ載せているぞい。


<script language="JavaScript">
<!--

// 使用する変数の宣言 --------------------------------------------------------

x = 4; y = -2;	// X座標とY座標(一番下のブロックが基準)
cr = "\r\n"
mojiretu= "";	// 画面に表示する文字列取得用
width = 8;	// 画面の広さ
height = 20;	// 画面の高さ
blockNo = 0;	// ブロックの種類保存用
blockNo2 = 0;	// 次のブロックの種類保存用
blockX = new Array( 4 );	// 動かすブロックの位置取得用配列変数
blockY = new Array( 4 );	// 0上 1右 2左 3下 を判断する
blockWX = new Array( 4 );	// 動かすブロック位置チェック用ワーク配列変数
blockWY = new Array( 4 );
kaiten = 0;		// ブロックの回転状態
gameflag = false;	// ゲーム中かどうかのフラグ
mode = 0;		// 現在のゲームの状態チェック用変数
speed = 1000;		// 次の処理へ進むスピード
score = 0;		// スコア
level = 1;		// レベル

// 仮想VRAM用2次配列
vr = new Array( width );
for ( i = 0; i < width; i++ ) {
	vr[i] = new Array( height );
}


// ブロックの形を仮想的に作る関数
function MakeWBlock() {
	switch ( blockNo ) {
		case 0:		// 棒
			switch ( kaiten ) {
				case 0:
				case 2:
					blockWX[0] = x;
					blockWY[0] = y - 3;
					blockWX[1] = x;
					blockWY[1] = y - 2;
					blockWX[2] = x;
					blockWY[2] = y - 1;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 1:
				case 3:
					blockWX[0] = x;
					blockWY[0] = y - 2;
					blockWX[1] = x + 1;
					blockWY[1] = y - 2;
					blockWX[2] = x - 2;
					blockWY[2] = y - 2;
					blockWX[3] = x - 1;
					blockWY[3] = y - 2;
					break;
			}
			break;
		
		case 1:		// Lその1
			switch ( kaiten ) {
				case 0:
					blockWX[0] = x - 1;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 1:
					blockWX[0] = x - 1;
					blockWY[0] = y - 1;
					blockWX[1] = x;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x - 1;
					blockWY[3] = y + 1;
					break;
				case 2:
					blockWX[0] = x - 1;
					blockWY[0] = y - 1;
					blockWX[1] = x;
					blockWY[1] = y - 1;
					blockWX[2] = x - 2;
					blockWY[2] = y - 1;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 3:
					blockWX[0] = x;
					blockWY[0] = y - 2;
					blockWX[1] = x;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
			}
			break;
		case 2:		// Lその2
			switch ( kaiten ) {
				case 0:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x;
					blockWY[1] = y;
					blockWX[2] = x - 2;
					blockWY[2] = y;
					blockWX[3] = x - 1;
					blockWY[3] = y;
					break;
				case 1:
					blockWX[0] = x - 1;
					blockWY[0] = y - 2;
					blockWX[1] = x;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x - 1;
					blockWY[3] = y;
					break;
				case 2:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x - 1;
					blockWY[3] = y;
					break;
				case 3:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x;
					blockWY[3] = y + 1;
					break;
			}
			break;
		case 3:		// クランクその1
			switch ( kaiten ) {
				case 0:
				case 2:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 1:
				case 3:
					blockWX[0] = x;
					blockWY[0] = y - 2;
					blockWX[1] = x;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x - 1;
					blockWY[3] = y;
					break;
			}
			break;
		case 4:		// クランクその2
			switch ( kaiten ) {
				case 0:
				case 2:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 1:
				case 3:
					blockWX[0] = x - 1;
					blockWY[0] = y - 2;
					blockWX[1] = x;
					blockWY[1] = y - 1;
					blockWX[2] = x - 1;
					blockWY[2] = y - 1;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
			}
			break;
		case 5:		// 凸
			switch ( kaiten ) {
				case 0:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y;
					break;
				case 1:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x + 1;
					blockWY[1] = y;
					blockWX[2] = x;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y + 1;
					break;
				case 2:
					blockWX[0] = x;
					blockWY[0] = y;
					blockWX[1] = x + 1;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y + 1;
					break;
				case 3:
					blockWX[0] = x;
					blockWY[0] = y - 1;
					blockWX[1] = x;
					blockWY[1] = y;
					blockWX[2] = x - 1;
					blockWY[2] = y;
					blockWX[3] = x;
					blockWY[3] = y + 1;
					break;
			}
			break;
		case 6:		// 四角
			blockWX[0] = x - 1;
			blockWY[0] = y - 1;
			blockWX[1] = x;
			blockWY[1] = y - 1;
			blockWX[2] = x - 1;
			blockWY[2] = y;
			blockWX[3] = x;
			blockWY[3] = y;
			break;
	}
}


// 次に落ちるブロックを表示する関数-------------------------------------------------
function MakeNextBlock( number ) {
	switch ( number ) {
		case 0:
			mojiretu =	"□□■□" + cr +
				  	"□□■□" + cr +
				  	"□□■□" + cr +
				  	"□□■□";
			break;
		case 1:
			mojiretu =	"□□□□" + cr +
					"□□■□" + cr +
					"□□■□" + cr +
					"□■■□";
			break;
		case 2:
			mojiretu =	"□□□□" + cr +
					"□■□□" + cr +
					"□■□□" + cr +
					"□■■□";
			break;
		case 3:
			mojiretu =	"□□□□" + cr +
					"□■■□" + cr +
					"□□■■" + cr +
					"□□□□";
			break;
		case 4:
			mojiretu =	"□□□□" + cr +
					"□■■□" + cr +
					"■■□□" + cr +
					"□□□□";
			break;
		case 5:
			mojiretu =	"□□□□" + cr +
					"□□■□" + cr +
					"□■■■" + cr +
					"□□□□";
			break;
		case 6:
			mojiretu =	"□□□□" + cr +
					"□■■□" + cr +
					"□■■□" + cr +
					"□□□□";
			break;
	}
	document.game.tugi.value = mojiretu;
}



// 仮想ブロックからブロックを作る関数
function MakeBlock() {
	for ( i = 0; i < 4; i++ ) {
		blockX[i] = blockWX[i];
		blockY[i] = blockWY[i];
	}
}


// 初期化用関数 -------------------------------------------------------------------
function Syokika() {
	
	// 変数の初期化
	gameflag = false;
	mode = 0;
	score = 0;
	level = 1;
	speed = 1000;
	
	for ( i = 0; i < height; i++ ) {
		for ( j = 0; j < width; j++ ) {
			vr[j][i] = "□";	// 仮想画面クリア
		}
	}
	// 仮想画面を表示する
	Hyouji();
	
	// 次画面をクリアする
	mojiretu =	"□□□□" + cr +
			"□□□□" + cr +
			"□□□□" + cr +
			"□□□□";
	document.game.tugi.value = mojiretu;
	
	// 最初に落ちるブロックを決める
	blockNo2 = Math.floor( Math.random() * 7 );
}


// 画面に書き込む関数 --------------------------------------------------------------
function Kakikomi() {
	// 仮想画面に現在動かすブロックを書き込む
	for ( i = 0; i < 4; i++ ) {
		if ( blockX[i] >= 0 && blockY[i] >= 0 ) {
			vr[blockX[i]][blockY[i]] = "■";
		}
	}
	// 仮想画面を表示する
	Hyouji();
	
	// 仮想画面から現在動かしているブロックを消去
	for ( i = 0; i < 4; i++ ) {
		if ( blockX[i] >= 0 && blockY[i] >= 0 ) {
			vr[blockX[i]][blockY[i]] = "□";
		}
	}
}


// 仮想画面を表示する関数
function Hyouji() {
	// 画面に表示する文字列の作成
	mojiretu = "";
	for ( i = 0; i < height; i++) {
		for ( j = 0; j < width; j++ ) {
			mojiretu = mojiretu + vr[j][i];
		}
		if ( i != height - 1 ) {
			mojiretu = mojiretu + cr;
		}
	}
	// 画面の表示
	document.game.gamen.value = mojiretu;
	document.game.SCORE.value = score;
	document.game.LEVEL.value = level;
}


// 各ボタンに対する関数
// 回転
function Moveturn() {
	// ゲームが始まっていない時は、ゲームを始める
	if ( !( gameflag ) ) {
		gameflag = true;
		Main();
	} else {
		// ゲーム中のボタンの動き
		if ( kaiten < 3 ) {
			kaiten++;
		} else {
			kaiten = 0;
		}
		MakeWBlock();
		MakeBlock();
		Kakikomi();
	}
}
// 左
function Moveleft() {
	// ゲームが始まっていない時は、ゲームを始める
	if ( !( gameflag ) ) {
		gameflag = true;
		Main();
	} else {
		// ゲーム中のボタンの動き
		x--;
		MakeWBlock();
		MakeBlock();
		Kakikomi();
	}
}
// 右
function Moveright() {
	// ゲームが始まっていない時は、ゲームを始める
	if ( !( gameflag ) ) {
		gameflag = true;
		Main();
	} else {
		// ゲーム中のボタンの動き
		x++;
		MakeWBlock();
		MakeBlock();
		Kakikomi();
	}
}
// 下
function Movedown() {
	// ゲームが始まっていない時は、ゲームを始める
	if ( !( gameflag ) ) {
		gameflag = true;
		Main();
	} else {
		// ゲーム中のボタンの動き
		y++;
		MakeWBlock();
		MakeBlock();
		Kakikomi();
	}
}



// メインルーチン
function Main() {
	// ゲームが始まっている時のみ処理をする
	if ( gameflag ) {
		// ゲームの状態によって、処理を変える
		switch ( mode ) {
			case 0:
				// 落下するブロックと、次落下するブロックを設定
				blockNo = blockNo2;
				blockNo2 = Math.floor( Math.random() * 7 );
				kaiten = 0;
				x = 4;
				y = 0;
				// 次落下するブロックを描く
				MakeNextBlock( blockNo2 );
				// 仮想ブロックの作成
				MakeWBlock();
				MakeBlock();	// ブロックを作成
				Kakikomi();	// ブロックの表示
				mode = 1;	// ブロックが落ちるモードに変更
				break;
				
			case 1:
				mode = 1;	//今回の仮の処理
				break;
		}
	}
	window.setTimeout( "Main()", speed );
}


// -->
</script>


ひろちゃん皇帝
うわぁ、なんだか難しそうだなあ・・・・。


すかじい
そうじゃな。少しずつ見ていこう。

まず、ブロックの座標用の変数、x と y があるな。
このブロックのX、Y座標を中心に、他のブロックが左右にいくつ、
上下にいくつ離れているか、を設定してブロックを作っておる。

それを行っているのが MakeWBlock() じゃ。

ひろちゃん皇帝
う〜ん、何をやっているのかよく解らないなあ。


すかじい
一番最初の blockWX[ ] blockWY[ ] 配列に何が入っているか
見てみよう。

ここは、棒型のブロックの縦の形を作る為の
4つのブロックの位置が入っておる。
棒型の一番下をx,y座標とすると、

■←( x, y - 3 )
■←( x, y - 2 )
■←( x, y - 1 )
■←( x, y )

となる。
その次が棒型の横の状態じゃ。

□□□□
■■■■
□□□□
□□□←ここが( x, y )になる。

これを座標で考えてみると、
( x - 2, y - 2 ) ( x - 1, y - 2 ) ( x, y - 2 ) ( x + 1, y - 2 )
という座標が必要なのがわかるじゃろ。
この座標がすべて blockWX[ ] blockWY[ ] 配列に入っておるな。


リナ姫
なんとなく解ったけど、配列に入れる順番がバラバラなのはどうしてなの?
実際には
( x, y - 2 ) ( x + 1, y - 2 ) ( x - 2, y - 2 ) ( x - 1, y - 2 )
の順番で入っているわよ?


すかじい
実は blockWX[ ] blockWY[ ] にはもう一つ重要な役割があってな、
今回は出てこないが、上下左右の位置確認用にも使っておる。
最初の方の配列を宣言している部分で

blockY = new Array( 4 );  // 0上 1右 2左 3下 を判断する

となっているコメント部分がそうじゃ。
テトリスのブロックは都合よく4つのブロックからできているので
ブロック一つが上下左右のチェックのうち一つを担当しているんじゃ。
blockWX[0],blockWY[0]は上チェックを行えるブロックが入っており、
blockWX[1],blockWY[1]は右チェックを行えるブロックが入っておる。
詳しくは、次回実際に処理を行っている所で説明しよう。

さて、棒型の次はどのような形が入っているか解るかね?


リナ姫
コメントを見ると、「L」型のようね。

ひろちゃん皇帝
4つの座標が
( x - 1, y - 1 ) ( x + 1, y ) ( x - 1, y ) ( x, y )
だから
( x - 1, y - 1 )
( x - 1, y ) ( x, y ) ( x + 1, y )
と考えると解りやすいな。
つまり、
■□□
■■■
□↑ここが( x, y )

ということなんだ!


すかじい
そのとおりじゃ。それを頭に置いた上で、ソースを読み解いていくぞい。

メインルーチンを見てみよう
前回、「次のブロック」を描く所までは終わったな。
今回、「次のブロック」を描く前に、移動するブロックの「回転」と「座標」を
初期化しておるのは解るな。


リナ姫
x = 4;
y = 0;
って所ね。
0から数えると、左から5つ目、上から1つ目がxとyの最初の位置ってわけね。


すかじい
そのとおりじゃ。
さて、落下するブロックが決まった次の処理じゃが、
MakeWBlock()、MakeBlock()、Kakikomi()、の3つの関数を立て続けに呼んでおる。


ひろちゃん皇帝
MakeWBlock()って、さっきの
blockWX[ ] blockWY[ ] 配列にブロックの形を設定しているやつだね。

switch ( blockNo )ってなっているなあ。
blockNoは前回出てきた変数だね。ブロックの形の番号が入っているんだよね。
その後に、switch ( kaiten ) がある。
ということは、blockNo とkaiten の値によって、ブロックの形を設定しているんだ!


すかじい
そうじゃ。
「回転」の処理も、実際には回転させているのではなくて、
「回転した状態」の別のブロックに切り替えているだけ、というのが解るじゃろう。

次の MakeBlock()では、blockWX[ ] blockWY[ ] 配列の値を blockX[ ] blockY[ ] 配列に
コピーしておる。
blockWX[ ] blockWY[ ] 配列には、今後、ブロックが移動できるかどうかの
チェックに使用しなくてはいけないので、
今の情報をブロック表示用の配列に書き写しておるのじゃ。

次回詳しく説明するが、この段階でのブロックは、
画面に表示できる位置が入った状態のブロック、
というわけじゃ。

次の Kakikomi()は何をやっておるか解るかな?


ひろちゃん皇帝
う〜ん、仮想画面用の配列に何かしているんだろうけど、よく解らないなあ・・・。


リナ姫
blockX[ ] blockY[ ] 配列には、ブロックの位置が入っているのでしょ?
vr[blockX[i]][blockY[i]] = "■";
↑ここの所がややこしそうに見えるけれども、
仮にblockX[0]=4、blockY[0]=0、だった場合
vr[4][0] = "■";
ってことなんでしょ?


ひろちゃん皇帝
そっか!vr[ ][ ]二次元配列のブロックのある位置に、ブロックを書き込んでいるんだ!

でも、その前の
if ( blockX[i] >= 0 && blockY[i] >= 0 ) {
は、何の条件なんだろう?


すかじい
テトリスのブロックが落ちてくる時は、画面からはみ出しているじゃろ。
blockY[i]がマイナスの値の時は、ブロックが上にはみ出している時なんじゃ。
でも、vr[ ][ ]配列には表示できるブロックしか格納できないので
はみ出したブロックが入らないように、ここでチェックしておるんじゃ。
vr[ ][ ]配列の要素にマイナスが入ると、エラーになってしまうでのう。

ちなみに、blockX[i]がマイナスになることは無いのだが、念のためチェックしておる。
テトリスのブロックが、左右にはみ出すことは無い、ということじゃな。

リナ姫
でも、ここの処理、
ブロックをvr[ ][ ]配列のブロックの位置に「■」を書き込むのだけど、
その後すぐに「□」に書き換えているわね・・・。

あ、その間に Hyouji()が呼ばれているわ!


すかじい
Hyouji()は、初期化処理の所で出てきたのだが、
覚えておるかな?


ひろちゃん皇帝
確か、vr[ ][ ]配列の中身をテキストエリアに表示するんだよね。


すかじい
そうじゃ。
ここまでが、「次表示するブロック」が実際に「動かすブロック」になり
画面に表示されるまでの流れじゃ。
さあ、あともう少しじゃぞ。

今回は、ボタンを押した時の処理にも追加部分があるが、
何を行っているか解るかな?


ひろちゃん皇帝
今までのボタンの動きは、「ゲームが始まっていない時」に
ゲームを開始する動きだったから、ゲーム中にボタンを押した時なのかな?
ということは、ブロックを動かす処理なのかな?


リナ姫
そのようね。Moveturn()で、回転用変数 kaiten を変化させているし、
Moveleft()、Moveright()、Movedown()、では x か y の値が変化しているわ。


ひろちゃん皇帝
そっか!ボタンを押した時の kaiten や x、y の変化が
ブロックの場所や形を変化させて、
最終的に vr[ ][ ]配列に書き込まれてテキストエリアに出力されるんだ!


すかじい
ふぉっふぉっふぉ。どうやら今までの処理の流れが1本になったようじゃの。

最後にもう一つ問題じゃが、ボタンを押した時にも、メインルーチンで呼んでいた
MakeWBlock()、MakeBlock()、Kakikomi()、の3つの関数を呼んでいるが、
なぜメインルーチンで呼ぶだけではダメなのか解るかの?


ひろちゃん皇帝
う〜ん、どちらも同じ順番で同じ処理をやっているんだよね?
何が違うんだろう???


すかじい
難しいかな。では、ヒントを出そう。
メインルーチンは、speed変数の値の間隔でしか処理されないのじゃったな。
一秒間に一回、とか。


ひろちゃん皇帝
それだったら、ボタンを押してもすぐにはブロックが動かないってことか。
そっか!たとえば、メインルーチンが1回処理するまでに右ボタンを2回押したら、
ブロックが2回動くようにするためなんだ!


すかじい
そのとおりじゃ。
今回のソースを実行してみると、ボタンクリックでブロックが動くようになるぞ。



Next


Level
以下、作成中・・・・