"Adding features to our bouncing balls demo" assessment

Hi Mozillians,

Having logic trouble re: Adding features to our bouncing balls demo. (Re-post from: Introducing JavaScript objects: Assessment: Adding features to our bouncing balls demo

Note: I’m trying to figure this out without looking at the solution. So far, I can generate/draw the evil circle on the page; however, when I add the other 3 evil methods (collisionDetect, setControls, checkBounds) the code breaks.

More so, my set up for the exist property does not make working sense. Finally, the counter is saved for last until I can figure out the evilCircle methods.

Please see code in JSFiddle.

Current code with comments below:

// setup canvas
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");

var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;

// ball counter setup
//var counterText = document.getElementById("#counter");
//counterText.textContent = 0;

// function to generate random number
function random(min, max) {
  var num = Math.floor(Math.random() * (max - min)) + min;
  return num;
}

// define Shape constructor
function Shape(x, y, velX, velY, exists) {
  this.x = x;
  this.y = y;
  this.velX = velX;
  this.velY = velY;
  this.exists = true;
}

// define Ball constructor
function Ball(x, y, velX, velY, color, size, exists) {
  Shape.call(this, x, y, velX, velY, exists);

  this.color = color;
  this.size = size;
}

Ball.prototype = Object.create(Shape.prototype);
Ball.prototype.constructor = Ball;

// define ball draw method
Ball.prototype.draw = function() {
  ctx.beginPath();
  ctx.fillStyle = this.color;
  ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
  ctx.fill();
}

// define ball's update method
Ball.prototype.update = function() {
  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;
}

// define ball's collison detection
Ball.prototype.collisionDetect = function() {
  for (var j = 0; j < balls.length; j++) {
    if (!(this === balls[j])) {
      var dx = this.x - balls[j].x;
      var dy = this.y - balls[j].y;
      var distance = Math.sqrt(dx * dx + dy * dy);

      if (distance < this.size + balls[j].size) {
        balls[j].color = this.color = "rgb(" + random(0, 255) + "," + random(0, 255) + "," + random(0, 255) + ")";
      }
    }
  }
};

// define EvilCircle()
function EvilCircle(x, y, exists) {
  Shape.call(this, x, y, exists);

  this.color = 'white';
  this.size = 50;
  this.x = 400;
  this.y = 100;

  //this.exists = true;
}

// need to better understand the assignments for prototype and Obj.create
EvilCircle.prototype = Object.create(Shape.prototype);
EvilCircle.prototype.constructor = EvilCircle; // why do we need to do this?


// define EvilCircle methods
EvilCircle.prototype.draw = function() {
  //  Ball.prototype.draw.call(this); // can I do this?
  //    ctx.fillStyle = ctx.strokeStyle = this.color;  // and this?

  ctx.beginPath();
  ctx.lineWidth = 10;
  ctx.strokeStyle = 'white';

  ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
  ctx.stroke();

};

// checkBounds()
/*
EvilCircle.prototype.checkBounds = function () {
  if ((this.x + this.size >= width)) {
    this.x = -(this.x);
  }
  if ((this.x - this.size <= 0)) {
    this.x = -(this.x);
  }
  if ((this.y + this.size) >= height) {
    this.y = -(this.y);
  }
  if ((this.y - this.size) <= 0) {
    this.y = -(this.y);
  }
};
*/

// setControls()
/*
EvilCircle.prototype.setControls = function () {
  var _this = this;
  window.onkeydown = function (e) {
    if (e.keyCode === 65) {
      _this.x -= _this.velX;
    } else if (e.keyCode === 68) {
      _this.x += _this.velX;
    } else if (e.keyCode === 87) {
      _this.y -= _this.velY;
    } else if (e.keyCode === 83) {
      _this.y += _this.velY;
    }
  }
};
*/

// collisionDetect()
/*
EvilCircle.prototype.collisionDetect = function () {
  for (var j = 0; j < balls.length; j++) {
    if (balls[j].exists) {
      var dx = this.x - balls[j].x;
      var dy = this.y - balls[j].y;
      var distance = Math.sqrt(dx * dx + dy * dy);

      if (distance < this.size + balls[j].size) {
        balls[j].exists = this.exists = false;
      }
    }
  }
};
*/

// define array to store balls
var balls = [];

// define loop that keeps drawing the scene constantly
function loop() {
  ctx.fillStyle = "rgba(0,0,0,0.25)";
  ctx.fillRect(0, 0, width, height);

  // create EvilCircle instance
  var evil = new EvilCircle();
  evil.draw();
  // evil.setControls();

  while (balls.length < 25) {
    var ball = new Ball(
      random(0, width),
      random(0, height),
      random(-7, 7),
      random(-7, 7),
      "rgb(" + random(0, 255) + "," + random(0, 255) + "," + random(0, 255) + ")",
      random(10, 20)
    );
    balls.push(ball);
  }

  for (var i = 0; i < balls.length; i++) {

    //  if (ball !== exist) {
    balls[i].draw();
    balls[i].update();
    balls[i].collisionDetect();
    //  }
  }

  //   evil.draw();
  //   evil.checkBounds();
  //   evil.collisionDetect();

  requestAnimationFrame(loop);
}

// start animation
loop();

Hi, may I have the marking guide for the Adding features to Our Bouncing Balls Demo assessment, please?

I made it through most of the assessment, but I can’t figure out how to make the counter work. I have no idea how to reference when a ball no longer exist and then make the counter decrement only one time. It just keeps subtracting one from the score as soon as one ball no longer exist, and I get the dreaded infinite loop.

Thanks in advance!

Hi there,

Here is the marking guide:

Although to help you fix your issue, you might be better just checking your code against our version:

It sounds like it should be a fairly simple problem to overcome.

If you still run into trouble, don’t hesitate to ask, and I’ll give you some more help.

best,

Chris

Thank you so much! I actually understood more than I thought I did. I just defined the checkBounds() function incorrectly and my evilCircle was checking to see if itself existed instead of checking to see if balls[j] existed. My thinking of how to make the counter work/update was correct though.

Sorry for the unnecessary explanation, but I learn better when I explain stuff. lol

Hey, no problem — I do too! I am glad you have figured it out OK.

Hi MDN peeps, regarding the adding features to bouncing balls portion of the guide, I just like to ask why changing exists property from true to false would remove the ball out of existence from the window? I was thinking deleting the ball from the array would do it (delete balls[j]) but then when a ball would hit my Evilcircle the program would crash. I was able to get the program to work via trial and error, but I still dont understand the logic behind it. Help clarify this for me, would appreciate it :slight_smile: thanks!.

code2

The logic is a little odd here; I could probably have come up with a more elegant solution here, but I remember finding it difficult to come up with something that worked, quickly :wink: The problem was around the fact that array items don’t have a specific index to identify them, and it is not easy to just delete one anywhere in the array. There is probably a better way.

Let’s go through the logic, referencing the finished code here:

  1. The exists property is defined in line 28
  2. exists is inherited by the Ball() and EvilCircle() constructors in lines 34 and 97
  3. in the collisionDetect() method (see lines 159-173) we see the first place exists is used. We check to see if each ball is colliding with the evil circle. If the ball doesn’t exist (line 161), we check its collision detection — if it is colliding with the evil circle (line 166), we set its exists property to false (line 167) and update the number of balls count (lines 168-169)
  4. Inside the main game loop (specifically, inside lines 205-211) we see the second place exists is used — here we check to see whether each ball exists (line 206). Only if it does do we bother to draw(), update() position and collisionDetect() it. (Bear in mind this is the ball’s collisionDetect() method to see if it is colliding with the other balls, not the evilCircle() collision detect).

So effectively, “exists” is not really accurate — balls that don’t exist (exists is false) aren’t deleted or anything. They are actually just no longer drawn, moved or collision detected, so are no longer seen in the game.

I hope this helps. Let me know if you need any more explanation.

It totally makes sense! So essentially if exists is false for given ball, it is not animated. Great explanation! Thanks for that quick and detailed feedback, appreciate it.

Just want to say thank you for the guys who made this guide. Started not knowing what an HTML tag is a month ago and now Im able to read and understand code. You guys rock!

1 Like

Cool, you’re welcome! And thanks for the kind words — I am the main writer of the MDN beginner’s material, and it really means a lot to me to know you are finding it so helpful.

Hello i just completed the " Adding features to our bouncing balls demo " assessment and would appreciate a marking/any feedback. Thanks.

<!DOCTYPE html>
<html lang="en-US">
  
  <head>
    <meta charset="utf-8" />
    <meta name="author" content="Koffi Eli KPONBLANOU" />
    <title>EXERCISING OBJECTS</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
    <style>
      body{
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      
      #compteur {
        position: absolute;
        right: 20px;
        left: 20px;
        color: #fff;
      }
    </style>
    
  </head>
  
  <body>
    <p id="compteur"></p>
    <canvas id="blackboard"></canvas>
    
    <script>
      // Get a reference to the canvas element and store it into a variable
      var blackBoard = document.querySelector("#blackboard");
      
      // Define a 2d context fot drawind on the canvas
      var blackBoardContext = blackBoard.getContext("2d");
      
      // Get a reference to the p element that counts the number of our balls
      var compteur = document.querySelector('#compteur');
      // Define the canvas width and height
      blackBoardWidth = blackBoard.width = window.innerWidth;
      blackBoardHeight = blackBoard.height = window.innerHeight;
      
      blackBoardContext.fillStyle = "#eee";
      blackBoardContext.fillRect(0, 0, blackBoardWidth, blackBoardHeight);
      
      // Create a generic shape Object
      function Shape(coordX, coordY, horizontalVelocity, verticalVelocity, radius) {
        // the coordinates variables
        this.coordX = coordX;
        this.coordY = coordY;
        
        // the horizontal and vertical velocity of our shape
        this.horizontalVelocity = horizontalVelocity;
        this.verticalVelocity = verticalVelocity;
        
        // The circle radius
        this.radius = radius;
        
       // By default a created ball exists
        this.exists = true;
      }
      
      
         // Add position update method to the Ball's prototype
      Shape.prototype.update = function(){
        // Change the direction of the ball on x axis
        // when it touches the right edge of the canvas
        if( (this.coordX + this.radius )  >= blackBoardWidth ) {
          this.horizontalVelocity = - ( this.horizontalVelocity );
        }
        
        // Change the direction of the ball on x axis
        // when it touches the left edge of the canvas
        if( ( this.coordX - this.radius ) <= 0 ) {
          this.horizontalVelocity = - (this.horizontalVelocity);
        }
        
        // Change the direction of the ball on Y axis
        // when it touches the bottom edge of the canvas
        if( (this.coordY + this.radius ) >= blackBoardHeight ) {
          this.verticalVelocity = - ( this.verticalVelocity );
        }
        
        // change the direction of the ball on Y axis
        // when it touches the bottom edge of the canvas
        if( (this.coordY - this.radius ) <= 0 ) {
          this.verticalVelocity = - ( this.verticalVelocity);
        }
        
        this.coordX += this.horizontalVelocity;
        this.coordY += this.verticalVelocity;
      }
      
      
      // Create a Ball constructor that inherits from our CircleShape consctructor
      function Ball(coordX, coordY, horizontalVelocity, verticalVelocity, radius, ballColor) {
        Shape.call(this, coordX, coordY, horizontalVelocity, verticalVelocity, radius, radius);
        
        // The color of our ball
        this.ballColor = ballColor;
      }
      
      
      // Inherit methods from Shape constructor
      Ball.prototype = Object.create(Shape.prototype);
      Ball.prototype.constructor = Ball;
      
      
      // Add a draw method to the prototype of Ball constructor
      Ball.prototype.drawBall = function() {
        blackBoardContext.beginPath();
        blackBoardContext.fillStyle = this.ballColor;
        blackBoardContext.arc(this.coordX, this.coordY, this.radius, 0, 2*Math.PI);
        blackBoardContext.fill();
      }
      
      
      // Ball collision Detection
      Ball.prototype.detectCollision = function() {
        for(var j = 0; j < balls.length ; j++) {
          if( !(this === balls[j] )) {
            
            var dx = this.coordX - balls[j].coordX;
            var dy = this.coordY - balls[j].coordY;
            var distance = Math.sqrt(dx*dx + dy*dy);
            if( (distance - this.radius) <= balls[j].radius  ) {
              if(this.ballColor !== balls[j].ballColor) {
                this.ballColor = balls[j].ballColor;
              } else {
                this.ballColor = 'rgb('+random(0, 355)+', '+random(0, 255)+', '+random(0, 255)+')';
              }
            }
          }
        }
      }
      
      
      /**
       * Define the EvilCircle constructor
       * */
       function  EvilBall(coordX, coordY, horizontalVelocity, verticalVelocity, radius, shapeColor) {
          Shape.call(this, coordX, coordY, horizontalVelocity, verticalVelocity, radius);
          this.shapeColor = shapeColor;
       }
       
       // EvilBall inherits methods from Shape consctructor
       EvilBall.prototype = Object.create(Shape.prototype);
       EvilBall.prototype.constructor = EvilBall;
       
       // Define a draw method on the prototype of Evilcircle
       EvilBall.prototype.drawEvil = function() {
          blackBoardContext.beginPath();
          blackBoardContext.lineWidth = 2;
          blackBoardContext.strokeStyle = this.shapeColor;
          blackBoardContext.arc(this.coordX, this.coordY, this.radius, 0, 2*Math.PI);
          blackBoardContext.stroke();
       }
       
       
       // Check the window bounds for the evil ball
       EvilBall.prototype.checkBounds = function() {
         if( (this.coordX + this.radius) >= blackBoardWidth ) {
           this.coordX = this.radius;
         }
         
         if( (this.coordX + this.radius) <= 0) {
           this.coordX = blackBoardWidth - this.radius ;
         }
         
         if( (this.coordY + this.radius) >= blackBoardHeight ) {
           this.coordY = this.radius;
         }
         
         if( (this.coordY + this.radius) <= 0  ) {
           this.coordY = blackBoardHeight - this.radius;
         }
       }
       
       // Move the evil Circle with the keyBoard
       EvilBall.prototype.setControls = function() {
         var _this = this;
         window.onkeydown = function(e) {
            // Avant -> 39
            // Arriere -> 37
            // Haut -> 38
            // bas -> 40
            if( e.keyCode === 39 ) {
              _this.checkBounds();
             _this.coordX -= _this.horizontalVelocity; 
            } 
            
            if( e.keyCode === 37 ) {
              _this.checkBounds();
              _this.coordX += _this.horizontalVelocity;
            }
            
            if( e.keyCode === 38 ) {
              _this.checkBounds();
             _this.coordY -= _this.verticalVelocity;
            }
            
            if( e.keyCode === 40 ) {
              _this.checkBounds();
              _this.coordY += _this.verticalVelocity;
            }
            
         }
       }
       
             // Create a new Evil

      var oneBall = new Ball(200, 200, -7, 7, 40, 'yellow');
       
       // Define a collision Detect function for the evil Ball
       EvilBall.prototype.evilCollisionDetect = function() {
          for( var k = 0; k < balls.length; k++) {
            if( balls[k].exists ) {
              
              var dx = evilBall.coordX - balls[k].coordX;
              var dy = evilBall.coordY - balls[k].coordY;
              var distance = Math.sqrt( dx*dx + dy*dy );
               if( (distance - evilBall.radius) <= balls[k].radius ) {
                  balls[k].exists = false;
               }
            }
          }
         
           /*var dx = this.coordX - evilBall.coordX;
           var dy = this.coordY - evilBall.coordY;
           var distance = Math.sqrt( dx*dx + dy*dy );
           if( (distance - evilBall.radius)  <= this.radius ){
             console.log('There is a collision');
           }*/
       }
      
      var evilBall= evilBall = new EvilBall(random(0, blackBoardWidth), 
                                            random(0, blackBoardHeight),
                                            -3, 3, 8, '#fff');
      evilBall.setControls();
       
      //  Generate a random number based on a minimum and a maximum
      function random(min, max) {
        var num = Math.floor( Math.random() * (max - min + 1 ) ) + min;
        return num;
      }
      
      
      var balls = [];
      var defaultNumber = 25;
      // The game loop
      function gameLoop() {

        blackBoardContext.fillStyle = "rgba(0,0,0, .25)";
        blackBoardContext.fillRect(0, 0, blackBoardWidth, blackBoardHeight);
        while( balls.length  < defaultNumber && defaultNumber >= 1 ) {
      
          // Create 25 balss with random Data
          var ball = new Ball( random(0, blackBoardWidth), 
                              random(0, blackBoardHeight) , 
                              random(-7, 7), random(-7, 7), 
                              random(0, 25), 
                             'rgb('+random(0, 355)+', '+random(0, 255)+', '+random(0, 255)+')' 
          );
          balls.push(ball);
        }
        
        for( var i = 0 ; i < balls.length ; i++) {
          evilBall.drawEvil();
          evilBall.evilCollisionDetect();
          
          if( balls[i].exists ) {
            
            balls[i].drawBall();
            balls[i].update();
            balls[i].detectCollision();
            
          } else {
            balls = balls.splice( balls.indexOf( balls[i], 1 ) );
            defaultNumber--;
          }
          
        }
        compteur.textContent = "Nombre total de balles: "+balls.length;
        requestAnimationFrame( gameLoop );
      }
      
      gameLoop();
      
    </script>
  </body>
  
</html>

Hi there! Congratulations on completing the OOJS module.

In terms of marking, I’d like to encourage you to look over the marking guide for this lesson, and also to compare it against our version of the code to make sure the functionality works as expected.

For a quick look at your code, everything looks mostly in order, although:

  1. The keyboard controls don’t work, or are not implemented.
  2. Every time a ball collides with the “evil circle”, all the existing ball positions seem to be redrawn. I am not sure what is going on there :wink:

I have reviewed the code and correct should be corrected.
About the controls, i use the Keyboard arrows instead of letters.
So:

  • Arrow Right to move the evil circle Forward

  • Arrow left to move the evil circle Backward

  • Arrow Up to move the evil circle Upward

  • Arrow down to move the evil circle DownWard


This is my reviewed Code:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title> BOUNCING BALLS </title>
<style>
    body {
        margin: 0;
        padding: 0;
        overflow: hidden;
    }

    .counter-container {
        color: #fff;
        position: absolute;
        font-weight: bold;
        top: 25px;
        left: 25px;
    }

</style>
</head>

<body>
<div class="counter-container">Remain balls: <span id="counter"></span></div>
<canvas id="black-board"></canvas>

<script>
    // Get a reference to the canvas and store it into a variable
    var blackBoard = document.querySelector('#black-board');

    // Get a reference to the counter and store it into a variable
    var counter = document.querySelector('#counter');

    // Set a 2d context on the canvas
    var blackBoardContext = blackBoard.getContext('2d');

    // Set the scene width and height
    var blackBoardWidth = blackBoard.width = window.innerWidth;
    var blackBoardHeight = blackBoard.height = window.innerHeight;

    // FDraw the sene and make it fit the window of the browser
    blackBoardContext.fillStyle = 'rgba(0,0,0, .25)';
    blackBoardContext.fillRect(0, 0, blackBoardWidth, blackBoardHeight);

    // Define the random number generator function
    function random(min, max) {
        var num = Math.floor( Math.random() * (max - min + 1 ) ) + min;
        return num;
    }

    // Define a shape Object constructor
    function Shape(coordX, coordY, horizontalVelocity, verticalVelocity, size) {
        // Coordinates of the shape
        this.coordX = coordX;
        this.coordY = coordY;

        // Define a velocity for the shape movement
        this.horizontalVelocity = horizontalVelocity;
        this.verticalVelocity = verticalVelocity;

        //Define the size of the shape
        this.size = size;
    }

    /*
    * Define the ball Object
    * The Ball Object will inherit from the Shape Object
    */
    function Ball(coordX, coordY, horizontalVelocity, verticalVelocity, size, ballColor) {
        // Call the Shape constructor to inherit its properties from it
        Shape.call(this, coordX, coordY, horizontalVelocity, verticalVelocity, size);

        // Define a color for the ball
        this.ballColor = ballColor;

        /*
        * Define an existance for our ball
        * This will help us to make a ball disappear when it will be eaten by the evil circle
        * By default, a ball exists at it creation
        */
        this.exists = true;
    }

    // Define a method that will draw our balls on the game context
    Ball.prototype.drawBall = function () {
        // Create a new Path
        blackBoardContext.beginPath();

        // Set the Fill color of the ball with the ballColor of the current ball
        blackBoardContext.fillStyle = this.ballColor;

        // Design the abstract form of the circle with the current ball datas
        blackBoardContext.arc(this.coordX, this.coordY, this.size, 0, 2 * Math.PI);

        // Fill the cicrcle and make it solid ans visible with the current ball color
        blackBoardContext.fill();
    }

    /*
    * Define a function that will update ball position
    * Though make a ball moves
    */

    Ball.prototype.update = function () {
        if ((this.coordX + this.size) >= blackBoardWidth) {
            this.horizontalVelocity = - (this.horizontalVelocity);
        }

        if ((this.coordX + this.size) <= 0) {
            this.horizontalVelocity = - (this.horizontalVelocity);
        }

        if ((this.coordY + this.size) >= blackBoardHeight) {
            this.verticalVelocity = - (this.verticalVelocity);
        }

        if ((this.coordY + this.size) <= 0) {
            this.verticalVelocity = - (this.verticalVelocity);
        }
        this.coordX += this.horizontalVelocity;
        this.coordY += this.verticalVelocity;
    }

    /*
    * var oneBall = new Ball(250, 250, -7, 7, 50, 'red');
    * *oneBall.drawBall();
    */

    // An array that will store all the balls we are playing with on the game scene
    var balls = [];

    /*
    * Define a methodd to change color of the balls 
    * each time they collide of one another
    */
    Ball.prototype.ballCollisionDetect = function () {
        for (var i = 0; i < balls.length; i++) {

            // Check if the ball is different from the current ball
            if (!(this === balls[i])) {

                // Determine the distance between the two balls
                distanceX = this.coordX - balls[i].coordX;
                distanceY = this.coordY - balls[i].coordY;
                ballsDistance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

                // Check if the two balls collides
                // And have different colors
                // Change the color of the second into the color of the current ball
                if ((ballsDistance - this.size) <= balls[i].size) {
                    if (this.ballColor !== balls[i].ballColor) {
                        this.ballColor = balls[i].ballColor;
                    } else {
                        this.ballColor = 'rgb('+random(0, 255)+', '+random(0, 255)+', '+random(0, 255)+')';
                    }
                }
            }
        }
    }


    /*
    * Define the EvilCircle Object constructor
    * This Object will inherit from Shape Object
    */
    function EvilCircle(coordX, coordY, horizontalVelocity, verticalVelocity, size, evilStrokeColor) {
        Shape.call(this, coordX, coordY, horizontalVelocity, verticalVelocity, size);

        // Set the Sroke Color of our Evil Circle
        this.evilStrokeColor = evilStrokeColor;
    }

    // Ddefine a method that draws the evil circle
    EvilCircle.prototype.drawEvil = function() {
        blackBoardContext.beginPath();
        blackBoardContext.lineWidth = 2;
        blackBoardContext.strokeStyle = "#fff";
        blackBoardContext.arc(this.coordX, this.coordY, this.size, 0, 2*Math.PI );
        blackBoardContext.stroke();
    }

    // Check the bounds to avoid the evil Circle to traverse the Window
    EvilCircle.prototype.checkBounds = function() {
        if( (this.coordX + this.size) >= blackBoardWidth ) {
            this.coordX = blackBoardWidth + 2*this.horizontalVelocity;
        }

        if( (this.coordX + this.size ) <= 0 ) {
            this.coordX = -2*this.horizontalVelocity;
        }

        if( (this.coordY + this.size ) >= blackBoardHeight ) {
            this.coordY = blackBoardHeight - 2*this.verticalVelocity;
        }

        if( (this.coordY + this.size)  <= 0) {
            this.coordY = 2*this.verticalVelocity;
        }
    }

    // Define a method that allows us control the movement of our evil circle
    EvilCircle.prototype.setControls = function() {
        console.log("I'am in");

        var myEvilCircle = this;
        // ArrowRight -> 39
        // ArrowLeft -> 37
        // ArrowUp -> 38
        // ArrowDown -> 40
        window.onkeydown = function(event) {

            event.preventDefault();
            // Move the Evil Cirlce Backward
            if(event.keyCode === 37 ) {
                myEvilCircle.checkBounds();
                myEvilCircle.coordX += myEvilCircle.horizontalVelocity;
            }

            // Move the Evil Circle forward
            if(event.keyCode === 39 ) {
                myEvilCircle.checkBounds();
                myEvilCircle.coordX -= myEvilCircle.horizontalVelocity;
            }

            // Move the Evil Circle UpWard
            if(event.keyCode === 38 ) {
                myEvilCircle.checkBounds();
                myEvilCircle.coordY -= myEvilCircle.verticalVelocity;
            }

            // Move the Evil Circle downWard
            if(event.keyCode === 40 ) {
                myEvilCircle.checkBounds();
                myEvilCircle.coordY += myEvilCircle.verticalVelocity;
            }
        }
    }


    // Make the  Evil circle eats balls when they collide with it
    EvilCircle.prototype.evilDetectCollision = function() {
        for( var k = 0 ; k < balls.length ; k++ ) {
            if( balls[k].exists ) {
                let distanceEvilBallX = this.coordX - balls[k].coordX;
                let distanceEvilBallY = this.coordY - balls[k].coordY; 
                let distanceEvilBall = Math.sqrt(distanceEvilBallX*distanceEvilBallX + distanceEvilBallY*distanceEvilBallY);

                if((distanceEvilBall - this.size) <= balls[k].size ) {
                    balls[k].exists = false;
                    defaultNumber--;
                }
            }
        }
    }

    // Create an evilCircle Object
    var evilCircle = new EvilCircle(250, 250, -20, 20, 10, '#fff');
    evilCircle.setControls();

    // A default number of balls to draw
    var defaultNumber = 25;

    // Define a loop to create our balls bouncing in the game scene
    function bouncingBallsLoop() {
        blackBoardContext.fillStyle = 'rgba(0,0,0, .25)';
        blackBoardContext.fillRect(0, 0, blackBoardWidth, blackBoardHeight);
        while( balls.length <= defaultNumber - 1 ) {
            // Create a New Ball with random parameters
            var ball = new Ball(
                random(0, blackBoardWidth),
                random(0, blackBoardHeight),
                random(-7, 7),
                random(-7, 7),
                random(5, 20),
                'rgb('+random(0, 255)+', '+random(0, 255)+', '+random(0, 255)+')'
            );
            balls.push(ball);
        }

        // Draw the ball, update it, and detect if collides with another balls
        for( var j = 0 ; j < balls.length ; j++ ) {
            evilCircle.drawEvil();
            evilCircle.evilDetectCollision();

            if(balls[j].exists) {
                balls[j].drawBall();
                balls[j].update();
                balls[j].ballCollisionDetect();
            }
        }

        
        counter.textContent = defaultNumber;
        // Recall the function by itself
        requestAnimationFrame(bouncingBallsLoop);
    }
    bouncingBallsLoop();
    
</script>
</body>

</html>

Cool, corrected code looks good. Also, I didn’t think to look at whether you had used different keys for the controls — looks like we are all good in that regard too.

Nice work!

m// JavaScript source code
// setup canvas

var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;

var para = document.querySelector('p');

// ballcount variable
var ballCount = 0;

// function to generate random number
function random(min, max) {
    var num = Math.floor(Math.random() * (max - min)) + min;
    return num;
}

// function to display ball count on screen
function displayBallCount() {
    para.textContent = 'Ball Count: ' + ballCount;
}

//Shape constructor function
function Shape(x, y, velX, velY, exists) {
    this.x = x;
    this.y = y;
    this.velX = velX;
    this.velY = velY;
    this.exists = exists;
}

//Ball constructor function
function Ball(x, y, velX, velY, exists, color, size) {
    Shape.call(this, x, y, velX, velY, exists);
    this.color = color;
    this.size = size;
}

// Ball prototype and constructor
Ball.prototype = Object.create(Shape.prototype);
Ball.prototype.constructor = Ball;

//Ball prototype methods
Ball.prototype.draw = function () {
    ctx.beginPath();
    ctx.fillStyle = this.color;
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
    ctx.fill();
    displayBallCount();
}

Ball.prototype.update = function () {
    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;
}

Ball.prototype.collisionDetect = function () {
    for (var j = 0; j < balls.length; j++) {
        if (!(this === balls[j])) {
            var dx = this.x - balls[j].x;
            var dy = this.y - balls[j].y;
            var distance = Math.sqrt(dx * dx + dy * dy);

            if (distance < this.size + balls[j].size) {
                balls[j].color = this.color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')';
            }
        }
    }
}

// EvilCircle constructor function
function EvilCircle(x, y, exists) {
    Shape.call(this, x, y, exists);
    this.color = 'white';
    this.size = 10;
    this.velX = 20;
    this.velY = 20;
}

//EvilCircle prototype and constructor
EvilCircle.prototype = Object.create(Shape.prototype);
EvilCircle.prototype.constructor = EvilCircle;

// EvilCircle prototype methods
EvilCircle.prototype.draw = function () {
    ctx.beginPath();
    ctx.stokeStyle = this.color;
    ctx.lineWidth = 3;
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
    ctx.stroke();
}

EvilCircle.prototype.checkBounds = function () {
    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;
    }

}

EvilCircle.prototype.setControls = function () {
    var _this = this;
    window.onkeydown = function (e) {
        if (e.keyCode === 65) {
            _this.x -= _this.velX;
        } else if (e.keyCode === 68) {
            _this.x += _this.velX;
        } else if (e.keyCode === 87) {
            _this.y -= _this.velY;
        } else if (e.keyCode === 83) {
            _this.y += _this.velY;
        }
    }
}

EvilCircle.prototype.collisionDetect = function () {
    for (var j = 0; j < balls.length; j++) {
        if (balls[j].exists) {
            var dx = this.x - balls[j].x;
            var dy = this.y - balls[j].y;
            var distance = Math.sqrt(dx * dx + dy * dy);
            if (distance < this.size + balls[j].size) {
                balls[j].exists = false;
                ballCount--;
                displayBallCount();
            }
        }
    }
}


var balls = []; // Balls array

var evilCircle = new EvilCircle(random(0, width), random(0, height), true);
evilCircle.setControls();

// function to run program. name: loop
function loop() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
    ctx.fillRect(0, 0, width, height);


    while (balls.length < 25) {
        var ball = new Ball(
            random(0, width),
            random(0, height),
            random(-7, 7),
            random(-7, 7),
            true,
            'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')',
            random(10, 20)
        );
        balls.push(ball);
        ballCount++;
    }



    for (var i = 0; i < balls.length; i++) {


        if (balls[i].exists) {
            balls[i].draw();
            balls[i].update();
            balls[i].collisionDetect();
        }
    }

    evilCircle.draw();
    evilCircle.checkBounds();
    evilCircle.collisionDetect();

    requestAniationFrame(loop);
}

loop();
1 Like

HI I completed the bounce tutorial and wanted to get the marking guide, I also wanted to share my code so that I could get it checked for problems and improvements.

cheers

Hi Laurent!

Congratulations on completing the assessment. These are the links you’ll find useful:

Let me know if you have any more questions. Have you got your source code up on a github repo or anything? That would probably be the easiest way to share it.

Hi.

I think there is a mistake in the code in line 97 when we are calling Shape() constructor:
Shape.call(this, x, y, exists);
This way we are essentially calling it like:
Shape.call(this, x, y, exists, undefined, undefined);
Which translates to:
this.x = x;
this.y = y;
this.velX = exists;
this.velY = undefined;
this.exists = undefined;
I think we should just call it like:
Shape.call(this, x, y, 20, 20, exists);
And remove lines 101, 102 in the code.
1 Like

Hi there!
I was finishing the assessment of this section, all exactly as the guidelines established and it was working perfectly, only thing remained was the score paragraph… then I felt like taking a challenge and decided to implement what @chrisdavidmills suggested and making the code suitable for 2 players; and I achieved it :rofl:
Here it’s the live preview of the game: Bouncing Balls Multiplayer ( A/W/S/D keys to control player 1; left/right/up/down keys to control player 2). Please try the game, before keep reading.

New Features

  • 2 players mode ( red and green)
  • keep score of both players
  • multi key press, which allows to move each evil circle at the same time pressing different keys and even more amazing, it allows you to move the evil circles in a diagonal path (for example, pressing up + left at the same time)

Code’s Adaptation

Here it’s my JS code.

  • guidelines asked us to " set the prototype and constructor properties correctly" i.e. to add Ball.prototype = Object.create(Shape.prototype) and
    Ball.prototype.constructor = Ball ; but I understood that that was made in order to inherit the methods from the parent constructor (in this case Shape ), but Shape doesn’t have any methods defined. So I found those lines of code without real purpose and didn’t add them.

  • because of the goal to make the game multiplayer, I was forced to solve the problem in a different way than the one that our assessment was based on. I replaced the onkeydown event inside EvilCircle by a couple of listeners to keep track of the keys kept pressed (stored in an array) and put those listeners just before the main loop function.

  • in EvilCircles, I replaced the “setControls” procedure by an “update” procedure, with a similar idea of the “update” in Balls but only fired when keys are kept pressed, inside the main “loop” function.

I added more properties in order to achieve the desired results, but I think it’s enough with what I explained before.

Wow, this is really cool — congratulations!

Even without the multiplayer, the keyboard controls implementation is a clear improvement on my original version. I think it’d be great to include those updates in the master code at some point.

Thank you, chris!!

And feel free to take any portion of the code I wrote, or implement it again or clean it :rofl:.

There’s just one thing I noticed minutes ago when I played it again, the speed of evil ball gets almost as twice as fast when the path is diagonal (square root of 2, to be exact), but that could get fixed with a conditional inside loop that will reduce the speed by that factor when the number of elements in the array is exactly two (number of keys pressed).