I decided to make a Breakout game in JavaScript. Is there a way of making it cleaner? For instance, creating objects for the ball and the paddle.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Brick Game</title>
<style type="text/css">
body {
background-color: black;
}
canvas {
border: 1px solid green;
}
</style>
</head>
<body>
<canvas id="game-canvas" height="600px" width="800px" </canvas>
<script type="text/javascript">
var canvas = document.getElementById("game-canvas");
// Get a 2D context for the canvas.
var ctx = canvas.getContext("2d");
var ballR = 10;
var x = canvas.width / 2;
var y = canvas.height - 30;
var dx = 3;
var dy = -3;
var pongH = 15;
var pongW = 80;
var pongX = (canvas.width - pongW) / 2;
var rightKey = false;
var leftKey = false;
var brickRows = 3;
var brickCol = 9;
var brickW = 75;
var brickH = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var bricks = [];
for (c = 0; c < brickCol; c++) {
bricks[c] = [];
for (r = 0; r < brickRows; r++) {
bricks[c][r] = {
x: 0,
y: 0,
status: 1
};
}
}
// function to draw the ball
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballR, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
// function draw the pong
function drawPong() {
ctx.beginPath();
ctx.rect(pongX, canvas.height - pongH, pongW, pongH);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
// function draw the bricks
function drawBricks() {
for (c = 0; c < brickCol; c++) {
for (r = 0; r < brickRows; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickW + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickH + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickW, brickH);
ctx.fillStyle = "green";
ctx.fill();
ctx.closePath();
}
}
}
}
function collisionDetection() {
for (c = 0; c < brickCol; c++) {
for (r = 0; r < brickRows; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickW && y > b.y && y < b.y + brickH) {
dy = -dy;
b.status = 0;
}
}
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPong();
collisionDetection();
if (x + dx > canvas.width - ballR || x + dx < ballR) {
dx = -dx;
}
if (y + dy < ballR) {
dy = -dy;
} else if (y + dy > canvas.height - ballR) {
if (x > pongX && x < pongX + pongW) {
dy = -dy;
} else {
// if the ball hits the bottom of canvas
// reload the game
document.location.reload();
}
}
// when key is pressed
function keyDown(e) {
if (e.keyCode == 39) {
rightKey = true;
} else if (e.keyCode == 37) {
leftKey = true;
}
}
// when key is not pressed
function keyUp(e) {
if (e.keyCode == 39) {
rightKey = false;
} else if (e.keyCode == 37) {
leftKey = false;
}
}
// Add an event listener to the keypress event.
document.addEventListener("keydown", keyDown, false);
document.addEventListener("keyup", keyUp, false);
// move the pong right if the right key pressed
if (rightKey && pongX < canvas.width - pongW) {
pongX += 7;
}
// move the pong left if the left key pressed
else if (leftKey && pongX > 0) {
pongX -= 7;
}
x += dx;
y += dy;
}
setInterval(draw, 10);
</script>
</body>
</html>
1 Answer 1
First, great job. I loved seeing that the whole game had been created with so little code, and was so easy to understand.
While you could consider making this more object-oriented, I don't think it's necessary, so I did some rewrites in keeping with the simple procedural style of the original.
The biggest place for improvement is in naming your concepts. For example, this:
if (hitSideWall())
dx = -dx;
if (hitTop() || hitPong())
dy = -dy;
if (gameOver())
document.location.reload();
is much clearer than:
if (y + dy < ballR) {
dy = -dy;
} else if (y + dy > canvas.height - ballR) {
if (x > pongX && x < pongX + pongW) {
dy = -dy;
} else {
document.location.reload();
}
}
You can apply this principle over and over, at all levels of your code. See the full rewrite for more examples.
Other notes:
- Avoid nesting wherever possible, and avoid "if... else" statements.
- The nested for loop for the bricks only needs to be done once. Use it once to create a flat array of bricks. Afterward, the nested structure is superfluous, since each brick object contains the info you need. Just loop through the flat array with
forEach
. - When initializing multiple variables, just use one
var
and commas.
Full rewrite:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Brick Game</title>
<style type="text/css">
body { background-color: black; }
canvas { border: 1px solid green; }
</style>
</head>
<body>
<canvas id="game-canvas" height="600px" width="800px" </canvas>
<script type="text/javascript">
var canvas = document.getElementById("game-canvas"),
ctx = canvas.getContext("2d"),
ballR = 10,
x = canvas.width / 2,
y = canvas.height - 30,
dx = 3,
dy = -3,
pongH = 15,
pongW = 80,
pongX = (canvas.width - pongW) / 2,
rightKey = false,
leftKey = false,
brickRows = 3,
brickCol = 9,
brickW = 75,
brickH = 20,
brickPadding = 10,
brickOffsetTop = 30,
brickOffsetLeft = 30;
var bricks = [];
for (c = 0; c < brickCol; c++) {
for (r = 0; r < brickRows; r++) {
bricks.push({
x: (c * (brickW + brickPadding)) + brickOffsetLeft,
y: (r * (brickH + brickPadding)) + brickOffsetTop,
status: 1
});
}
}
// function to draw the ball
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballR, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
// function draw the pong
function drawPong() {
ctx.beginPath();
ctx.rect(pongX, canvas.height - pongH, pongW, pongH);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
// function draw the bricks
function drawBricks() {
bricks.forEach(function(brick) {
if (!brick.status) return;
ctx.beginPath();
ctx.rect(brick.x, brick.y, brickW, brickH);
ctx.fillStyle = "green";
ctx.fill();
ctx.closePath();
});
}
function collisionDetection() {
bricks.forEach(function(b) {
if (!b.status) return;
var inBricksColumn = x > b.x && x < b.x + brickW,
inBricksRow = y > b.y && y < b.y + brickH;
if (inBricksColumn && inBricksRow) {
dy = -dy;
b.status = 0;
}
});
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPong();
collisionDetection();
if (hitSideWall())
dx = -dx;
if (hitTop() || hitPong())
dy = -dy;
if (gameOver())
document.location.reload();
var RIGHT_ARROW = 39,
LEFT_ARROW= 37;
function hitPong() { return hitBottom() && ballOverPong() }
function ballOverPong() { return x > pongX && x < pongX + pongW }
function hitBottom() { return y + dy > canvas.height - ballR }
function gameOver() { return hitBottom() && !ballOverPong() }
function hitSideWall() { return x + dx > canvas.width - ballR || x + dx < ballR }
function hitTop() { return y + dy < ballR }
function xOutOfBounds() { return x + dx > canvas.width - ballR || x + dx < ballR }
function rightPressed(e) { return e.keyCode == RIGHT_ARROW }
function leftPressed(e) { return e.keyCode == LEFT_ARROW }
function keyDown(e) {
rightKey = rightPressed(e);
leftKey = leftPressed(e);
}
function keyUp(e) {
rightKey = rightPressed(e) ? false : rightKey;
leftKey = leftPressed(e) ? false : leftKey;
}
// Add an event listener to the keypress event.
document.addEventListener("keydown", keyDown, false);
document.addEventListener("keyup", keyUp, false);
// move the pong right if the right key pressed
var maxX = canvas.width - pongW,
minX = 0,
pongDelta = rightKey ? 7 : leftKey ? -7 : 0;
pongX = pongX + pongDelta;
pongX = Math.min(pongX, maxX);
pongX = Math.max(pongX, minX);
x += dx;
y += dy;
}
setInterval(draw, 10);
</script>
</body>
</html>