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; this.rectSize = Math.floor(this.width / this.colCount); this.startAtX = (this.width - this.colCount * this.rectSize) / 2; this.originX = this.width / 2; this.originY = (this.rowCount + 2) * this.rectSize; this.newOriginX = null; this.ballRadius = 10; 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; this.speed = 1; this.lastSpeedUp = 0; 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() { // this.canvas.removeEventListener() } 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; this.lastSpeedUp = Date.now(); 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() { this.speed = 1; this.lastSpeedUp = Date.now(); this.elements.speedUpContainer.innerHTML = ""; 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() { const spawnBall = this.roundNumber > 1, 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) { this.powerUps.push(new PowerUpBall(this, this.startAtX + (i + .5) * this.rectSize, .5 * this.rectSize)); continue; } if (spawnCoin && coinPos === i) { this.powerUps.push(new PowerUpCoin(this, this.startAtX + (i + .5) * this.rectSize, .5 * this.rectSize)); continue; } if (Math.random() < .6) { const number = Math.random() < .6 ? this.roundNumber : 2 * this.roundNumber; this.rects.push(new Rect(this, this.startAtX + i * this.rectSize, 0, number)); } } } 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); }); 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(); } } if (!this.isPaused && !this.isGameOver) { requestAnimationFrame(() => this.update()); } } start() { this.increaseRoundNumber(); this.addNewObjects(); this.shiftObjectsDown(); this.play() } play() { this.isPaused = false; this.lastSpeedUp = Date.now(); 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() { this.elements.gameOverMenu.classList.remove('shown'); this.removeEventListeners(); } }