<!DOCTYPE html> <html> <head> <title>Comp 86 example Breakout</title> <!-- Based on: https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript --> <style> #myCanvas { background: #eee; } #buttonPanel { max-width: 480px; margin-top: 12px; text-align: center; background: #0095DD; } button { margin: 4px; color: #0095DD; } #scorePanel { max-width: 480px; background: #eee; } p.score { font-family: sans-serif; font-size: 24px; color: #0095DD; } </style> <script> "use strict"; /* ************************************************** * Main class ************************************************** */ class Main { constructor () { // Save this, for convenience this._ctx = myCanvas.getContext("2d"); // Instantiate the bricks this._bricks = [] //*5 Instantiate the bricks for (let c=0; c<Brick.columnCount; c++) { //*5 for (let r=0; r<Brick.rowCount; r++) { //*5 this._bricks.push (new Brick (r, c)) //*5 } } // Instantiate our main objects and connect them up this._paddle = new Paddle () //*18 Instantiate our main objects and connect them up this._rules = new Rules (this._paddle, this._bricks) //*18 this._ball = new Ball (this._paddle, this._rules) //*18 this._rules.init (this._ball) //*19 Delayed initialization } // Update everything and redraw for animation loop loop () { //*17 Animation: update then redraw if (this._rules.playing) { //*20 Pause/resume uses a state variable // Updates this._paddle.update() //*17 this._ball.update() //*17 this._rules.detectCollision(); //*17 // Clear background this._ctx.clearRect(0, 0, myCanvas.width, myCanvas.height); // Draw this._ball.draw(this._ctx); //*17 this._paddle.draw(this._ctx); //*17 for (let b of this._bricks) b.draw(this._ctx) //*17 this._rules.draw(); //*17 } // Set up for next frame, use arrow function to override "this" requestAnimationFrame(() => this.loop()); //*17 } } /* ************************************************** * Ball class ************************************************** */ class Ball { //*1 Ball class, like Square constructor (paddle, rules) { //*1 // Stash args this._paddle = paddle //*1 this._rules = rules //*1 // Our own ivars this._radius = 10; //*2 Maintains info this._x = myCanvas.width/2; //*2 this._y = myCanvas.height-30; //*2 this._dx = 2; //*2 this._dy = -2; //*2 } // Full pedantic style with private ivars, but with getter syntax here get x () { return this._x; } //*8 Use getter function get y () { return this._y; } draw (ctx) { //*3 Knows how to draw itself when told ctx.beginPath(); //*3 ctx.arc(this._x, this._y, this._radius, 0, Math.PI*2); //*3 ctx.fillStyle = "#0095DD"; //*3 ctx.fill(); //*3 } update () { //*4 Knows how to update itself when told // Normal update this._x += this._dx; //*4 this._y += this._dy; //*4 // U-turn ball at sides if (this._x + this._dx > myCanvas.width-this._radius || this._x + this._dx < this._radius) { //*13 Ball hits side or top = bounce back this._dx = -this._dx; //*13 } // U-turn ball at top if (this._y + this._dy < this._radius) { //*13 this._dy = -this._dy; //*13 } // Ball hit bottom of screen if (this._y + this._dy > myCanvas.height-this._radius) { //*14 Ball hits paddle = bounce up if (this._paddle.collide (this)) { //*14 // Hit paddle = turn around this._dy = -this._dy; //*14 } else { //*15 Ball hits bottom = use a life // Falls off screen = use a life this._rules.nextLife() //*15 } } } // Ball hits a brick reverseY () { this._dy = -this._dy; } // Ball resets and speeds up nextLife() { this._x = myCanvas.width/2; this._y = myCanvas.height-30; this._dx = 4; this._dy = -4; } } /* ************************************************** * Brick class ************************************************** */ class Brick { constructor (row, col) { // Initialize our ivars this._x = (row * (Brick.width+Brick.padding)) + Brick.offsetLeft; //*6 Calculate Brick's x, y from r,c subscripts this._y = (col * (Brick.height+Brick.padding)) + Brick.offsetTop; //*6 this._visible = true //*7 Use visible flag } remove () { //*7 this._visible = false; //*7 } draw (ctx) { if(this._visible) { //*7 ctx.beginPath(); ctx.rect (this._x, this._y, Brick.width, Brick.height); ctx.fillStyle = "#0095DD"; ctx.fill(); } } // It only counts if our visible==true collide (ball) { //*16 Check for ball hitting a brick return this._visible && ball.x > this._x && ball.x < this._x+Brick.width && ball.y > this._y && ball.y < this._y+Brick.height //*16 } } // Class vars for Brick, handled awkwardly in JS like this 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 () { // Initialize our own ivars this._height = 10; this._width = 75; this._x = (myCanvas.width-this._width)/2; //*9 Paddle: maintains X location, draws itself; Rules; draws itself in HTML elements // Paddle is also responsible for keyboard and mouse controls this._leftPressed = false this._rightPressed = false // Install callbacks document.addEventListener ("keydown", (e) => this.keyDownHandler (e)); //*10 Remember arrow key state, but don't act yet document.addEventListener("keyup", (e) => this.keyUpHandler (e)); //*10 document.addEventListener("mousemove", (e) => this.mouseMoveHandler (e)); //*12 Mouse moves also changes paddle X location } draw (ctx) { //*9 ctx.beginPath(); ctx.fillStyle = "#0095DD"; ctx.rect(this._x, myCanvas.height-this._height, this._width, this._height); //*9 ctx.fill(); } collide (ball) { return ball.x > this._x && ball.x < this._x + this._width } // Moves on every frame, while you are holding down a key update () { //*11 Update paddle if an arrow key is down if (this._rightPressed && this._x < myCanvas.width-this._width) { //*11 this._x += 7; //*11 } else if (this._leftPressed && this._x > 0) { //*11 this._x -= 7; //*11 } } // Reset position for next game nextLife() { this._x = (myCanvas.width-this._width)/2; } // Keyboard callbacks for arrow keys down and up // here we just remember if they are up or down, // update() takes the action keyDownHandler (e) { //*10 if(e.keyCode == 39) { //*10 this._rightPressed = true; //*10 } else if(e.keyCode == 37) { this._leftPressed = true; } } keyUpHandler (e) { //*10 if(e.keyCode == 39) { //*10 this._rightPressed = false; //*10 } else if(e.keyCode == 37) { //*10 this._leftPressed = false; //*10 } } // Mouse callback mouseMoveHandler (e) { //*12 const relativeX = e.clientX - myCanvas.offsetLeft; if (relativeX > 0 && relativeX < myCanvas.width) { //*12 this._x = relativeX - this._width/2; //*12 } } } /* ************************************************** * Rules class ************************************************** */ class Rules { constructor (paddle, bricks) { // Stash this._paddle = paddle this._bricks = bricks // Initialize this._score = 0; this._lives = 0; this._playing = true; // We will handle the button callbacks pauseButton.addEventListener ("click", () => this._pause()) resumeButton.addEventListener ("click", () => this._resume()) restartButton.addEventListener ("click", () => this._restart()) } // Delayed initialization cause ball was not available when our constructor was called init (ball) { //*19 this._ball = ball //*19 } draw () { score.textContent = this._score //*9 lives.textContent = this._lives //*9 } // Tests the ball against every (visible) brick detectCollision () { //*16 for (let brick of this._bricks) { //*16 if(brick.collide (this._ball)) { //*16 brick.remove () //*16 this._ball.reverseY() //*16 this._score++; //*16 if (this._score == Brick.rowCount*Brick.columnCount) { alert ("YOU WIN, CONGRATS!"); document.location.reload(); } } } } nextLife () { this._lives++; this._ball.nextLife() this._paddle.nextLife() } get playing () {return this._playing} //*20 // "Private" methods, denoted by "_" _pause() { //*20 this._playing = false //*20 } _resume() { //*20 this._playing = true //*20 } _restart() { document.location.reload(); } } /* ************************************************** * Initialize ************************************************** */ onload = function () { //*21 Initialize // Just instantiate, and loop the first time, which will trigger the next times new Main().loop() //*21 } </script> </head> <body> <canvas id="myCanvas" width="480" height="320" ></canvas> <div id="buttonPanel"> <button id="pauseButton">Pause</button> <button id="resumeButton">Resume</button> <button id="restartButton">Restart</button> </div> <div id="scorePanel"> <p class="score">Score: <b><span id="score"></span></b></p> <p class="score">Lives used: <b><span id="lives"></span></b></p> </div> </body> </html>