2019-04-06 17:19:26 +00:00
|
|
|
class Game {
|
|
|
|
constructor(container) {
|
|
|
|
this.elements = {
|
|
|
|
highscore: container.querySelector('#highscore'),
|
|
|
|
score: container.querySelector('#score'),
|
|
|
|
coinCount: container.querySelector('#coin-count'),
|
|
|
|
pauseBtn: container.querySelector('.play-btn'),
|
|
|
|
pauseMenu: container.querySelector('#pause-menu'),
|
|
|
|
gameOverMenu: container.querySelector('#game-over-menu'),
|
|
|
|
};
|
|
|
|
|
|
|
|
this.canvas = container.querySelector('#canvas');
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
|
|
this.width = this.canvas.width;
|
|
|
|
this.height = this.canvas.height;
|
|
|
|
|
|
|
|
this.colCount = 7;
|
|
|
|
this.rowCount = 7;
|
2019-04-10 08:29:41 +00:00
|
|
|
this.rectSize = Math.floor(this.width / this.colCount);
|
|
|
|
this.startAtX = (this.width - this.colCount * this.rectSize) / 2;
|
2019-04-06 17:19:26 +00:00
|
|
|
|
|
|
|
this.originX = this.width / 2;
|
|
|
|
this.originY = (this.rowCount + 2) * this.rectSize;
|
|
|
|
this.newOriginX = null;
|
|
|
|
|
2019-04-10 08:29:41 +00:00
|
|
|
this.ballRadius = 10;
|
2019-04-06 17:19:26 +00:00
|
|
|
this.ballVelocity = 15;
|
|
|
|
|
|
|
|
this.powerUpRadius = 15;
|
|
|
|
|
|
|
|
this.rects = [];
|
|
|
|
this.powerUps = [];
|
|
|
|
this.balls = [];
|
|
|
|
|
|
|
|
this.roundNumber = 0;
|
|
|
|
this.ballCount = 1;
|
|
|
|
|
|
|
|
this.remainingBalls = this.ballCount;
|
|
|
|
this.newBalls = 0;
|
|
|
|
this.isMoving = false;
|
|
|
|
this.isPaused = false;
|
|
|
|
this.isGameOver = false;
|
2019-04-10 16:00:13 +00:00
|
|
|
this.speed = 1;
|
|
|
|
this.lastSpeedUp = 0;
|
2019-04-06 17:19:26 +00:00
|
|
|
|
|
|
|
this.coinAngle = 0;
|
|
|
|
this.mousePos = null;
|
|
|
|
|
|
|
|
this.addEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
addEventListeners() {
|
|
|
|
this.canvas.addEventListener('mousedown', (e) => {
|
|
|
|
this.mousePos = cbRelMousePos(e);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.canvas.addEventListener('mousemove', (e) => {
|
|
|
|
if (this.mousePos) {
|
|
|
|
this.mousePos = cbRelMousePos(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.canvas.addEventListener('mouseup', (e) => {
|
|
|
|
this.mousePos = cbRelMousePos(e);
|
|
|
|
this.shoot().then(() => {
|
|
|
|
this.mousePos = null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
this.elements.pauseBtn.addEventListener('click', () => {
|
|
|
|
if (this.isPaused) {
|
|
|
|
this.play();
|
|
|
|
} else {
|
|
|
|
this.pause();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
removeEventListeners() {
|
2019-04-10 08:29:41 +00:00
|
|
|
// this.canvas.removeEventListener()
|
2019-04-06 17:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
draw() {
|
|
|
|
this.ctx.clearRect(0, 0, this.width, this.height);
|
|
|
|
this.drawBase();
|
|
|
|
|
|
|
|
this.rects.forEach(rect => rect.draw());
|
|
|
|
this.powerUps.forEach(powerUp => powerUp.draw());
|
|
|
|
this.balls.forEach(ball => ball.draw());
|
|
|
|
|
|
|
|
this.drawOrigin();
|
|
|
|
|
|
|
|
if (!this.isPaused) {
|
|
|
|
requestAnimationFrame(() => this.draw());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drawBase() {
|
|
|
|
const y = this.originY + this.ballRadius;
|
|
|
|
const height = this.height - this.originY - this.ballRadius;
|
|
|
|
|
|
|
|
this.ctx.fillStyle = 'rgba(0,0,0,.15)';
|
|
|
|
this.ctx.fillRect(0, y, this.width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
drawOrigin() {
|
|
|
|
if (this.originX !== null) {
|
|
|
|
if (!this.isMoving && this.mousePos !== null) {
|
|
|
|
this.drawShootingLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.ctx.fillStyle = '#fff';
|
|
|
|
this.ctx.fillCircle(this.originX, this.originY, this.ballRadius);
|
|
|
|
|
|
|
|
if (this.remainingBalls > 0) {
|
|
|
|
this.ctx.fillText(this.remainingBalls + 'x', this.originX, this.originY + 50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.newOriginX !== null) {
|
|
|
|
this.ctx.fillStyle = '#fff';
|
|
|
|
this.ctx.fillCircle(this.newOriginX, this.originY, this.ballRadius);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drawShootingLine() {
|
|
|
|
const dir = this.calcMouseAngle();
|
|
|
|
let curX = this.originX;
|
|
|
|
let curY = this.originY;
|
|
|
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
const alpha = 1 - (i / 20);
|
|
|
|
this.ctx.fillStyle = `rgba(255,255,255,${alpha})`;
|
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.arc(curX, curY, this.ballRadius, 0, 2 * Math.PI);
|
|
|
|
this.ctx.fill();
|
|
|
|
|
|
|
|
curX += Math.cos(dir) * this.ballRadius * 4;
|
|
|
|
curY -= Math.sin(dir) * this.ballRadius * 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
calcMouseAngle() {
|
|
|
|
const dx = this.mousePos.x - this.originX,
|
|
|
|
dy = this.originY - this.mousePos.y;
|
|
|
|
|
|
|
|
let dir = Math.atan(dy / dx);
|
|
|
|
if (dx < 0)
|
|
|
|
dir += Math.PI;
|
|
|
|
|
|
|
|
dir = Math.min(Math.max(dir, .05), Math.PI - 0.05);
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
async shoot() {
|
|
|
|
if (this.isMoving || this.mousePos >= this.originY) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isMoving = true;
|
2019-04-10 16:00:13 +00:00
|
|
|
this.lastSpeedUp = Date.now();
|
2019-04-06 17:19:26 +00:00
|
|
|
|
|
|
|
const dir = this.calcMouseAngle();
|
|
|
|
const ballCount = this.ballCount;
|
|
|
|
for (let i = 0; i < ballCount; i++) {
|
|
|
|
const ball = new Ball(this, this.originX, this.originY, this.ballVelocity, dir);
|
|
|
|
this.balls.push(ball);
|
|
|
|
this.remainingBalls--;
|
|
|
|
if (this.remainingBalls === 0) {
|
|
|
|
this.originX = null;
|
|
|
|
}
|
|
|
|
await sleep(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
roundOver() {
|
2019-04-10 16:00:13 +00:00
|
|
|
this.speed = 1;
|
|
|
|
this.lastSpeedUp = Date.now();
|
|
|
|
this.elements.speedUpContainer.innerHTML = "";
|
|
|
|
|
2019-04-06 17:19:26 +00:00
|
|
|
this.ballCount += this.newBalls;
|
|
|
|
this.newBalls = 0;
|
|
|
|
this.remainingBalls = this.ballCount;
|
|
|
|
this.originX = this.newOriginX;
|
|
|
|
this.newOriginX = null;
|
|
|
|
|
|
|
|
this.increaseRoundNumber();
|
|
|
|
this.addNewObjects();
|
|
|
|
this.shiftObjectsDown();
|
|
|
|
|
|
|
|
sounds.roundOver.play();
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
this.isMoving = false;
|
|
|
|
}, 500);
|
|
|
|
}
|
|
|
|
|
|
|
|
gameOver() {
|
|
|
|
this.isGameOver = true;
|
|
|
|
coinCount += Math.floor((this.roundNumber - this.rowCount) / 10);
|
|
|
|
|
|
|
|
this.elements.gameOverMenu.classList.add('shown');
|
|
|
|
|
|
|
|
onGameOver();
|
|
|
|
}
|
|
|
|
|
|
|
|
addNewObjects() {
|
2019-04-10 08:29:41 +00:00
|
|
|
const spawnBall = this.roundNumber > 1,
|
2019-04-06 17:19:26 +00:00
|
|
|
ballPos = spawnBall ? Math.round(Math.random() * (this.colCount - 1)) : null,
|
|
|
|
spawnCoin = Math.random() < .33,
|
|
|
|
coinPos = spawnCoin ? Math.round(Math.random() * (this.colCount - 1)) : null;
|
|
|
|
|
|
|
|
for (let i = 0; i < this.colCount; i++) {
|
|
|
|
if (spawnBall && ballPos === i) {
|
2019-04-10 08:29:41 +00:00
|
|
|
this.powerUps.push(new PowerUpBall(this, this.startAtX + (i + .5) * this.rectSize, .5 * this.rectSize));
|
2019-04-06 17:19:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spawnCoin && coinPos === i) {
|
2019-04-10 08:29:41 +00:00
|
|
|
this.powerUps.push(new PowerUpCoin(this, this.startAtX + (i + .5) * this.rectSize, .5 * this.rectSize));
|
2019-04-06 17:19:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Math.random() < .6) {
|
|
|
|
const number = Math.random() < .6 ? this.roundNumber : 2 * this.roundNumber;
|
2019-04-10 08:29:41 +00:00
|
|
|
this.rects.push(new Rect(this, this.startAtX + i * this.rectSize, 0, number));
|
2019-04-06 17:19:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
shiftObjectsDown() {
|
|
|
|
this.rects.forEach(rect => {
|
|
|
|
moveObject(rect, 0, this.rectSize, 500);
|
|
|
|
|
|
|
|
if (rect.y + 2 * this.rectSize >= this.originY) {
|
|
|
|
this.gameOver();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.powerUps.forEach(powerUp => {
|
|
|
|
moveObject(powerUp, 0, this.rectSize, 500);
|
|
|
|
|
|
|
|
if (powerUp.cy + this.rectSize >= this.originY) {
|
|
|
|
setTimeout(() => {
|
|
|
|
powerUp.remove();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
increaseRoundNumber() {
|
|
|
|
this.roundNumber++;
|
|
|
|
|
|
|
|
this.elements.score.innerText = this.roundNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
increaseCoinCount() {
|
|
|
|
coinCount++;
|
|
|
|
|
|
|
|
this.elements.coinCount.innerText = coinCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
update() {
|
|
|
|
this.balls.forEach(ball => {
|
|
|
|
ball.update(10);
|
|
|
|
});
|
|
|
|
|
2019-04-10 16:00:13 +00:00
|
|
|
this.coinAngle += .05;
|
|
|
|
if (this.coinAngle >= 2 * Math.PI) {
|
|
|
|
this.coinAngle -= 2 * Math.PI;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.isMoving && Date.now() - this.lastSpeedUp >= 10000) {
|
|
|
|
const button = document.createElement('button');
|
|
|
|
button.innerText = 'FASTER';
|
|
|
|
this.elements.speedUpContainer.innerHTML = "";
|
|
|
|
this.elements.speedUpContainer.appendChild(button);
|
|
|
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
this.speed *= 2;
|
|
|
|
button.remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
this.lastSpeedUp = Date.now();
|
|
|
|
}
|
2019-04-06 17:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.isPaused && !this.isGameOver) {
|
|
|
|
requestAnimationFrame(() => this.update());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
|
|
|
this.increaseRoundNumber();
|
|
|
|
this.addNewObjects();
|
|
|
|
this.shiftObjectsDown();
|
|
|
|
this.play()
|
|
|
|
}
|
|
|
|
|
|
|
|
play() {
|
|
|
|
this.isPaused = false;
|
2019-04-10 16:00:13 +00:00
|
|
|
this.lastSpeedUp = Date.now();
|
2019-04-06 17:19:26 +00:00
|
|
|
this.update();
|
|
|
|
this.draw();
|
|
|
|
|
|
|
|
this.elements.pauseBtn.classList.add('paused');
|
|
|
|
this.elements.pauseMenu.classList.remove('shown');
|
|
|
|
}
|
|
|
|
|
|
|
|
pause() {
|
|
|
|
this.isPaused = true;
|
|
|
|
|
|
|
|
this.elements.pauseBtn.classList.remove('paused');
|
|
|
|
this.elements.pauseMenu.classList.add('shown');
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
2019-04-10 16:00:13 +00:00
|
|
|
this.elements.gameOverMenu.classList.remove('shown');
|
2019-04-06 17:19:26 +00:00
|
|
|
this.removeEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|