game with scoring
#myCanvas {
border: 1px solid black;
float: left;
}
#controls {
float: left;
}
#panelRight {
margin-left: 20px;
float: left;
}
class Ball {
constructor(x, y, radius, speedX, speedY, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.speedX = speedX;
this.speedY = speedY;
this.color = color;
}
draw() {
// GOOD practice: save the context, use 2D trasnformations
ctx.save();
// translate the coordinate system, draw relative to it
ctx.translate(this.x, this.y);
ctx.fillStyle = this.color;
ctx.fillStyle = this.color;
// (0, 0) is the top left corner of the monster.
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, 2 * Math.PI);
ctx.fill();
// GOOD practice: restore the context
ctx.restore();
}
move(globalSpeedMutiplier) {
this.x += (this.speedX * globalSpeedMutiplier);
this.y += (this.speedY * globalSpeedMutiplier);
this.testCollisionBallWithWalls();
}
testCollisionBallWithWalls() {
// COLLISION WITH VERTICAL WALLS ?
if ((this.x + this.radius) > w) {
// the ball hit the right wall
// change horizontal direction
this.speedX = -this.speedX;
// put the ball at the collision point
this.x = w - this.radius;
} else if ((this.x - this.radius) < 0) {
// the ball hit the left wall
// change horizontal direction
this.speedX = -this.speedX;
// put the ball at the collision point
this.x = this.radius;
}
// COLLISIONS WTH HORIZONTAL WALLS ?
// Not in the else as the ball can touch both
// vertical and horizontal walls in corners
if ((this.y + this.radius) > h) {
// the ball hit the right wall
// change horizontal direction
this.speedY = -this.speedY;
// put the ball at the collision point
this.y = h - this.radius;
} else if ((this.y - this.radius) < 0) {
// the ball hit the left wall
// change horizontal direction
this.speedY = -this.speedY;
// put the ball at the collision point
this.Y = this.radius;
}
}
}
class Balls {
constructor(n, globalSpeedMutiplier) {
this.ballsArray = [];
// create n balls
for (var i = 0; i < n; i++) {
var ball = new Ball(w / 2, h / 2, 5 + 20 * Math.random(), -2 + 4 * Math.random(), -2 + 4 * Math.random(), getARandomColor());
this.ballsArray.push(ball);
}
this.globalSpeedMutiplier = globalSpeedMutiplier;
}
add(x, y, radius, speedX, speedY, color) {
let ball = new Ball(x, y, radius, speedX, speedY, color);
this.ballsArray.push(ball);
}
draw() {
for (var i = 0; i < this.ballsArray.length; i++) {
this.ballsArray[i].draw();
};
}
drawBallNumbers() {
ctx.save();
ctx.font = "20px Arial";
if (gameState === "displayGameOverMenu") {
ctx.fillText("Game Over!", 20, 30);
// Display Game Over Menu
restartEl.style.display = "block";
// keep game score on Local Store
let ballGames = JSON.parse(localStorage.ballGames);
var d = new Date();
let ballGame = { 'date': d, 'level': gameLevel };
ballGames.push(ballGame);
ballGames.sort(function(a, b) {
return b.level - a.level;
});
localStorage.ballGames = JSON.stringify(ballGames);
let end = 10;
if (ballGames.length < end) {
end = ballGames.length;
}
for (var i = 0; i < end; i++) {
let pEl = document.createElement('p');
ballGames[i].date = new Date(ballGames[i].date);
pEl.innerText = '' + (i + 1) + ' level: ' + ballGames[i].level + ' date: ' + ballGames[i].date.getFullYear() + '/' + ballGames[i].date.getMonth() + '/' + ballGames[i].date.getDate() + ' ' + ballGames[i].date.getHours() + ":" + ballGames[i].date.getMinutes() + ":" + ballGames[i].date.getSeconds();
historyEl.append(pEl);
}
return;
} else if (goodBallsEaten === numberOfGoodBalls) {
/* ctx.fillText("You Win! Final score : " + (initialNumberOfBalls - wrongBallsEaten),
20, 30); */
gameLevel++;
gameLevelEl.innerHTML = 'Game Level: ' + gameLevel;
numberOfBalls++;
startGame();
} else {
ctx.fillText("Balls still alive: " + this.ballsArray.length, 210, 30);
ctx.fillText("Good Balls eaten: " + goodBallsEaten, 210, 50);
ctx.fillText("Wrong Balls eaten: " + wrongBallsEaten, 210, 70);
}
ctx.restore();
}
move() {
for (var i = 0; i < this.ballsArray.length; i++) {
this.ballsArray[i].move(this.globalSpeedMutiplier);
this.testCollisionWithPlayer(i);
};
}
countNumberOfGoodBalls(colorToEat) {
var nb = 0;
for (var i = 0; i < this.ballsArray.length; i++) {
if (this.ballsArray[i].color === colorToEat) {
nb++;
}
};
return nb;
}
testCollisionWithPlayer(index) {
if (circRectsOverlap(player.x, player.y,
player.width, player.height,
this.ballsArray[index].x, this.ballsArray[index].y, this.ballsArray[index].radius)) {
// we remove the element located at index
// from the balls array
// splice: first parameter = starting index
// second parameter = number of elements to remove
if (this.ballsArray[index].color === colorToEat) {
// Yes, we remove it and increment the score
goodBallsEaten += 1;
} else {
wrongBallsEaten += 1;
if (wrongBallsEaten >= numberOfWrongBalls) {
gameState = "displayGameOverMenu";
}
}
balls.ballsArray.splice(index, 1);
}
}
}
function getARandomColor() {
var colors = ['red', 'blue', 'cyan', 'purple', 'pink', 'green', 'yellow'];
// a value between 0 and color.length-1
// Math.round = rounded value
// Math.random() a value between 0 and 1
var colorIndex = Math.round((colors.length - 1) * Math.random());
var c = colors[colorIndex];
// return the random color
return c;
}
function changeNbBalls(nb) {
numberOfBalls = nb;
startGame();
}
function changeNbWrongBalls(nb) {
numberOfWrongBalls = nb;
startGame();
}
function changeColorToEat(color) {
colorToEat = color;
}
function changePlayerColor(color) {
player.color = color;
}
function changeBallSpeed() {
balls.globalSpeedMutiplier = speedEl.value * 1;
}
// useful to have them as global variables
var canvas, ctx, w, h;
var mousePos;
var historyEl;
// Game level
var gameLevel;
// Game State
var gameState;
// Number of Balls
var numberOfBalls;
var numberOfBallsEl;
// Number of Wrong Balls
var numberOfWrongBalls;
var numberOfWrongBallsEl;
var numberOfWrongBallsMax;
// Level Element
var gameLevelEl;
// Restart Element
var restartEl;
// Speed Element
var speedEl;
var player = {
x: 10,
y: 10,
width: 20,
height: 20,
color: 'red'
}
let colorToEat = 'red';
window.onload = function init() {
// called AFTER the page has been loaded
canvas = document.querySelector("#myCanvas");
// check local Storage
if (localStorage.ballGames === undefined) {
let ballGames = [];
localStorage.ballGames = JSON.stringify(ballGames);
}
// Number of Balls
numberOfBallsEl = document.querySelector("#nbBalls");
numberOfBalls = numberOfBallsEl.value * 1; // make sure it's a number
numberOfWrongBallsEl = document.querySelector("#nbWrongBalls");
numberOfWrongBalls = numberOfWrongBallsEl.value * 1; // make sure it's a number
// Sped
speedEl = document.querySelector("#ballSpeed");
speedEl.addEventListener("input", changeBallSpeed);
// Game Level
gameLevelEl = document.querySelector("#gameLevel");
// Game Over Menu
restartEl = document.querySelector("#restart");
restartEl.addEventListener("click", init);
// History
historyEl = document.querySelector("#history");
// often useful
w = canvas.width;
h = canvas.height;
// important, we will draw with this object
ctx = canvas.getContext('2d');
// Game Level
gameLevel = 1;
// start game
startGame();
// add a mousemove event listener to the canvas
canvas.addEventListener('mousemove', mouseMoved);
// ready to go !
mainLoop();
};
function startGame() {
do {
balls = new Balls(numberOfBalls, speedEl.value * 1);
initialNumberOfBalls = numberOfBalls;
numberOfGoodBalls = balls.countNumberOfGoodBalls(colorToEat);
} while (numberOfGoodBalls === 0);
wrongBallsEaten = goodBallsEaten = 0;
gameState = "gameRunning";
// Game Level
gameLevelEl.innerHTML = 'Game Level: ' + gameLevel;
// Hide Game Over Menu
restartEl.style.display = "none";
// Delete Score Standings
historyEl.innerHTML = '';
}
function mouseMoved(evt) {
mousePos = getMousePos(canvas, evt);
}
function getMousePos(canvas, evt) {
// necessary work in the canvas coordinate system
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function movePlayerWithMouse() {
if (mousePos !== undefined) {
player.x = mousePos.x;
player.y = mousePos.y;
}
}
function mainLoop() {
// 1 - clear the canvas
ctx.clearRect(0, 0, w, h);
// draw the ball and the player
drawFilledRectangle(player);
balls.draw();
balls.drawBallNumbers();
if (gameState === "displayGameOverMenu") {
return;
}
// animate the ball that is bouncing all over the walls
balls.move();
movePlayerWithMouse();
// ask for a new animation frame
requestAnimationFrame(mainLoop);
}
// Collisions between rectangle and circle
function circRectsOverlap(x0, y0, w0, h0, cx, cy, r) {
var testX = cx;
var testY = cy;
if (testX < x0) testX = x0;
if (testX > (x0 + w0)) testX = (x0 + w0);
if (testY < y0) testY = y0;
if (testY > (y0 + h0)) testY = (y0 + h0);
return (((cx - testX) * (cx - testX) + (cy - testY) * (cy - testY)) < r * r);
}
function drawFilledRectangle(r) {
// GOOD practice: save the context, use 2D trasnformations
ctx.save();
// translate the coordinate system, draw relative to it
ctx.translate(r.x, r.y);
ctx.fillStyle = r.color;
// (0, 0) is the top left corner of the monster.
ctx.fillRect(0, 0, r.width, r.height);
// GOOD practice: restore the context
ctx.restore();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Animating a ball</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Draw a monster in a canvas</title>
</head>
<body>
<div id="controls">
<label for="nbBalls">Number of balls: </label>
<input type="number" min=1 max=30 value=10 id="nbBalls" oninput="changeNbBalls(this.value);">
<p></p>
<label for="nbWrongBalls">Number of Wrong balls: </label>
<input type="number" min=1 max=5 value=3 id="nbWrongBalls" oninput="changeNbWrongBalls(this.value);">
<p></p>
<label for="colorChooser">Player color: </label>
<input type="color" value='#FF0000' oninput="changePlayerColor(this.value);" id="colorChooser">
<p></p>
<label for="selectColorOfBallToEat">Color of ball to eat: </label>
<select onchange="changeColorToEat(this.value);" id="selectColorOfBallToEat">
<option value='red'>red</option>
<option value='blue'>blue</option>
<option value='green'>green</option>
</select>
<p></p>
<label for="ballSpeed">Change ball speed: </label> - <input type="range" value='1' min=0.1 max=3 step=0.1 id="ballSpeed"> +
<p></p>
</div>
<canvas id="myCanvas" width="400" height="400"></canvas>
<section id="panelRight">
<p id="gameLevel"></p>
<button id="restart">Restart</button>
<div id="history"></div>
</section>
</body>
</html>
<script src="js/balls.js"></script>
<script src="js/index.js"></script>
</body>
</html>