Breakout lab sample solution

Breakout.oop.html  [download]

<!DOCTYPE html>

<html>
<head>

<title>My OOP version of Breakout</title>
<!-- Based on: https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript -->

<style>
canvas { background: #eee; }
</style>

<script type="text/javascript">
"use strict";

// Globals
var canvas, ctx
var ball, paddle, rules
var bricks = []

/*
 **************************************************
 * Ball class
 **************************************************
 */
class Ball {
	constructor () {
		// Ivars
		this._radius = 10;
		this._x = canvas.width/2;
		this._y = canvas.height-30;
		this._dx = 2;
		this._dy = -2;
	}

	// Full pedantic style with private ivars
	getX() { return this._x; }
	getY() { return this._y; }

	draw () {
		ctx.beginPath();
		ctx.arc(this._x, this._y, this._radius, 0, Math.PI*2);
		ctx.fillStyle = "#0095DD";
		ctx.fill();
		ctx.closePath();
	}

	update () {
		// Normal update
		this._x += this._dx;
		this._y += this._dy;

		// U-turn ball at sides
		if (this._x + this._dx > canvas.width-this._radius || this._x + this._dx < this._radius) {
			this._dx = -this._dx;
		}

		// U-turn ball at top
		if (this._y + this._dy < this._radius) {
			this._dy = -this._dy;
		}

		// Ball hit bottom of screen
		if (this._y + this._dy > canvas.height-this._radius) {
			if (paddle.collide (this)) {
				// Hit paddle = turn around
				this._dy = -this._dy;
			}
			else {
				// Falls off screen = lose
				rules.loseLife()
			}
		}
	}

	reverse () {
		this._dy = -this._dy;
	}

	nextLife() {
		// Ball resets and speeds up
		this._x = canvas.width/2;
		this._y = canvas.height-30;
		this._dx = 3;
		this._dy = -3;
	}
}

/*
 **************************************************
 * Brick class
 **************************************************
 */
class Brick {
	constructor (row, col) {
		this._x = (row * (Brick.width+Brick.padding)) + Brick.offsetLeft;
		this._y = (col * (Brick.height+Brick.padding)) + Brick.offsetTop;
		this._status = true
	}

	remove () {
		this._status = false;
	}

	draw () {
		if(this._status) {
			ctx.beginPath();
			ctx.rect (this._x, this._y, Brick.width, Brick.height);
			ctx.fillStyle = "#0095DD";
			ctx.fill();
			ctx.closePath();
		}
	}

	// Also requires that our status==true
	collide (ball) {
		return this._status && ball.getX() > this._x && ball.getX() < this._x+Brick.width && ball.getY() > this._y && ball.getY() < this._y+Brick.height
	}
}

// Class vars for Brick, awkwardly
Brick.rowCount = 5;
Brick.columnCount = 3;
Brick.width = 75;
Brick.height = 20;
Brick.padding = 10;
Brick.offsetTop = 30;
Brick.offsetLeft = 30;

/*
 **************************************************
 * Paddle class
 **************************************************
 */
class Paddle {
	constructor () {
		this._height = 10;
		this._width = 75;
		this._x = (canvas.width-this._width)/2;

		// Paddle is also responsible for keyboard and mouse controls
		this._leftPressed = false
		this._rightPressed = false

		// Install callbacks
		var self = this
		document.addEventListener("keydown", function (e) { self.keyDownHandler(e)}, false);
		document.addEventListener("keyup", function (e) {self.keyUpHandler(e)}, false);
		document.addEventListener("mousemove", function (e) {self.mouseMoveHandler(e)}, false);
	}

	draw () {
		ctx.beginPath();
		ctx.rect(this._x, canvas.height-this._height, this._width, this._height);
		ctx.fillStyle = "#0095DD";
		ctx.fill();
		ctx.closePath();
	}

	collide (ball) {
		return ball.getX() > this._x && ball.getX() < this._x + this._width
	}

	update () {
		if (this._rightPressed && this._x < canvas.width-this._width) {
			this._x += 7;
		}
		else if (this._leftPressed && this._x > 0) {
			this._x -= 7;
		}
	}

	nextLife() {
		this._x = (canvas.width-this._width)/2;
	}

	// Keyboard callbacks
	keyDownHandler (e) {
		if(e.keyCode == 39) {
			this._rightPressed = true;
		}
		else if(e.keyCode == 37) {
			this._leftPressed = true;
		}
	}
	
	keyUpHandler (e) {
		if(e.keyCode == 39) {
			this._rightPressed = false;
		}

		else if(e.keyCode == 37) {
			this._leftPressed = false;
		}
	}

	// Mouse callback
	mouseMoveHandler (e) {
		var relativeX = e.clientX - canvas.offsetLeft;
		if(relativeX > 0 && relativeX < canvas.width) {
			this._x = relativeX - this._width/2;
		}
	}
}

/*
 **************************************************
 * Rules class
 **************************************************
 */
class Rules {
	constructor () {
		this._score = 0;
		this._lives = 3;
	}

	draw () {
		ctx.font = "16px Arial";
		ctx.fillStyle = "#0095DD";

		// Score
		ctx.fillText("Score: " + this._score, 8, 20);

		// Lives
		ctx.fillText("Lives: " + this._lives, canvas.width-65, 20);
	}

	collisionDetection () {
		for (var brick of bricks) {
			if(brick.collide (ball)) {
				brick.remove ()
				ball.reverse()

				this._score++;

				if (this._score == Brick.rowCount*Brick.columnCount) {
					alert ("YOU WIN, CONGRATS!");
					document.location.reload();
				}
			}
		}
	}

	loseLife () {
		this._lives--;

		if (this._lives>0) {
			ball.nextLife()
			paddle.nextLife()
		}
		else {
			alert("GAME OVER");
			document.location.reload();
		}
	}
}

/*
 **************************************************
 * Initialization, top level functions
 **************************************************
 */

// Initialize
onload = function () {
	// Initalize globals 
	canvas = document.getElementById("myCanvas");
	ctx = canvas.getContext("2d");

	// Instantiate our objects
	ball = new Ball ()
	paddle = new Paddle ()
	rules = new Rules ()
	for (var c=0; c<Brick.columnCount; c++) {
		for (var r=0; r<Brick.rowCount; r++) {
			bricks.push (new Brick (r, c))
		}
	}

	// Draw the first time, which will trigger the next time
	draw();
}

// Draw everything
function draw() {
	// Updates
	paddle.update()	
	ball.update()
	rules.collisionDetection();
	
	// Clear background
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	
	// Draw
	ball.draw();
	paddle.draw();
	for (var b of bricks) b.draw()
	rules.draw();

	// Set up for next frame
	requestAnimationFrame(draw);
}

</script>

</head>

<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
</body>

</html>