Initial commit
This commit is contained in:
commit
5c8296fbe3
BIN
assets/game_over.wav
Normal file
BIN
assets/game_over.wav
Normal file
Binary file not shown.
BIN
assets/hit_ball.wav
Normal file
BIN
assets/hit_ball.wav
Normal file
Binary file not shown.
BIN
assets/hit_block.wav
Normal file
BIN
assets/hit_block.wav
Normal file
Binary file not shown.
BIN
assets/hit_coin.wav
Normal file
BIN
assets/hit_coin.wav
Normal file
Binary file not shown.
BIN
assets/round_success.wav
Normal file
BIN
assets/round_success.wav
Normal file
Binary file not shown.
107
ball.js
Normal file
107
ball.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
class Ball {
|
||||||
|
constructor(game, cx, cy, v, dir) {
|
||||||
|
this.game = game;
|
||||||
|
this.id = this.game.balls.length;
|
||||||
|
this.cx = cx;
|
||||||
|
this.cy = cy;
|
||||||
|
this.vx = v * Math.cos(dir);
|
||||||
|
this.vy = v * Math.sin(dir);
|
||||||
|
this.collide = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(stepCount = 10) {
|
||||||
|
if (!this.game.isMoving)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = 0; i < stepCount; i++) {
|
||||||
|
this.cx += this.vx / stepCount;
|
||||||
|
this.cy -= this.vy / stepCount;
|
||||||
|
|
||||||
|
if (this.cx - this.game.ballRadius <= 0 || this.cx + this.game.ballRadius >= this.game.canvas.width) {
|
||||||
|
this.vx *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cy - this.game.ballRadius <= 0) {
|
||||||
|
this.vy *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cy >= this.game.originY && this.vy < 0) {
|
||||||
|
this.vy = 0;
|
||||||
|
this.cy = this.game.originY;
|
||||||
|
|
||||||
|
if (this.game.newOriginX === null || this.cx === this.game.newOriginX) {
|
||||||
|
this.game.newOriginX = this.cx;
|
||||||
|
this.vy = 0;
|
||||||
|
this.remove();
|
||||||
|
} else {
|
||||||
|
this.vx = (this.game.newOriginX - this.cx) / 60 * 2;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.vx = 0;
|
||||||
|
this.remove();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.collide) {
|
||||||
|
this.game.rects.forEach(rect => {
|
||||||
|
this.checkHitRect(rect);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.powerUps.forEach(powerUp => {
|
||||||
|
this.checkHitPowerUp(powerUp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.game.balls.splice(this.game.balls.findIndex(ball => ball === this), 1);
|
||||||
|
|
||||||
|
if (this.game.isMoving && this.game.balls.length === 0) {
|
||||||
|
this.game.roundOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHitRect(rect) {
|
||||||
|
const closestX = Math.clamp(this.cx, rect.x, rect.x + this.game.rectSize);
|
||||||
|
const closestY = Math.clamp(this.cy, rect.y, rect.y + this.game.rectSize);
|
||||||
|
|
||||||
|
const distX = this.cx - closestX;
|
||||||
|
const distY = this.cy - closestY;
|
||||||
|
|
||||||
|
const distSquared = (distX ** 2) + (distY ** 2);
|
||||||
|
|
||||||
|
if (distSquared < this.game.ballRadius ** 2) {
|
||||||
|
const collBottom = rect.y + this.game.rectSize - (this.cy - this.game.ballRadius);
|
||||||
|
const collTop = (this.cy + this.game.ballRadius) - rect.y;
|
||||||
|
const collLeft = (this.cx + this.game.ballRadius) - rect.x;
|
||||||
|
const collRight = rect.x + this.game.rectSize - (this.cx - this.game.ballRadius);
|
||||||
|
|
||||||
|
if ((collTop <= collLeft && collTop <= collRight) || (collBottom <= collLeft && collBottom <= collRight)) {
|
||||||
|
this.vy *= -1;
|
||||||
|
} else {
|
||||||
|
this.vx *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.hit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHitPowerUp(powerUp) {
|
||||||
|
const distX = this.cx - powerUp.cx;
|
||||||
|
const distY = this.cy - powerUp.cy;
|
||||||
|
|
||||||
|
const distSquared = (distX ** 2) + (distY ** 2);
|
||||||
|
|
||||||
|
if (distSquared < (this.game.ballRadius + this.game.powerUpRadius) ** 2) {
|
||||||
|
powerUp.hit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
this.game.ctx.fillStyle = '#fff';
|
||||||
|
this.game.ctx.beginPath();
|
||||||
|
this.game.ctx.arc(this.cx, this.cy, this.game.ballRadius, 0, 2 * Math.PI);
|
||||||
|
this.game.ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
298
game.js
Normal file
298
game.js
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
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 = this.width / this.colCount;
|
||||||
|
|
||||||
|
this.originX = this.width / 2;
|
||||||
|
this.originY = (this.rowCount + 2) * this.rectSize;
|
||||||
|
this.newOriginX = null;
|
||||||
|
|
||||||
|
this.ballRadius = 12;
|
||||||
|
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.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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.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 = Math.random() < .5,
|
||||||
|
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, (i + .5) * this.rectSize, .5 * this.rectSize));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spawnCoin && coinPos === i) {
|
||||||
|
this.powerUps.push(new PowerUpCoin(this, (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, 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.isPaused && !this.isGameOver) {
|
||||||
|
requestAnimationFrame(() => this.update());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.increaseRoundNumber();
|
||||||
|
this.addNewObjects();
|
||||||
|
this.shiftObjectsDown();
|
||||||
|
this.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
play() {
|
||||||
|
this.isPaused = false;
|
||||||
|
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.removeEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
120
helper-functions.js
Normal file
120
helper-functions.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveObject(object, amountX, amountY, time) {
|
||||||
|
let counter = 0;
|
||||||
|
const step = time / 1000 * 60;
|
||||||
|
const stepX = amountX / step;
|
||||||
|
const stepY = amountY / step;
|
||||||
|
|
||||||
|
let goalX;
|
||||||
|
let goalY;
|
||||||
|
if (object.hasOwnProperty('cx') && object.hasOwnProperty('cy')) {
|
||||||
|
goalX = object.cx + amountX;
|
||||||
|
goalY = object.cy + amountY;
|
||||||
|
} else {
|
||||||
|
goalX = object.x + amountX;
|
||||||
|
goalY = object.y + amountY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (object.hasOwnProperty('cx') && object.hasOwnProperty('cy')) {
|
||||||
|
object.cx += stepX;
|
||||||
|
object.cy += stepY;
|
||||||
|
} else {
|
||||||
|
object.x += stepX;
|
||||||
|
object.y += stepY;
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if (counter >= step) {
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
if (object.hasOwnProperty('cx') && object.hasOwnProperty('cy')) {
|
||||||
|
object.cx = goalX;
|
||||||
|
object.cy = goalY;
|
||||||
|
} else {
|
||||||
|
object.x = goalX;
|
||||||
|
object.y = goalY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000 / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateColor(number) {
|
||||||
|
let minHue;
|
||||||
|
let maxHue;
|
||||||
|
let percentage;
|
||||||
|
if (number < 10) {
|
||||||
|
minHue = 25;
|
||||||
|
maxHue = 35;
|
||||||
|
percentage = number / 10;
|
||||||
|
} else if (number < 20) {
|
||||||
|
minHue = 60;
|
||||||
|
maxHue = 80;
|
||||||
|
percentage = (number - 10) / 10;
|
||||||
|
} else if (number < 50) {
|
||||||
|
minHue = 310;
|
||||||
|
maxHue = 340;
|
||||||
|
percentage = (number - 20) / 30;
|
||||||
|
} else if (number < 100) {
|
||||||
|
minHue = 35;
|
||||||
|
maxHue = 65;
|
||||||
|
percentage = (number - 50) / 50;
|
||||||
|
} else if (number < 200) {
|
||||||
|
minHue = 260;
|
||||||
|
maxHue = 280;
|
||||||
|
percentage = (number - 100) / 100;
|
||||||
|
}
|
||||||
|
const hue = (percentage * (maxHue - minHue)) + minHue;
|
||||||
|
return `hsl(${hue}, 100%, 50%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cbElementPos(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
let obj = event.target || event.srcElement,
|
||||||
|
x = 0,
|
||||||
|
y = 0;
|
||||||
|
while (obj.offsetParent) {
|
||||||
|
x += obj.offsetLeft;
|
||||||
|
y += obj.offsetTop;
|
||||||
|
obj = obj.offsetParent;
|
||||||
|
}
|
||||||
|
return {x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cbMousePos(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
return {
|
||||||
|
x: event.pageX || event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
|
||||||
|
y: event.pageY || event.clientY + document.body.scrollTop + document.documentElement.scrollTop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cbRelMousePos(event) {
|
||||||
|
const elem = cbElementPos(event),
|
||||||
|
mouse = cbMousePos(event);
|
||||||
|
return {
|
||||||
|
x: (mouse.x - elem.x),
|
||||||
|
y: (mouse.y - elem.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame = window.requestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
window.msRequestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
function (callback) {
|
||||||
|
return setTimeout(callback, 1000 / 60);
|
||||||
|
};
|
||||||
|
|
||||||
|
Math.clamp = function (a, b, c) {
|
||||||
|
return Math.max(b, Math.min(c, a));
|
||||||
|
};
|
||||||
|
|
||||||
|
CanvasRenderingContext2D.prototype.fillCircle = function (cx, cy, radius) {
|
||||||
|
this.beginPath();
|
||||||
|
this.arc(cx, cy, radius, 0, 2 * Math.PI);
|
||||||
|
this.fill();
|
||||||
|
};
|
63
index.html
Normal file
63
index.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ballz.js</title>
|
||||||
|
<link href="style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="details">
|
||||||
|
<div class="menu-btn-container">
|
||||||
|
<div class="play-btn">
|
||||||
|
<div class="pause-btn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="high-score-container">
|
||||||
|
<span class="details-title">Best:</span>
|
||||||
|
<h3 id="highscore">0</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="score-container">
|
||||||
|
<h1 id="score">0</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="coin-container">
|
||||||
|
<span class="details-title">Coins:</span>
|
||||||
|
<h3 id="coin-count">0</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="game-container">
|
||||||
|
<canvas id="canvas" width="500" height="800"></canvas>
|
||||||
|
|
||||||
|
<div class="game-overlay" id="pause-menu">
|
||||||
|
<h2 class="game-overlay-title">Pausing</h2>
|
||||||
|
|
||||||
|
<div class="game-overlay-btns">
|
||||||
|
|
||||||
|
<button class="btn primary">Resume</button>
|
||||||
|
<button class="btn danger">Main Menu</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="game-overlay" id="game-over-menu">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="helper-functions.js"></script>
|
||||||
|
<script src="rect.js"></script>
|
||||||
|
<script src="powerup-ball.js"></script>
|
||||||
|
<script src="powerup-coin.js"></script>
|
||||||
|
<script src="ball.js"></script>
|
||||||
|
<script src="game.js"></script>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
17
main.js
Normal file
17
main.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
const sounds = {
|
||||||
|
hitBlock: new Audio('assets/hit_block.wav'),
|
||||||
|
hitCoin: new Audio('assets/hit_coin.wav'),
|
||||||
|
hitBall: new Audio('assets/hit_ball.wav'),
|
||||||
|
roundOver: new Audio('assets/round_success.wav'),
|
||||||
|
gameOver: new Audio('assets/game_over.wav')
|
||||||
|
};
|
||||||
|
|
||||||
|
let coinCount = 0;
|
||||||
|
|
||||||
|
const container = document.getElementsByClassName('container')[0];
|
||||||
|
const game = new Game(container);
|
||||||
|
game.start();
|
||||||
|
|
||||||
|
function onGameOver() {
|
||||||
|
|
||||||
|
}
|
36
powerup-ball.js
Normal file
36
powerup-ball.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
class PowerUpBall {
|
||||||
|
constructor(game, cx, cy) {
|
||||||
|
this.game = game;
|
||||||
|
this.cx = cx;
|
||||||
|
this.cy = cy;
|
||||||
|
this.anim = 0.1;
|
||||||
|
this.animChange = 0.025;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
this.game.ctx.fillStyle = '#fff';
|
||||||
|
this.game.ctx.strokeStyle = '#fff';
|
||||||
|
this.game.ctx.beginPath();
|
||||||
|
this.game.ctx.arc(this.cx, this.cy, this.game.powerUpRadius * .6, 0, 2 * Math.PI);
|
||||||
|
this.game.ctx.fill();
|
||||||
|
|
||||||
|
this.game.ctx.beginPath();
|
||||||
|
this.game.ctx.arc(this.cx, this.cy, this.game.powerUpRadius * (.6 + this.anim), 0, 2 * Math.PI);
|
||||||
|
this.game.ctx.stroke();
|
||||||
|
|
||||||
|
this.anim = this.anim + this.animChange;
|
||||||
|
if (this.anim >= .4 || this.anim <= 0.1) {
|
||||||
|
this.animChange *= -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hit() {
|
||||||
|
sounds.hitBall.play();
|
||||||
|
this.game.newBalls++;
|
||||||
|
this.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.game.powerUps.splice(this.game.powerUps.findIndex(powerUp => powerUp === this), 1);
|
||||||
|
}
|
||||||
|
}
|
24
powerup-coin.js
Normal file
24
powerup-coin.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
class PowerUpCoin {
|
||||||
|
constructor(game, cx, cy) {
|
||||||
|
this.game = game;
|
||||||
|
this.cx = cx;
|
||||||
|
this.cy = cy;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
this.game.ctx.fillStyle = '#fa0';
|
||||||
|
this.game.ctx.beginPath();
|
||||||
|
this.game.ctx.ellipse(this.cx, this.cy, Math.abs(this.game.powerUpRadius * Math.cos(this.game.coinAngle)), this.game.powerUpRadius, 0, 0, 2 * Math.PI);
|
||||||
|
this.game.ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
hit() {
|
||||||
|
sounds.hitCoin.play();
|
||||||
|
this.game.increaseCoinCount();
|
||||||
|
this.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.game.powerUps.splice(this.game.powerUps.findIndex(powerUp => powerUp === this), 1);
|
||||||
|
}
|
||||||
|
}
|
37
rect.js
Normal file
37
rect.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
class Rect {
|
||||||
|
constructor(game, x, y, number) {
|
||||||
|
this.game = game;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.number = number;
|
||||||
|
this.color = calculateColor(number);
|
||||||
|
this.lastHits = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
this.game.ctx.fillStyle = this.color;
|
||||||
|
this.game.ctx.fillRect(this.x + 2, this.y + 2, this.game.rectSize - 4, this.game.rectSize - 4);
|
||||||
|
|
||||||
|
this.game.ctx.fillStyle = '#000';
|
||||||
|
this.game.ctx.font = '20px Roboto, Arial, sans-serif';
|
||||||
|
this.game.ctx.textAlign = 'center';
|
||||||
|
this.game.ctx.textBaseline = 'middle';
|
||||||
|
this.game.ctx.fillText(this.number, this.x + this.game.rectSize / 2, this.y + this.game.rectSize / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
hit(ball) {
|
||||||
|
if (this.lastHits[ball.id] && Date.now() - this.lastHits[ball.id] < 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.number--;
|
||||||
|
this.color = calculateColor(this.number);
|
||||||
|
sounds.hitBlock.play();
|
||||||
|
|
||||||
|
if (this.number <= 0) {
|
||||||
|
this.game.rects.splice(this.game.rects.findIndex(rect => rect === this), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastHits[ball.id] = Date.now();
|
||||||
|
}
|
||||||
|
}
|
171
style.css
Normal file
171
style.css
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: #fff;
|
||||||
|
font-family: Roboto, Arial, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: none;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.primary {
|
||||||
|
background: #2272ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.danger {
|
||||||
|
background: #F72754;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 2fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details h1, .details h3, .details .details-title {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 10px auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn::before, .menu-btn-container .play-btn::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 51%;
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
content: "";
|
||||||
|
transition: .3s;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn.paused::before {
|
||||||
|
-moz-transform: translateX(-25%);
|
||||||
|
-ms-transform: translateX(-25%);
|
||||||
|
-o-transform: translateX(-25%);
|
||||||
|
-webkit-transform: translateX(-25%);
|
||||||
|
transform: translateX(-25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn.paused::after {
|
||||||
|
transform: translateX(25%)
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn.paused .pause-btn::before, .menu-btn-container .play-btn.paused .pause-btn::after {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn .pause-btn::before, .menu-btn-container .play-btn .pause-btn::after {
|
||||||
|
position: absolute;
|
||||||
|
width: 150%;
|
||||||
|
height: 100%;
|
||||||
|
background: #333;
|
||||||
|
outline: 1px solid transparent;
|
||||||
|
content: "";
|
||||||
|
transition: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn .pause-btn::before {
|
||||||
|
top: -100%;
|
||||||
|
transform: rotate(26.5deg);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn-container .play-btn .pause-btn::after {
|
||||||
|
top: 100%;
|
||||||
|
transform: rotate(-26.5deg);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container .game-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #555;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container .game-overlay.shown {
|
||||||
|
transform: none;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container .game-overlay .game-overlay-title {
|
||||||
|
margin: 45% 25px 50px;
|
||||||
|
font-size: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container .game-overlay .game-overlay-btns {
|
||||||
|
display: block;
|
||||||
|
width: 50%;
|
||||||
|
margin: 25px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container .game-overlay .game-overlay-btns .btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 16px 0 0;
|
||||||
|
}
|
205
temp.js
Normal file
205
temp.js
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
// const canvas = document.getElementById('canvas');
|
||||||
|
// const ctx = canvas.getContext('2d');
|
||||||
|
//
|
||||||
|
// let width = 500,
|
||||||
|
// height = 800;
|
||||||
|
//
|
||||||
|
// const rectSize = width / 7,
|
||||||
|
// ballRadius = 12,
|
||||||
|
// powerUpRadius = 15,
|
||||||
|
// ballVelocity = 15,
|
||||||
|
// frameRate = 60;
|
||||||
|
//
|
||||||
|
// const sounds = {
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// let roundNumber = 0;
|
||||||
|
//
|
||||||
|
// const rects = [];
|
||||||
|
// const powerUps = [];
|
||||||
|
// let balls = [];
|
||||||
|
// let ballCount = 1;
|
||||||
|
// let remainingBalls;
|
||||||
|
// let newBalls = 0;
|
||||||
|
// let originX;
|
||||||
|
// let newOriginX = 250;
|
||||||
|
// const originY = 9 * rectSize;
|
||||||
|
// let isMoving = false;
|
||||||
|
//
|
||||||
|
// let coinAngle = 0;
|
||||||
|
//
|
||||||
|
// let mousePos = null;
|
||||||
|
//
|
||||||
|
// function drawShootingLine() {
|
||||||
|
// const dx = mousePos.x - originX;
|
||||||
|
// const dy = originY - mousePos.y;
|
||||||
|
// let angle = Math.atan(dy / dx);
|
||||||
|
// let curX = originX;
|
||||||
|
// let curY = originY;
|
||||||
|
//
|
||||||
|
// if (dx < 0)
|
||||||
|
// angle += Math.PI;
|
||||||
|
//
|
||||||
|
// angle = Math.min(Math.max(angle, 0.05), Math.PI - 0.05);
|
||||||
|
//
|
||||||
|
// ctx.fillStyle = 'rgba(255, 255, 255, .5)';
|
||||||
|
// for (let i = 0; i < 10; i++) {
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.arc(curX, curY, ballRadius, 0, 2 * Math.PI);
|
||||||
|
// ctx.fill();
|
||||||
|
//
|
||||||
|
// curX += ballRadius * 4 * Math.cos(angle);
|
||||||
|
// curY -= ballRadius * 4 * Math.sin(angle);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function update() {
|
||||||
|
// balls.forEach(ball => {
|
||||||
|
// ball.update(10);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// coinAngle += .05;
|
||||||
|
//
|
||||||
|
// requestAnimationFrame(update);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function draw() {
|
||||||
|
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
//
|
||||||
|
// ctx.fillStyle = 'rgba(0,0,0,.15)';
|
||||||
|
// ctx.fillRect(0, originY + ballRadius, width, height - originX - ballRadius);
|
||||||
|
//
|
||||||
|
// rects.forEach(rect => rect.draw());
|
||||||
|
//
|
||||||
|
// powerUps.forEach(powerUp => powerUp.draw());
|
||||||
|
//
|
||||||
|
// if (originX !== null) {
|
||||||
|
// if (!isMoving && mousePos !== null) {
|
||||||
|
// drawShootingLine();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ctx.fillStyle = '#fff';
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.arc(originX, originY, ballRadius, 0, 2 * Math.PI);
|
||||||
|
// ctx.fill();
|
||||||
|
//
|
||||||
|
// if (remainingBalls > 0) {
|
||||||
|
// ctx.fillText(`${remainingBalls}x`, originX, originY + 50);
|
||||||
|
// } else {
|
||||||
|
// originX = null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if(newOriginX !== null) {
|
||||||
|
// ctx.fillStyle = '#fff';
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.arc(newOriginX, originY, ballRadius, 0, 2 * Math.PI);
|
||||||
|
// ctx.fill();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// balls.forEach(ball => {
|
||||||
|
// ball.draw();
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// requestAnimationFrame(draw);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onRoundEnd();
|
||||||
|
// update();
|
||||||
|
// draw();
|
||||||
|
//
|
||||||
|
// canvas.addEventListener('mousemove', (e) => {
|
||||||
|
// mousePos = cbRelMousePos(e);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// canvas.addEventListener('click', (e) => {
|
||||||
|
// mousePos = cbRelMousePos(e);
|
||||||
|
// shoot();
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// async function shoot() {
|
||||||
|
// if (isMoving)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// if (mousePos.y >= originY)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// isMoving = true;
|
||||||
|
// const dx = mousePos.x - originX,
|
||||||
|
// dy = originY - mousePos.y;
|
||||||
|
// let dir = Math.atan(dy / dx);
|
||||||
|
// if (dx < 0) {
|
||||||
|
// dir += Math.PI;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// dir = Math.min(Math.max(dir, 0.05), Math.PI - 0.05);
|
||||||
|
//
|
||||||
|
// for (let i = 0; i < ballCount; i++) {
|
||||||
|
// balls.push(new Ball(originX, originY, ballVelocity, dir));
|
||||||
|
// remainingBalls--;
|
||||||
|
// await sleep(100);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function onRoundEnd() {
|
||||||
|
// isMoving = false;
|
||||||
|
// roundNumber++;
|
||||||
|
// ballCount += newBalls;
|
||||||
|
// newBalls = 0;
|
||||||
|
// remainingBalls = ballCount;
|
||||||
|
// originX = newOriginX;
|
||||||
|
// newOriginX = null;
|
||||||
|
//
|
||||||
|
// rects.forEach(rect => {
|
||||||
|
// moveObject(rect, 0, rectSize, 500);
|
||||||
|
//
|
||||||
|
// if(rect.y + 2 * rectSize >= originY) {
|
||||||
|
// gameOver();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// powerUps.forEach(powerUp => {
|
||||||
|
// moveObject(powerUp, 0, rectSize, 500);
|
||||||
|
//
|
||||||
|
// if(powerUp.cy + rectSize >= originY) {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// powerUp.remove();
|
||||||
|
// }, 500);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const cols = width / rectSize;
|
||||||
|
// const spawnBall = Math.random() < .5;
|
||||||
|
// const ballPos = spawnBall ? Math.round(Math.random() * (width / rectSize - 1)) : null;
|
||||||
|
// const spawnCoin = Math.random() < .33;
|
||||||
|
// const coinPos = spawnCoin ? Math.round(Math.random() * (width / rectSize - 1)) : null;
|
||||||
|
// for (let i = 0; i < cols; i++) {
|
||||||
|
// if (spawnBall && i === ballPos) {
|
||||||
|
// const powerUp = new PowerUpBall((i + .5) * rectSize, .5 * rectSize);
|
||||||
|
// powerUps.push(powerUp);
|
||||||
|
//
|
||||||
|
// moveObject(powerUp, 0, rectSize, 500);
|
||||||
|
// } else if (spawnCoin && i === coinPos) {
|
||||||
|
// const powerUp = new PowerUpCoin((i + .5) * rectSize, .5 * rectSize);
|
||||||
|
// powerUps.push(powerUp);
|
||||||
|
//
|
||||||
|
// moveObject(powerUp, 0, rectSize, 500);
|
||||||
|
// } else {
|
||||||
|
// if (Math.random() < .6) {
|
||||||
|
// const number = Math.random() < .5 ? roundNumber : 2 * roundNumber;
|
||||||
|
// const rect = new Rect(i * rectSize, 0, number);
|
||||||
|
// rects.push(rect);
|
||||||
|
//
|
||||||
|
// moveObject(rect, 0, rectSize, 500);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// sounds.roundOver.play();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// function gameOver() {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
Loading…
Reference in New Issue
Block a user