"use strict";
requestAnimationFrame(mainLoop); // request first frame. This will only happen after all the code below has been evaluated
// I am using direct reference to the canvas by its ID
//const canvas = document.getElementById("mainCanv");
const ctx = canvas.getContext("2d");
var score = 0;
/*===========================================================================================
all game constants and settings
============================================================================================*/
const game = {
backgroundColor : "black",
autoPlay : true,
auto : {
playSpeed : 10, // Number of autoplay frames per 60th second Note one extra normal frame is also rendered
follow : 0.7, // how well auto play ball follows 1 is perfect, 0.8 will always hit, < 0.8 ocational miss to 0 wont move
},
paddle : { // details for the paddle
init : { // this is added directly to the paddle object
w : 80,
h : 15,
color : "green",
},
fromBottom : 40, // these are added programmatic
speed : 5,
},
ball : {
init : { // this is added directly to the ball object
w: 20,
h: 20,
x: 200,
y: 200,
color : "red"
},
dir : 0.6, // these are added programmatic
speed : 4,
},
brick : {
init : { // this is added directly to each brick object
w : 20,
h : 20,
color : "blue",
},
spacingX : 40, // these are added programmatic
spacingY : 40,
top : 60,
rowCount : 14,
count : 14 * 4,
value : 10, // points its worth
},
score : { // where to put the score and its color
x : 20,
y : 20,
color : "white",
},
}
/*===========================================================================================
helper Function
============================================================================================*/
function doRectsOverlap(rectA, rectB){ // both rectA, and rectB must have properties x,y,w,h
return ! (
rectA.y > rectB.y + rectB.h ||
rectA.y + rectA.h < rectB.y ||
rectA.x + rectA.w < rectB.x ||
rectA.x > rectB.x + rectB.w
);
}
/*===========================================================================================
Common object properties and functions
============================================================================================*/
const rectangle = {
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
/*===========================================================================================
The main animation loop
============================================================================================*/
function mainLoop() {
ctx.fillStyle = game.backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height)
inPlay();
requestAnimationFrame(mainLoop); // request next frame
}
/*===========================================================================================
When in play
============================================================================================*/
function inPlay() {
if (ball.outOfBounds) {
reset(); // reset game when out of bounds
}
// auto play loops over main game draws everything but bricks
// at a faded alpha level
if (game.autoPlay) {
ctx.globalAlpha = 1 / (game.auto.playSpeed /4);
for (let i = 0; i < game.auto.playSpeed; i++) {
ball.update();
paddle.update();
bricks.update();
paddle.testBall(ball);
paddle.draw();
ball.draw();
}
ctx.globalAlpha = 1;
if (keys.any) {
game.autoPlay = false;
}
}
// main render for normal play at alpha = 1;
ball.update();
paddle.update();
bricks.update();
paddle.testBall(ball);
paddle.draw();
ball.draw();
bricks.draw();
if (bricks.length === 0 && paddle.hitBall) { bricks.init() }
paddle.hitBall = false;
drawScore();
}
function reset(){
ball.reset();
bricks.init();
paddle.reset();
keys.clear();
score = 0;
}
function drawScore(){ // does the score thing
ctx.fillStyle = game.score.color;
ctx.fillText("Score: " + score, game.score.x, game.score.y)
}
/*===========================================================================================
Keyboard state and event handler
============================================================================================*/
const keys = {
ArrowLeft : false,
ArrowRight : false,
any : false,
event(event) {
if (keys[event.code] !== undefined) {
keys[event.code] = event.type === "keydown";
}
keys.any = event.type === "keydown" ? true : keys.any;
},
clear(){
for(const key of Object.keys(keys)) {
if (typeof keys[key] !== "function") { keys[key] = false }
}
}
};
addEventListener("keydown", keys.event);
addEventListener("keyup", keys.event);
/*===========================================================================================
Brick related code
============================================================================================*/
// Assign some functions to an array of bricks
const bricks = Object.assign([], { // The array to get some more properties
init() {
this.length = 0; // zero length
for (let i = 0; i < game.brick.count; i++) {
this.push({
...rectangle,
...game.brick.init,
x: (i % game.brick.rowCount) * game.brick.spacingX,
y: game.brick.top + (i / game.brick.rowCount | 0) * game.brick.spacingY,
})
}
},
draw() {
for (const brick of this) { brick.draw() }
},
update() {
var i;
for (i = 0; i < this.length; i ++) {
const brick = this[i];
if (ball.testBrick(brick)) { // check ball against brick remove brick if hit
this.splice(i--, 1); // remove brick
score += game.brick.value;
}
}
}
});
/*===========================================================================================
Ball related code
============================================================================================*/
const ball = {
...rectangle, // assign common
...game.ball.init, // assign ball init values
dx : 0, // common name for speed is delta so I use deltaX, and deltaY shortened to dx,dy
dy : 0,
outOfBounds : true, // this forces reset at start of game
reset() {
Object.assign(this,
game.ball.init, {
dx : Math.cos(game.ball.dir) * game.ball.speed,
dy : Math.sin(game.ball.dir) * game.ball.speed,
outOfBounds : false,
}
);
},
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x <= 0) {
this.x = -this.x; // the ball will have moved away from the wall the same distance it has moved into the wall
// MUST MOVE AWAY +
this.dx = Math.abs(this.dx); // Set the direction as always positive. If you do -this.dx it may stuff up when you have
// the ball and paddle interacting at the same time
} else if (this.x > canvas.width - this.w) {
this.x = (canvas.width - this.w) - (this.x - (canvas.width - this.w));
// MUST MOVE AWAY -
this.dx = -Math.abs(this.dx);
}
if (this.y < 0) {
this.y = -this.y;
this.dy = Math.abs(this.dy);
}
if (this.y > canvas.height) { // ball out of bounds
this.outOfBounds = true;
}
},
checkbrickTopBot(brick){
if (this.dy < 0) { // moving up
this.y = (brick.y + brick.h) + ((brick.y + brick.h) - this.y);
this.dy = -this.dy;
return true;
}
// must be moving down
this.y = brick.y - ((this.y + this.h) - brick.y) - this.h;
this.dy = -this.dy;
return true;
},
checkbrickLeftRight(brick){
if (this.dx < 0) { // moving up
this.x= (brick.x + brick.w) + ((brick.x + brick.w) - this.x);
this.dx = -this.dx;
return true;
}
// must be moving down
this.x = brick.x - ((this.x + this.w) - brick.x) - this.w;
this.dx = -this.dx;
return true;
},
testBrick (brick) { // returns true if brick hit
if (doRectsOverlap(this, brick)) {
// use ball direction to check which side of brick to test
// First normalize the speed vector
var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
var nx = this.dx / speed;
var ny = this.dy / speed;
var xDist,yDist;
// now find the distance to the brick edges in terms of normalised spped
if (this.dx < 0) {
xDist = ((this.x - this.dx) - (brick.x + brick.w)) / nx;
} else {
xDist = ((this.x - this.dx + this.w) - brick.x) / nx;
}
if (this.dy < 0) {
yDist = ((this.y - this.dy) - (brick.y + brick.h)) / ny;
} else {
yDist = ((this.y - this.dy + this.h) - brick.y) / ny;
}
// the edge that is hit first is the smallest dist
if (xDist <= yDist) { // x edge first to hit
return this.checkbrickLeftRight(brick);
}
return this.checkbrickTopBot(brick);
}
return false;
},
};
/*===========================================================================================
Paddle related code
============================================================================================*/
const paddle = {
...rectangle, // assign common
...game.paddle.init, // assign startup
x: (canvas.width / 2 - game.paddle.init.w / 2) | 0, // | 0 floors the result (same as Math.floor)
y: canvas.height - game.paddle.fromBottom,
hitBall : true,
leftEdge : 0,
rightedge : canvas.width - game.paddle.init.w,
reset() {
this.x = (canvas.width / 2 - game.paddle.init.w / 2) | 0; // | 0 floors the result (same as Math.floor)
this.y = canvas.height - game.paddle.fromBottom;
},
update() {
if (game.autoPlay) {
keys.ArrowRight = false;
keys.ArrowLeft = false;
if (Math.random() < game.auto.follow) {
var x = ball.x + ((Math.random()- 0.5) * this.w * 2);
if (ball.x > this.x + this.w * (3/4)) { keys.ArrowRight = true }
if (ball.x < this.x + this.w * (1/4)) { keys.ArrowLeft = true }
}
}
if (keys.ArrowRight) { this.x += game.paddle.speed }
if (keys.ArrowLeft) { this.x -= game.paddle.speed }
this.x = this.x < this.leftEdge ? this.leftEdge : this.x;
this.x = this.x >= this.rightEdge ? this.rightEdge : this.x;
},
testBall(ball) {
if (doRectsOverlap(this, ball)) {
// we now know that the ball must be hitting the paddle.
var cx = (ball.x + ball.w / 2); // get ball x center
// check for side hits. If so ball goes away from edge
if (cx < this.x) { // hits paddle left side
ball.y = this.y - ((ball.y + ball.h) - this.y) - ball.h;
ball.x = this.x - ball.w;
ball.dy = -Math.abs(ball.dy); // Must be negative
ball.dx = -Math.abs(ball.dx); // Must be negative
this.hitBall = true;
} else if( cx > this.x + this.w) { // hits paddle right side
ball.y = this.y - ((ball.y + ball.h) - this.y) - ball.h;
ball.x = this.x + this.w;
ball.dy = -Math.abs(ball.dy); // Must be negative
ball.dx = Math.abs(ball.dx); // Must be positive
this.hitBall = true;
} else { // must be hits bat top or have run over the ball too lat
if (ball.y > this.y + this.h / 2) { // if ball is more than halfway through the bat too lat to rescue so let it continue on
return;
}
ball.y = this.y - ((ball.y + ball.h) - this.y) - ball.h;
ball.dy = -Math.abs(ball.dy); // Must be negative
this.hitBall = true;
}
}
},
}
<!-- the id is used to reference the element canvas -->
<canvas id="canvas" width="540" height="400"></canvas>