Added the bricks (and improve the bouncing)

This commit is contained in:
José David Guillén 2021-11-14 23:49:49 +01:00
parent 0f4a403edb
commit 7493713529
12 changed files with 303 additions and 111 deletions

175
Ball.js
View File

@ -1,51 +1,53 @@
class Ball { class Ball {
constructor(ctx, bar) { constructor() {
this.ctx = ctx; this.size = 10;
this.bar = bar;
this.size = 15;
this.moving = false; this.moving = false;
this.x = this.bar.x + (this.bar.w) / 2; this.speed = 7;
this.y = this.bar.y - this.size -1;
this.speed = 5;
// this.angle = 90; // this.angle = 90;
this.bounce(220,340); this.setAngle(180 +60, 360 - 60);
this.color = 'red'; this.color = 'red';
this.limits = null;
this.angleTR = this.g2r(360);
this.angleBR = this.g2r(90);
this.angleBL = this.g2r(180);
this.angleTL = this.g2r(270);
this.limits = {
l: this.size,
t: this.size,
r: this.ctx.canvas.width - this.size,
b: this.ctx.canvas.height
}
} }
start() { start() {
this.moving = true; this.moving = true;
} }
update() { update(ctx, x, y) {
if (this.move()) { this.limits ??= {
this.draw(); l: this.size,
t: this.size,
r: ctx.canvas.width - this.size,
b: ctx.canvas.height
};
if (this.move(x,y)) {
this.draw(ctx);
return true; return true;
} }
return false; return false;
} }
draw() { draw(ctx) {
this.ctx.beginPath(); ctx.beginPath();
this.ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI, false); ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI, false);
this.ctx.fillStyle = this.color; ctx.fillStyle = this.color;
this.ctx.fill(); ctx.fill();
this.ctx.lineWidth = 1; ctx.lineWidth = 1;
this.ctx.strokeStyle = '#003300'; ctx.strokeStyle = '#003300';
this.ctx.stroke(); ctx.stroke();
} }
move() { move(x,y) {
if (this.moving) { if (this.moving) {
this.x += this.speed * Math.cos(this.angle); this.x += this.speed * Math.cos(this.angle);
this.y += this.speed * Math.sin(this.angle); this.y += this.speed * Math.sin(this.angle);
@ -55,43 +57,108 @@ class Ball {
this.moving = false; this.moving = false;
return false; return false;
} }
if (this.y > this.bar.y ) return true; // Ball is lost, don't check anything else
if ( (this.y + this.size) > this.bar.y && this.x > this.bar.x && this.x < (this.bar.x + this.bar.w) ) { this.collideWalls(this.limits.l, this.limits.t, this.limits.r, this.limits.b);
// Down (Bar)
this.bounce(220,340);
} else {
if ( this.x < this.limits.l ) {
// Left wall
if (this.angle<this.g2r(180)) this.bounce(20,70); // It was going DOWN
else this.bounce(290,340); // It was going UP
} else
if ( this.x > this.limits.r ) {
// Right wall
if (this.angle<this.g2r(180)) this.bounce(110,160); // It was going DOWN
else this.bounce(200,250); // It was going UP
} else
if ( this.y < this.limits.t ) {
// Top Wall
this.bounce(20,140);
}
}
} else { } else {
this.x = this.bar.x + (this.bar.w) / 2; this.x = x;
this.y = this.bar.y - this.size -1; this.y = y - this.size - 1;
} }
return true; return true;
} }
bounce(min,max) { bounceL(r) {
this.angle = this.g2r( Math.floor(Math.random() * (max-min)) + min ); if(this.angle<=this.angleBL)
this.setAngle(0 + r, 90 - r);
else
this.setAngle(270 + r, 360 - r);
}
bounceR(r) {
if(this.angle<=this.angleBR)
this.setAngle(90 + r, 180 - r);
else
this.setAngle(180 + r, 270 - r);
}
bounceT(r) {
if(this.angle<=this.angleTL)
this.setAngle(90 + r, 180 - r);
else
this.setAngle(0 + r, 90 - r);
}
bounceB(r) {
if(this.angle<=this.angleBR)
this.setAngle(270 + r, 360 - r);
else
this.setAngle(180 + r, 270 - r);
}
collideWalls(x0, y0, x1, y1) {
let r = 20;
if ( this.x <= x0 ) {
this.bounceL(r);
return true;
}
if (this.x >= x1 ) {
this.bounceR(r);
return true;
}
if (this.y <= y0 ) {
this.bounceT(r);
return true;
}
if (this.y >= y1 ) {
this.bounceB(r);
return true;
}
return false;
}
/*
TL 270 TR
180 0
BL 90 BR
B
---------
R | | L
---------
T
*/
collide(x0,y0,x1,y1) { // 0 = hit Left/Right, 1 = hit Up/Down
let r = 20;
if (this.x>=x0 && this.x<=x1 && (this.y+this.size)>=y0 && (this.y+this.size)<y1) {
this.bounceB(r);
return true;
}
if (this.x>=x0 && this.x<=x1 && (this.y-this.size)<=y1 && (this.y-this.size)>y0) {
this.bounceT(r);
return true;
}
if (this.y>=y0 && this.y<=y1 && (this.y+this.size)>=x0 && (this.y-this.size)<x1) {
this.bounceR(r);
return true;
}
if (this.y>=y0 && this.y<=y1 && (this.y-this.size)<=x1 && (this.y-this.size)>x0) {
this.bounceL(r);
return true;
}
return false;
}
setAngle(min, max) {
this.angle = this.g2r(Math.floor(Math.random() * (max - min)) + min);
} }
g2r(deg) { g2r(deg) {
return ( ((360+deg)%360) * Math.PI) / 180.0; return (((360 + deg) % 360) * Math.PI) / 180.0;
} }
r2g(rad) { r2g(rad) {
return rad*180 / Math.PI; return rad * 180 / Math.PI;
} }
} }

5
Bar.js
View File

@ -3,14 +3,15 @@ class Bar {
this.ctx = ctx; this.ctx = ctx;
this.key = key; this.key = key;
this.w = 100; this.w = 80;
this.h = 20; this.h = 15;
this.speed = 10; // Target Speed this.speed = 10; // Target Speed
this._speed = 0; // Current Speed and direction this._speed = 0; // Current Speed and direction
this.xLimit = (ctx.canvas.width - this.w); this.xLimit = (ctx.canvas.width - this.w);
this.reset(); this.reset();
} }
reset() { reset() {
this.x = (this.ctx.canvas.width - this.w) / 2; this.x = (this.ctx.canvas.width - this.w) / 2;
this._y = (this.ctx.canvas.height - this.h * 2); this._y = (this.ctx.canvas.height - this.h * 2);

View File

@ -29,13 +29,17 @@ class Board {
} }
next(nextStage) { next(nextStage) {
this.loopStop();
this.resolve(nextStage);
}
loopStop() {
this.stop = true; this.stop = true;
if (this.requestID) { if (this.requestID) {
cancelAnimationFrame(this.requestID); cancelAnimationFrame(this.requestID);
this.requestID = null; this.requestID = null;
} }
this.ctx.clearRect(0, 0, this.w, this.h); this.ctx.clearRect(0, 0, this.w, this.h);
this.resolve(nextStage);
} }
loop() { loop() {

30
Bricks.js Normal file
View File

@ -0,0 +1,30 @@
class Brick {
constructor(type, column, row) {
this.type = type;
this.row = row;
this.column = column;
this.vspace = 2;
this.hspace = 2;
this.w = (360/8) -this.hspace;
this.h = (20) - this.vspace;
this.x = (this.w +this.hspace)*column;
this.y = 80 + (this.h +this.vspace)*row;
this.alive = true;
}
update(ctx) {
if (!this.alive) return false;
switch(this.type) {
case 1:
ctx.fillStyle = 'blue';
ctx.fillRect(this.x+1, this.y, this.w, this.h);
break;
}
return true;
}
}

View File

@ -1,16 +1,25 @@
class GameOver { class GameOver {
constructor(ctx) { constructor() {
this.ctx = ctx; this.w = 240;
this.x = ctx.canvas.width; this.h = 120;
this.y = ctx.canvas.height / 2 - 48;
this.cx = 360 / 2;
this.cy = 640 / 2;
this.x = this.cx - this.w/2;
this.y = this.cy - this.h/2;
} }
update() { update(ctx) {
this.centerText('GAME OVER', this.y, '48px', 'Consolas', 'Black'); ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillRect(this.x, this.y, this.w, this.h);
this.centerText(ctx, 'GAME OVER', this.cy, '48px', 'Consolas', 'Black');
this.centerText(ctx, '(Press "N" to start)', this.cy + 48, '24px', 'Consolas', 'Black');
} }
centerText(txt, y, s, f, c) { centerText(ctx, txt, y, s, f, c) {
this.ctx.font = s + ' ' + f; ctx.font = s + ' ' + f;
this.ctx.fillStyle = 'Black'; ctx.fillStyle = 'Black';
let x = (this.ctx.canvas.width - this.ctx.measureText(txt).width) / 2; let x = (ctx.canvas.width - ctx.measureText(txt).width) / 2;
this.ctx.fillText(txt, x, y); ctx.fillText(txt, x, y);
} }
} }

View File

@ -4,39 +4,73 @@ class GamePlay extends Board {
this.controls = { this.controls = {
'KeyX': ()=>{ 'KeyX': ()=>{
this.balls.push(new Ball(this.ctx, this.bar)); let b = new Ball();
b.update(this.ctx, this.bar.x + this.bar.w/2, this.bar.y);
this.balls.push(b);
this.balls[this.balls.length - 1].start(); this.balls[this.balls.length - 1].start();
}, },
'Space': ()=>{ 'Space': ()=>{
this.balls[0].moving = true; this.balls[0].moving = true;
},
'KeyN': ()=>{
if(this.lives.get()==0) this.next(1);
} }
} }
this.score = new Score(ctx); this.gameOver = new GameOver();
this.lives = new Lives(ctx); this.score = new Score();
this.lives = new Lives();
this.bar = new Bar(ctx, key); this.bar = new Bar(ctx, key);
this.levels = new Levels();
this.newGame(); this.newGame();
} }
newGame() { newGame() {
this.lives.reset(); this.lives.reset();
this.score.reset(); this.score.reset();
this.nextLevel(1);
}
nextLevel(lvl) {
this.level = lvl;
this.bricks = this.levels.load(lvl);
this.bar.reset(); this.bar.reset();
this.balls = []; this.balls = [];
this.balls.push(new Ball(this.ctx, this.bar)); this.balls.push(new Ball());
} }
update() { update() {
if(this.lives.get()==0) { if(this.lives.get()==0) {
gameOver.update(); this.loopStop();
this.gameOver.update(this.ctx);
// this.next(2);
} else { } else {
this.balls = this.balls.filter(ball => ball.update()); this.balls = this.balls.filter(ball => {
if (this.balls.length==0) { let r = ball.update(this.ctx, this.bar.x + this.bar.w/2, this.bar.y);
if ( !this.lives.lost() ) this.balls.push(new Ball(this.ctx, this.bar)); ball.collide( this.bar.x, this.bar.y, this.bar.x + this.bar.w, this.bar.y + this.bar.h );
this.bricks.forEach(b=>{
if(b.alive){
if ( ball.collide(b.x,b.y,b.x+b.w,b.y+b.h) ) {
this.score.add(1);
b.alive = false;
}
}});
return r;
}
);
if (this.bricks.length==0) {
this.nextLevel(++this.level);
} }
if (this.balls.length==0) {
if ( !this.lives.lost() ) this.balls.push(new Ball());
}
this.bricks = this.bricks.filter(brick => brick.update(this.ctx));
// if ( this.bricks.length == 0 ) this.nextLevel();
this.bar.update(); this.bar.update();
} }
this.score.update(); this.score.update(this.ctx);
this.lives.update(); this.lives.update(this.ctx);
} }
} }

View File

@ -1,4 +1,4 @@
class Key { class Keyboard {
constructor(onKeydown) { constructor(onKeydown) {
this._pressed = {}; this._pressed = {};
this.cb_onKeydown = onKeydown; this.cb_onKeydown = onKeydown;

39
Levels.js Normal file
View File

@ -0,0 +1,39 @@
class Levels {
load(lvl) {
let map = [];
switch (+lvl) {
case 1:
map = [].concat(
this.row(0, [1, 0, 1, 0, 0, 1, 0, 1]),
this.row(1, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(3, [0, 1, 1, 1, 1, 1, 1, 0]),
this.row(4, [1, 1, 1, 1, 1, 1, 1, 1])
);
break;
default:
map = [].concat(
this.row(0, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(1, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(2, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(3, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(4, [1, 1, 1, 1, 1, 1, 1, 1])
);
break;
}
return this.toBricks(map);
}
row(r, bricksTypes) {
let row = [];
for (var i = 0; i < bricksTypes.length; i++) row.push([bricksTypes[i], i, r]);
return row;
}
toBricks(map) {
let bricks = [];
map.forEach(b => {
if (b[0] > 0) bricks.push(new Brick(b[0], b[1], b[2]));
});
return bricks;
}
}

View File

@ -1,15 +1,10 @@
class Lives { class Lives {
constructor(ctx) { constructor() {
this.ctx = ctx;
this.reset(); this.reset();
this.ctx.font = "30px Consolas";
let m = ctx.measureText('Score: 00000');
this.x = ctx.canvas.width - m.width;
this.y = 20;
} }
reset() { reset() {
this.lives = 3; this.lives = 3;
this.y = 10;
} }
lost() { lost() {
this.lives--; this.lives--;
@ -18,14 +13,15 @@ class Lives {
get() { get() {
return this.lives; return this.lives;
} }
update() { update(ctx) {
if (this.y != 60) this.y++; if (this.y != 48) this.y++;
if (this.y > 0) { if (this.y > 0) {
let txt = (String.fromCharCode(parseInt('26A1', 16))+" ").repeat(this.lives); let txt = (String.fromCharCode(parseInt('26A1', 16))+" ").repeat(this.lives);
this.ctx.font = "30px Consolas"; ctx.font = "18px Consolas";
this.ctx.fillStyle = 'Green'; ctx.fillStyle = 'Green';
this.ctx.fillText(txt, this.x, this.y); this.x = ctx.canvas.width - ctx.measureText(txt).width;
ctx.fillText(txt, this.x, this.y);
} }
} }
} }

View File

@ -1,25 +1,21 @@
class Score { class Score {
constructor(ctx) { constructor() {
this.ctx = ctx;
this.reset(); this.reset();
this.ctx.font = "30px Consolas";
let m = ctx.measureText('Score: 00000');
this.x = ctx.canvas.width - m.width;
this.y = -10;
} }
reset() { reset() {
this.points = 0; this.points = 0;
this.x = 235;
this.y = -10;
} }
add(x) { add(x) {
this.points += x; this.points += x;
} }
update() { update(ctx) {
if (this.y != 30) this.y++; if (this.y != 20) this.y++;
if (this.y > 0) { if (this.y > 0) {
this.ctx.font = "30px Consolas"; ctx.font = "20px Consolas";
this.ctx.fillStyle = 'Black'; ctx.fillStyle = 'Black';
this.ctx.fillText('Score: ' + this.points, this.x, this.y); ctx.fillText('Score: ' + this.points, this.x, this.y);
} }
} }
} }

View File

@ -5,14 +5,21 @@
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
align-items: center;
justify-content: center;
display: flex;
} }
canvas { border:1px solid black; }
</style> </style>
<!-- ------------------------------------------ --> <!-- ------------------------------------------ -->
<script src="Key.js"></script> <script src="Keyboard.js"></script>
<script src="Board.js"></script> <script src="Board.js"></script>
<!-- ------------------------------------------ --> <!-- ------------------------------------------ -->
<script src="GameIntro.js"></script> <script src="GameIntro.js"></script>
<!-- ------------------------------------------ --> <!-- ------------------------------------------ -->
<script src="GameOver.js"></script>
<script src="Bricks.js"></script>
<script src="Levels.js"></script>
<script src="Score.js"></script> <script src="Score.js"></script>
<script src="Lives.js"></script> <script src="Lives.js"></script>
<script src="Bar.js"></script> <script src="Bar.js"></script>

View File

@ -7,20 +7,29 @@ document.addEventListener('DOMContentLoaded', init);
function init() { function init() {
let ctx, canvas = document.createElement("canvas"); let ctx, canvas = document.createElement("canvas");
canvas.width = window.innerWidth canvas.width = 360; // window.innerWidth
canvas.height = window.innerHeight canvas.height = 640; // window.innerHeight
ctx = canvas.getContext('2d'); ctx = canvas.getContext('2d');
document.body.insertBefore(canvas, document.body.childNodes[0]); document.body.insertBefore(canvas, document.body.childNodes[0]);
let container = document.querySelector("body");
let resize = (e) => {
container.clientWidth / container.clientHeight > 1
? (canvas.style.height = "100vh") && (canvas.style.width = "auto")
: (canvas.style.height = "auto") && (canvas.style.width = "100vw");
};
resize();
container.onresize = resize;
let key = new Key(), board;
let key = new Keyboard(), board;
function runBoard(stage) { function runBoard(stage) {
switch(stage) { switch (stage) {
case 1: board = new GamePlay(ctx, key); break; case 1: board = new GamePlay(ctx, key); break;
default: board = new GameIntro(ctx,key); break; default: board = new GameIntro(ctx, key); break;
} }
board board
.run() .run()
.then( stage=>runBoard(stage), e=>{} );; .then(stage => runBoard(stage), e => { });;
} }
runBoard(0); runBoard(0);
} }