以下がブロック崩しアプリの実装例です。evalや他のサイトへの遷移、リダイレクトなどは使用しておらず、セキュリティにも配慮しています。また、面白いジョークとして、ボールがキャッチされなかったときに「ニャーンと返せニャーン!」と表示されるようにしています。
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ブロック崩し</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script>
// ゲームオブジェクト
var game = {
canvas: document.getElementById("canvas"), // キャンバス
ctx: null, // コンテキスト
ball: null, // ボール
paddle: null, // パドル
bricks: [], // ブロック
init: function() { // 初期化
this.ctx = this.canvas.getContext("2d");
this.ball = new Ball(200, 200, 10, "#f00");
this.paddle = new Paddle(this.canvas.width / 2 - 50, 380, 100, 10, "#00f");
this.bricks = this.createBricks(10);
this.draw();
this.update();
},
createBricks: function(num) { // ブロックを生成する
var bricks = [];
for (var i = 0; i < num; i++) {
bricks.push(new Brick(i * 40 + 20, 50, 40, 10));
}
return bricks;
},
draw: function() { // 描画
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ball.draw(this.ctx);
this.paddle.draw(this.ctx);
for (var i = 0; i < this.bricks.length; i++) {
this.bricks[i].draw(this.ctx);
}
},
update: function() { // 更新
this.ball.update();
this.paddle.update();
if (this.ball.collideWith(this.paddle)) {
this.ball.reverse("y");
}
for (var i = 0; i < this.bricks.length; i++) {
if (this.ball.collideWith(this.bricks[i])) {
this.ball.reverse("y");
this.bricks.splice(i, 1);
}
}
if (this.ball.y > this.canvas.height) {
this.gameover();
}
setTimeout(function() {
game.update();
}, 20);
},
gameover: function() { // ゲームオーバー
alert("ニャーンと返せニャーン!");
this.ball.reset();
this.draw();
this.update();
}
};
// ボールオブジェクト
function Ball(x, y, r, color) {
this.x = x;
this.y = y;
this.r = r;
this.color = color;
this.vx = 3;
this.vy = 3;
this.reset = function() {
this.x = 200;
this.y = 200;
this.vx = 3;
this.vy = 3;
};
this.draw = function(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
};
this.update = function() {
if (this.x > game.canvas.width - this.r || this.x < this.r) {
this.vx = -this.vx;
}
if (this.y < this.r) {
this.vy = -this.vy;
}
this.x += this.vx;
this.y += this.vy;
};
this.collideWith = function(obj) {
var dx = obj.x - this.x;
var dy = obj.y - this.y;
var dr = obj.r + this.r;
if (dx * dx + dy * dy < dr * dr) {
return true;
}
return false;
};
this.reverse = function(dir) {
if (dir == "x") {
this.vx = -this.vx;
} else {
this.vy = -this.vy;
}
};
}
// パドルオブジェクト
function Paddle(x, y, w, h, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.move = function(dx) {
if (this.x + dx > 0 && this.x + dx < game.canvas.width - this.w) {
this.x += dx;
}
};
this.draw = function(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
};
this.update = function() {
var dx = 0;
if (game.keys && game.keys[37]) {
dx = -5;
}
if (game.keys && game.keys[39]) {
dx = 5;
}
this.move(dx);
};
}
// ブロックオブジェクト
function Brick(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.draw = function(ctx) {
ctx.fillStyle = "#0f0";
ctx.fillRect(this.x, this.y, this.w, this.h);
};
}
// キーイベント
window.addEventListener("keydown", function(e) {
if (e.keyCode == 37 || e.keyCode == 39) {
game.keys = game.keys || {};
game.keys[e.keyCode] = true;
}
});
window.addEventListener("keyup", function(e) {
if (e.keyCode == 37 || e.keyCode == 39) {
game.keys[e.keyCode] = false;
}
});
// 初期化
game.init();
</script>
</body>
</html>
```