# Breakout-like game in JS

I've made a small breakout game, so far with one level. My plan is to expand it so that it has a level editor, but for now I wanted to make sure that everything is currently looking well-designed. I don't use js a lot so I'm not always very sure where to put things.

Just a heads up, the collision doesn't work entirely, but that's a side issue that can be fixed in it's own function. If you'd like to give it a try, go for it, but mainly I want to see what you think of the rest of the code and the structure.

The game is already hosted on my website, but since I don't have all the features I want it isn't linked to anywhere but here now.

Since I assume people want the actual code here and not having to inspect for it, here ya go.

var then;
var text;
var canvasW = 600;
var canvasH = 400;
var numX = 30;
var numY = 20;
var blockW = 20;
var blockH = 15;
var paddleY = canvasH - 10;
var ballS = 5;
var ballSpeedX = 0;
var ballSpeedY = maxSpeed;
var ballDirX = 1;
var ballDirY = -1;
var ballAlive = true;
var ballReleased = false;
var gameBoard = new Array(numX);
for(var i = 0; i < numX; i++) {
gameBoard[i] = new Array(numY);
for(var j = 0; j < numY; j++) {
gameBoard[i][j] = 'black';
}
}
var ctx;
var canvas;
var keysDown = {};

$(document).ready(function() { if (Modernizr.canvas) {$('#broken').hide();
initialize();
then = Date.now();
setInterval(main, 1);
}
});

keysDown[e.keyCode] = true;
}, false);

delete keysDown[e.keyCode];
}, false);

function initialize(){
$('#game').removeClass(); canvas =$('<canvas>');
canvas.attr('id', 'canvas');
canvas.attr('width', $('#game').width()); canvas.attr('height',$('#game').height());
canvas.appendTo('#game');
ctx = $('#canvas')[0].getContext("2d"); levelSelect(1); } function main() { var now = Date.now(); var delta = now - then; update(delta / 1000); draw(); then = now; }; function levelSelect(i){ switch(i) { case 1: level1(); break; case 2: level2(); break; } } function level1() { for(var i = 0; i < numX; i++) { gameBoard[i][0] = 'gray'; if(i<numY) { gameBoard[0][i] = 'gray'; gameBoard[numX-1][i] = 'gray'; } } for(var i = 4; i < numX; i++) { for(var j = 2; j < numY; j++) { gameBoard[i-2][j] = 'green'; } } } function update(time) { keys(time); ball(time); } function keys(time) { if ((37 in keysDown)&&(paddleX > 0)) { // Player holding left paddleX -= 256*time; } if ((39 in keysDown)&&(paddleX < canvasW-paddleW)) { // Player holding right paddleX += 256*time; } if (32 in keysDown) { ballReleased = true; } if (32 in keysDown) { text = ""; } } function ball(time) { if(ballAlive) { if(ballReleased) { moveBallY(time); moveBallX(time); checkBlocks(); } else { ballX = paddleX+paddleW/2; ballY = paddleY-2.5; } } else { resetBall(); } } function resetBall() { ballSpeedX = 0; ballSpeedY = maxSpeed; ballDirX = 1; ballDirY = -1; ballAlive = true; ballReleased = false; } function moveBallX(time) { if(checkXWalls(time)) { ballDirX = -ballDirX; } ballX = ballX + ballSpeedX*ballDirX*time; } function moveBallY(time) { if(checkYWalls(time)||checkPaddle(time)) { ballDirY = -ballDirY; } ballY = ballY + ballSpeedY*ballDirY*time; } function checkBlocks(time) { if(contains(ballX, ballY)) { var ballXC = ballX + ballS/2; var ballYC = ballY + ballS/2; var ballLeft = ballXC - ballS/2 - 1; var ballRight = ballXC + ballS/2 + 1; var ballTop = ballYC - ballS/2 - 1; var ballBottom = ballYC + ballS/2 + 1; text = ""; if(contains(ballLeft, ballYC)) { hit(ballLeft, ballYC); text = text + "left"; ballDirX = 1; } if (contains(ballRight, ballYC)) { text = text + "right"; hit(ballRight, ballYC); ballDirX = -1; } if(contains(ballXC, ballTop)) { text = text + "top"; hit(ballXC, ballTop); ballDirY = 1; } else if (contains(ballXC, ballBottom)) { text = text + "bottom"; hit(ballXC, ballBottom); ballDirY = -1; } } } function contains(i, j) { var x = Math.floor(i/blockW); var y = Math.floor(j/blockH); var block = gameBoard[x][y]; return intersects(block); } function intersects(block) { if((block != 'black')&&(typeof block != 'undefined')) { return true; } else { return false; } } function hit(i, j) { var x = Math.floor(i/blockW); var y = Math.floor(j/blockH); if(gameBoard[x][y] != 'gray') { gameBoard[x][y] = 'black'; } } function checkXWalls(time) { var newBallX = ballX + ballSpeedX*ballDirX*time; if((newBallX <= 0)||(newBallX >= canvasW)) { return true; } else { return false; } } function checkYWalls(time) { var newBallY = ballY + ballSpeedY*ballDirY*time; if(newBallY <= 0) { return true; } else if (newBallY >= canvasH) { ballAlive = false; return false; } return false; } function checkPaddle(time) { var newBallX = ballX + ballSpeedX*ballDirX*time; var newBallY = ballY + ballSpeedY*ballDirY*time; if((newBallX >= paddleX) && (newBallX <= paddleX + paddleW) && (newBallY >= paddleY) && (newBallY <= paddleY + paddleH)) { ballSpeedX = (ballX - (paddleX + paddleW/2))*10; if(ballSpeedX<0) { ballSpeedX = -ballSpeedX; ballDirX = -1; } else { ballDirX = 1; } ballSpeedY = maxSpeed - ballSpeedX; return true; } return false; } function draw() { if(ctx) { drawBackground(); drawBoard(); if(ballAlive) { drawBall(); } drawPaddle(); ctx.fillStyle = "blue"; ctx.font = "bold 16px Arial"; ctx.fillText(text, 100, 100); } } function drawBackground() { ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvasW, 400); } function drawBoard() { for(var i = 0; i < numX; i++) { for(var j = 0; j < numY; j++) { if(gameBoard[i][j] != 'black') { ctx.fillStyle = gameBoard[i][j]; ctx.strokeStyle = 'blue'; ctx.fillRect(i*blockW,j*blockH, blockW, blockH); ctx.strokeRect(i*blockW,j*blockH, blockW, blockH); } } } } function drawPaddle() { ctx.fillStyle = 'blue'; ctx.fillRect(paddleX, paddleY, paddleW, paddleH); } function drawBall() { ctx.beginPath(); ctx.arc(ballX, ballY, ballS/2, 0, 2 * Math.PI, false); ctx.fillStyle = 'white'; ctx.fill(); }  - Do you know about requestAnimationFrame? I haven't used it yet but would probably try it if I was making a new game. That link also notes that max frame rate is usually 60, so setInterval with an interval of less than 17 might not make sense. – Stuart Jan 19 '13 at 19:57 by setting it to 1 it just does it as fast as possible, the time variable keeps track of how fast it happens and the code works off of that. it probably wouldnt be good to do in a more complex game, but since it's 1P breakout it doesnt really matter. requestAnimationFrame actually looks pretty cool, I'll probably use that and polyfill for the next project i do – TMP Jan 20 '13 at 2:06 ## 1 Answer You might want to put all the canvas stuff into an object or class. function Canvas() { var el =$('<canvas>'),
ctx;
this.width = 600;
this.height = 400;
el.attr('id', 'canvas');
el.attr('width', $('#game').width()); el.attr('height',$('#game').height());
el.appendTo('#game');
ctx = \$('#canvas')[0].getContext("2d");
this.draw = function () {
if (ctx) {
drawBackground();
drawBoard();
if (ballAlive) {
drawBall();
}
ctx.fillStyle = "blue";
ctx.font = "bold 16px Arial";
ctx.fillText(text, 100, 100);
}
}

function drawBackground() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvasW, 400);
}

function drawBoard() {
for (var i = 0; i < numX; i++) {
for (var j = 0; j < numY; j++) {
if (gameBoard[i][j] != 'black') {
ctx.fillStyle = gameBoard[i][j];
ctx.strokeStyle = 'blue';
ctx.fillRect(i * blockW, j * blockH, blockW, blockH);
ctx.strokeRect(i * blockW, j * blockH, blockW, blockH);
}
}
}
}

ctx.fillStyle = 'blue';
}

function drawBall() {
ctx.beginPath();
ctx.arc(ballX, ballY, ballS / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
}
}


This keeps all the drawing code separate from the game logic, which will simplify the other functions, and allows you to have variables storing state of the drawn objects if necessary, without having to make more global variables.

In the initialize function you then just need canvas = new Canvas(), and you can access canvas.width, canvas.height and canvas.draw() as needed.

In general as the game increases in complexity it is likely to be useful to group functions and variables that are currently all in the global scope, into objects. Ball and paddle are obvious candidates. e.g. paddleX -= 256*time could get replaced with paddle.move('left', time).

-
thanks, I like how you organized the canvas a lot. I at first tried to use them as separate functions because I didn't want to pass them around, but this solves that problem. thanks for all the help –  TMP Jan 20 '13 at 2:11