ブロック崩し

20140302a

日経ソフトウェア2014年4月号「HTML5でゲームを作ろう(第5回)」に掲載された「ブロック崩し」です。

プログラムソースは、表示エリアに関係する部分のみ変更してみました。

「横幅640×高さ700」を「横幅300×高さ400」に変更し、タブレットとスマートフォンで操作できるようにしたが、PCでは小ぢんまりした画面に・・・。

背景画像のグラデーション設定部分は変数(定数)を参照するように変更しました。

PCの場合は、Enterキーを押すか、マウスクリックで開始します。

バーは移動し続けるので、矢印カーソルキー(左右)かマウスクリックで、移動方向を変えて、ボールを打ち返します。

タブレットやスマートフォンの場合は、タップで開始し、本体を左右に傾けることでバーを動かします。

加速度センサーがオフになっている場合は、タップ操作で移動方向を変えることができるはずです。

ソースはこちら


<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=300,user-scalable=no">
		<title>ブロック崩し</title>
		<style>
			* {
				margin:0;
				padding:0;
			}
		</style>
		<script type="text/javascript" src="block.js"></script>
	</head>
	<body style="text-align:center;background-color:black;">
		<canvas id="aCanvas" width="300" height="400">
		</canvas>
	</body>
</html>

// block.js
// 変数の初期化(定数の宣言)
var CANVAS_W = 300, CANVAS_H = 400;		// サイズの指定
var STAGE_COL = 8, STAGE_ROW = 20;		// ブロックの個数
var BLOCK_W = CANVAS_W / STAGE_COL;		// ブロックの横幅
var BLOCK_H = CANVAS_H / STAGE_ROW;		// ブロックの高さ
var PAD_W = BLOCK_W / 20;				// ブロックの余白
var BALL_W = BLOCK_H / 4;				// ボールのサイズ
var BALL_SPEED = BALL_W * 2.0;			// ボールの移動量
var BAR_SPEED = BALL_W * 2.5;			// バーの移動量
var BAR_W = BLOCK_W * 2;				// バーの横幅
var BAR_Y = CANVAS_H - BLOCK_H * 2;		// バーのy座標の位置
var INTERVAL = 100;		// ボールを移動するタイマーの間隔
var STAGE_D = [		// ステージデータ
	[0,0,0,0,0,0,0,0],
	[0,1,0,0,0,0,1,0],
	[0,0,1,0,0,1,0,0],
	[0,0,1,1,1,1,0,0],
	[0,1,2,1,1,2,1,0],
	[0,1,2,1,1,2,1,0],
	[0,1,1,1,1,1,1,0],
	[0,1,1,2,2,1,1,0],
	[0,1,1,1,1,1,1,0],
	[0,0,1,0,0,1,0,0],
	[0,1,1,0,0,1,1,0]
];
// ブロックの色
var BCOLOR = ["#000000","#EE00FF","#FF0000"];
// ゲーム中で利用する変数の宣言
var aCanvas, ctx;	// 描画用オブジェクト
var stage = [];		// ステージデータ
var barX;			// バーのx座標
var barSX;			// バーの移動方向
var ballX, ballY;	// ボールの座標
var ballSX, ballSY;	// ボールの移動方向
var score;		// スコア
var bgstyle;	// 背景のグラデーション描画用
var isPlaying = false;	// ゲーム中かどうかを表す
// 初期化イベント
window.onload = function() {
	// 描画用コンテキストの取得
	aCanvas = $("aCanvas");
	ctx = aCanvas.getContext("2d");
	// グラデーションの背景を生成
	bgstyle = ctx.createLinearGradient(0,0,CANVAS_W,0);
	bgstyle.addColorStop(0, 'black');
	bgstyle.addColorStop(0.5, '#303030');
	bgstyle.addColorStop(1, 'black');
	// マウスイベントを設定
	aCanvas.onclick = checkStart;
	aCanvas.onmousedown = mouseHandler;
	aCanvas.ontouchstart = touchHandler;
	// 加速度センサーイベントを設定
	window.ondevicemotion = motionHandler;
	// キーボードイベントを設定
	window.onkeydown = keyHandler;
	// ゲームデータの初期化を実行
	initGame();
};
// ゲームデータの初期化
function initGame() {
	// ステージデータを初期化
	stage = [];
	for (var y = 0; y < STAGE_ROW; y++) {
		var a = stage[y] = [];
		for (var x = 0; x < STAGE_COL; x++) {
			a[x] = (STAGE_D.length > y) ? STAGE_D[y][x] : 0;
		}
	}
	// バーやボールなどの座標を初期化
	barX = (CANVAS_W - BAR_W) / 2;
	barSX = BAR_SPEED;
	ballX = (CANVAS_W - BALL_W) / 2;
	ballY = BAR_Y - BALL_W * 3;
	ballSX = BALL_SPEED;
	ballSY = BALL_SPEED;
	score = 0;
	drawStage();
}
// ターンを進める
function nextTurn() {
	if (!isPlaying) return;
	// バーを動かす
	barX += barSX;
	if (barX < 0) barX = 0;
	if (barX > (CANVAS_W - BAR_W )) {
		barX = CANVAS_W - BAR_W;
	}
	// ボールを動かす
	var lw = CANVAS_W - PAD_W;
	var tx = ballX + ballSX;
	var ty = ballY + ballSY;
	// 壁があるか
	if (tx < PAD_W || tx > lw) {
		ballSX = BALL_SPEED;
		ballSX *= (tx < PAD_W) ? 1 : -1;
		tx = ballX + ballSX * 2;
	}
	if (ty < PAD_W) {
		ballSY *= -1;
		ty = ballY + ballSY;
	}
	// バーがあるか
	var barX2 = barX + BAR_W;
	if (barX <= ballX && ballX <= barX2 && ballY > (BAR_Y - BALL_W * 2)) {
		ballSY *= -1;
		ty = ballY + ballSY * 2;
	}
	// ブロックがあるか
	var bx = Math.floor(tx / BLOCK_W);
	var by = Math.floor(ty / BLOCK_H);
	var c = stage[by][bx];
	if (c > 0) {
		revBallSX();
		ballSY *= -1;
		stage[by][bx] = 0;	// ブロックを崩す
		score++;
		if (checkClear()) {		// クリア判定
			drawStage();
			alert("GAME CLEAR!");
			isPlaying = false;
			initGame();
			return;
		}
	}
	ballX = tx; ballY = ty;
	drawStage();
	// ゲームオーバー判定
	if (ty >= CANVAS_H - BLOCK_H) {
		alert("GAME OVER!\nSCORE="+score);
		initGame();
		isPlaying = false;
		return;
	}
	// 次のターンをセット
	setTimeout(nextTurn, INTERVAL);
}
//ボールの向きをランダムに変える
function revBallSX() {
	var x = Math.random() * BALL_SPEED * 1.2;
	ballSX = x * ((ballSX < 0) ? 1 : -1);
}
// 画面の描画
function drawStage() {
	// 画面を初期化
	ctx.fillStyle = bgstyle;
	ctx.fillRect(0,0,CANVAS_W,CANVAS_H);
	// 画面の縁を描画
	ctx.fillStyle = "blue";
	ctx.fillRect(0,0,CANVAS_W,PAD_W);
	ctx.fillRect(0,0,PAD_W,CANVAS_H);
	ctx.fillRect(CANVAS_W - PAD_W,0,PAD_W,CANVAS_H);
	// ブロックを描画
	each2a(stage, function(x, y, c) {
			if (c <= 0) return true;
			var rx = x * BLOCK_W + PAD_W;
			var ry = y * BLOCK_H + PAD_W;
			ctx.fillStyle = BCOLOR;
			ctx.fillRect(rx, ry, BLOCK_W - PAD_W * 2, BLOCK_H - PAD_W * 2);
			return true;
	});
	// ボールを描画
	ctx.beginPath();
	ctx.fillStyle = "white";
	ctx.arc(ballX, ballY, BALL_W, 0, Math.PI * 2, false);
	ctx.fill();
	// バーを描画
	ctx.fillStyle = "red";
	ctx.fillRect(barX, BAR_Y, BAR_W, 8);
}
// キーボードのキーが押されたとき
function keyHandler(e) {
	switch (e.keyCode) {
	case 37:	// left
		barSX = -1 * BAR_SPEED;
		break;
	case 39:	// right
		barSX = BAR_SPEED;
		break;
	case 32:	// space
	case 13:	// return
		checkStart();
		break;
	default:
		console.log("PUSH:" + e.keyCode);
	}
}
// マウスボタンが押されたとき
function mouseHandler(e) {
	// 座標を得る
	var x = e.clientX;
	var r = e.target.getBoundingClientRect();
	x -= r.left;
	checkMove(x);
}
// スマートフォンで画面がタッチされたとき
function touchHandler(e) {
	// タッチされた座標を得る
	var p = e.touches[0];
	var x = p.clientX;
	var r = e.target.getBoundingClientRect();
	x -= r.left;
	checkMove(x);
}
function checkMove(x) {
	// 現在のバーの中央を調べる
	var cx = barX + BAR_W / 2;
	if (x < cx) {
		barSX = -1 * BAR_SPEED;
	} else {
		barSX = BAR_SPEED;
	}
}
// 加速度センサーからの入力があったとき
function motionHandler(e) {
	var acc = e.accelerationIncludingGravity;
	if (acc.x < 0) {
		barSX = -1 * BAR_SPEED;
	} else {
		barSX = BAR_SPEED;
	}
	// Androidでは逆方向となる
	if (navigator.userAgent.indexOf('Android') > 0) {
		barSX *= -1;
	}
}
// ゲームを開始する
function checkStart() {
	if (isPlaying) return;
	isPlaying = true;
	nextTurn();
}
// クリアしたか確認する
function checkClear() {
	var blocks = 0;;
	each2a(stage, function(x, y, c) {
			if (c > 0) blocks++;
			return true;
	});
	return (blocks == 0);
}
// 2次元配列の各要素に対して関数fを実行する
function each2a(ary, f) {
	for (var y = 0; y < ary.length; y++) {
		var a = ary[y];
		for (var x = 0; x < a.length; x++) {
			if (!f(x, y, a[x])) return false;
		}
	}
	return true;
}
function $(id) {
	return document.getElementById(id);
}