<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Bouncing balls</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>bouncing balls</h1>
<p>Ball count: </p>
<canvas></canvas>
<script src="main5.js"></script>
</body>
</html>
html, body {
margin: 0;
}
html {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
}
body {
overflow: hidden;
height: inherit;
}
h1 {
font-size: 2rem;
letter-spacing: -1px;
position: absolute;
margin: 0;
top: -4px;
right: 5px;
color: transparent;
text-shadow: 0 0 4px white;
}
/* 5. Implementing the score counter */
p {
position: absolute;
margin: 0;
top: 35px;
right: 5px;
color: #aaa;
}
// set up canvas
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
// function to generate random number
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// function to generate random RGB color value
function randomRGB() {
return `rgb(${random(0, 255)},${random(0, 255)},${random(0, 255)})`;
}
// 5. Ball count (1/3)
const para = document.querySelector('p');
let count = 0;
// 1. Create a Shape class
class Shape {
constructor(x, y, velX, velY) {
this.x = x;
this.y = y;
this.velX = velX;
this.velY = velY;
}
}
class Ball extends Shape {
constructor(x, y, velX, velY, color, size) {
super(x, y, velX, velY);
this.color = color;
this.size = size;
this.exists = true; // NOTE: couldn't figure this out by myself
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
}
update() {
if ((this.x + this.size) >= width) {
this.velX = -(this.velX);
}
if ((this.x - this.size) <= 0) {
this.velX = -(this.velX);
}
if ((this.y + this.size) >= height) {
this.velY = -(this.velY);
}
if ((this.y - this.size) <= 0) {
this.velY = -(this.velY);
}
this.x += this.velX;
this.y += this.velY;
}
collisionDetect() {
for (const ball of balls) {
if (!(this === ball) && ball.exists) {
const dx = this.x - ball.x;
const dy = this.y - ball.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + ball.size) {
ball.color = this.color = randomRGB();
}
}
}
}
}
// 2. Defining EvilCircle
class EvilCircle extends Shape {
constructor(x, y) {
super(x, y, 20, 20);
this.color = "white"; // NOTE: I originally forgot to add quote marks
this.size = 10;
window.addEventListener('keydown', (e) => {
switch(e.key) {
case 'a':
this.x -= this.velX;
break;
case 'd':
this.x += this.velX;
break;
case 'w':
this.y -= this.velY;
break;
case 's':
this.y += this.velY;
break;
}
});
}
// 3. Defining methods for EvilCircle
draw() {
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.stroke();
}
checkBounds() {
if ((this.x + this.size) >= width) {
this.x -= this.size;
}
if ((this.x - this.size) <= 0) {
this.x += this.size;
}
if ((this.y + this.size) >= height) {
this.y -= this.size;
}
if ((this.y - this.size) <= 0) {
this.y += this.size;
}
}
collisionDetect() {
for (const ball of balls) {
if (ball.exists) { // NOTE: I wasn't sure but was correct
const dx = this.x - ball.x;
const dy = this.y - ball.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + ball.size) {
ball.exists = false;
/* QUESTION 1: I originally wrote "ball.exists === false" but then the count kept declining.
Why should this be "=" instead of "==="?
Answer 1:
We don’t want to compare the values, we want to assign false to ball.exists.
The reason is that when the condition (distance < this.size + ball.size) is met,
we have a collision and want to remove the ball.
Therefor we set its exists property to false.
*/
// 5. Ball count (2/3)
count--;
para.textContent = `Ball count: ${count}`;
/* QUESTION 2: Is there any way I don't have to re-write "Ball count: "
but somehow utilise the text in <p></p> in HTML?
Answer 2:
If you absolutely want to, you could have a span inside the para and only change its content:
HTML:
<p>Ball count: <span></span></p>
JS:
const span = document.querySelector('span');
...
span.textContent = count;
*/
}
}
}
}
}
const balls = [];
while (balls.length < 25) {
const size = random(10,20);
const ball = new Ball(
// ball position always drawn at least one ball width
// away from the edge of the canvas, to avoid drawing errors
random(0 + size, width - size),
random(0 + size, height - size),
random(-7,7),
random(-7,7),
randomRGB(),
size
);
balls.push(ball);
// 5. Ball count (3/3)
count++;
para.textContent = `Ball count: ${count}`;
}
// 4. Bringing the evil circle into the program
const evilCircle = new EvilCircle(
random(0, width), random(0, height)
/* QUESTION 3: I originally set the x and y of evil circle as below,
but that way, nothing was displayed on screen. Why does this not work?
random(0 + size, width - size),
random(0 + size, height - size)
Answer 3:
When you encounter such errors I recommend looking at the console.
There we see: “ReferenceError: size is not defined”.
There reason is that the size variable is defined inside the EvilCircle class
and is unknown to the global code that creates the evil circle.
*/
);
function loop() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(0, 0, width, height);
for (const ball of balls) {
if(ball.exists) {
ball.draw();
ball.update();
ball.collisionDetect();
}
}
evilCircle.draw();
evilCircle.checkBounds();
evilCircle.collisionDetect();
requestAnimationFrame(loop);
}
loop();
/* NOTE: my original attempt of 5. Ball count (didn't work)
const para = document.querySelector('p');
let count = balls.length; <- This was incorrect!
for (ball of balls) {
if (ball.exist) { <- This doesn't mean when a ball is pushed
count++;
} else if (!ball.exist) {
count--;
}
}
para.textContent += `${count}`; <- This showed all numbers from 0 to 25
*/
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.