Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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 paddleW = 50;
var paddleH = 5;
var paddleX = canvasW/2-paddleW/2;
var paddleY = canvasH - 10;
var ballX = paddleX+paddleW/2;
var ballY = paddleY-2.5;
var ballS = 5;
var maxSpeed = paddleW*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);
    }
});

addEventListener("keydown", function (e) {
    keysDown[e.keyCode] = true;
}, false);

addEventListener("keyup", function (e) {
    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(); 
}
share|improve this question
    
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 1

up vote 2 down vote accepted

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();
      }
      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();
  }
}

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).

share|improve this answer
    
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

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.