-1

i am new to coding. Currently creating a game for a school project.

In short, random circles or "targets" will pop up on the screen, and the user has to click them. I tried to make something that would create a new canvas without the clicked circle, but instead it creates an empty canvas and the loop goes again. How do i keep the "un-clicked" circles on my new canvas?

I am sorry if that made no sense :D. If you have any tips, i would appreciate if you could dumb it down for me :D. Any help would be greatly appreciated.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var radiustracker = [];
var xpos = [];
var ypos = [];
var radius;
var x;
var y;
var color = 'blue';
ctx.fillStyle = 'lightblue';
ctx.fillRect(0, 0, canvas.width, canvas.height);

function randomize() {
  radius = Math.floor(Math.random() * 25) + 10;
  x = Math.floor(Math.random() * 600) + 50;
  y = Math.floor(Math.random() * 400) + 50;
  radiustracker.push(radius);
  xpos.push(x);
  ypos.push(y);
  drawCircle(x, y, radius);
}

function drawCircle(x, y, radius) {
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.fillStyle = color;
  ctx.fill();
}

function clickCircle(xmus, ymus) {
  for (var i = 0; i < xpos.length; i++) {
    var distance =
      Math.sqrt(
        ((xmus - xpos[i]) * (xmus - xpos[i])) +
        ((ymus - ypos[i]) * (ymus - ypos[i]))
      );
    console.log(distance);
    if (distance < radiustracker[i]) {
      radiustracker[i] = 0;
      ctx.fillStyle = 'lightblue';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
  }
}

var intervall = setInterval(randomize, 1000);
canvas.addEventListener('click', (event) => {
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  clickCircle(x, y);
});
#canvas {
  display:inline;
  margins:auto;
}
<body>
<canvas id="canvas" height="500" width="700" ></canvas>
</body>
Wendelin
  • 2,354
  • 1
  • 11
  • 28
Panda002
  • 3
  • 2

2 Answers2

1

Instead of having three separate arrays that track the circle x, y, and r, I would use an array of objects like so:

var circlesDrawn = [];
//And then the following repeated for each circle you draw
circlesDrawn.push({
    x: x,
    y: y,
    r: r
});

At least that way your data is grouped together and has some meaning.

Secondly, what you want to do, if I understand the question correctly, is to remove a circle that is clicked on. In a JS Canvas you cannot remove something that is drawn, you can only clearRect, so we should use that to our advantage since it's a faster method anyways than trying to paint over your circle with another clear circle

My idea was to first clear the canvas, and then draw whatever circles you have in your circles array, and then when the user clicks you can do the same thing expect remove the circle that was clicked from your circles array (you can filter out the element that matches x, y, and r)

In addition to all this you can also have your interval drawing in a new circle

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

var circles = [];
function drawCanvas() {
    ctx.fillStyle = 'lightblue';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    for (var a = 0; a < circles.length; a++) {
        drawCircle(circles[a].x, circles[a].y, circles[a].r);
    }
}
function drawCircle(x, y, radius){
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.fillStyle = color;
    ctx.fill();
}
function clickCircle(xmus, ymus){
    for (var i = 0; i < circles.length; i++) {            
        var distance = Math.sqrt(
            Math.pow(xmus - circles[i].x, 2)
            + 
            Math.pow(ymus - circles[i].y, 2)
        );
       console.log(distance);
       if(distance < circles[i].r){   
           //Remove the circle from our array
            circles.splice(i, 1); //This removes 1 element from our circles array at index i
           //Then redraw our canvas from the beginning
           drawCanvas();
        }
    }
}

canvas.addEventListener('click', (event) => {
    const rect = canvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    clickCircle(x,y);
});

function randomize(){
    var radius = Math.floor(Math.random()*25)+10;
    var x = Math.floor(Math.random()*600)+50;
    var y = Math.floor(Math.random()*400)+50;
    circles.push({
        x: x,
        y: y,
        r: radius
    });
    drawCircle(x,y,radius);
}

var interval = setInterval(randomize, 1000);

I did you the favor of cleaning up some of your code and what not

The way it works is you need to call drawCanvas once on its own, and since you have 0 circles to start with, it'll draw all 0 circles and set up your canvas. Then you register what will happen when you click on a circle, and then you set the interval to start drawing a new circle every second, hopefully that works and isn't too complicated. I tested it out and it works perfectly for me!

Da Mahdi03
  • 1,468
  • 1
  • 9
  • 18
  • This will probably not work since you are splicing while iterating. This causes the element after the splice to be skipped. See https://stackoverflow.com/q/9882284/7448536 – Wendelin Feb 18 '21 at 23:06
  • It works perfectly, and it's ok if the next element is skipped because you're only clicking on one circle. The loop is supposed to only find the matching element, not apply something to every element so it's all good – Da Mahdi03 Feb 18 '21 at 23:47
  • That depends, many circles *can* be ontop of each other so you *can* click multiple at once. Also if you actually only want to remove a single circle you should probably return or break after removing one, otherwise you could end up remove another. – Wendelin Feb 18 '21 at 23:49
0

You have to redraw all circles when you remove one, there is no way around that. The change I've made is quite simple, in clickCircle I clear the whole canvas and then loop through all circles, remove any that were clicked and draw all that were not clicked.

// Tip: don't use var, it can lead to strange behavoir. Use const and let instead.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Tip: Use single array instead of radius and position arrays
const circles = [];
const color = 'blue';
ctx.fillStyle = 'lightblue';
ctx.fillRect(0, 0, canvas.width, canvas.height);

function randomize() {
  //Tip: Reduce the scope of variables, before radius, x and y were global, now they only exist inside the function
  const radius = Math.floor(Math.random() * 25) + 10;
  const x = Math.floor(Math.random() * 600) + 50;
  const y = Math.floor(Math.random() * 400) + 50;
  const circle = {radius: radius, x: x, y: y}
  circles.push(circle)
  drawCircle(circle)
}

function drawCircle(circle) {
  ctx.beginPath();
  ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.fillStyle = color;
  ctx.fill();
}

function clickCircle(xmus, ymus) {
  // Clear canvas
  ctx.fillStyle = 'lightblue';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  // Need to loop backwards since we are removing elements while looping
  // See https://stackoverflow.com/q/9882284/7448536
  for (let i = circles.length - 1; i >= 0; i--) {
    const circle = circles[i]
    const distance =
      // Tip: Use ** for exponents
      Math.sqrt(
        ((xmus - circle.x) ** 2) +
        ((ymus - circle.y) ** 2)
      );
    if (distance < circle.radius) {
      // Array.splice(index, count) to remove item from array
      circles.splice(circles.indexOf(circle), 1);
    } else {
      // Redraw all circles that were not clicked
      drawCircle(circle);
    }
  }
}

const intervall = setInterval(randomize, 1000);
canvas.addEventListener('click', (event) => {
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  clickCircle(x, y);
});
#canvas {
  display:inline;
  margins:auto;
}
<body>
<canvas id="canvas" height="500" width="700" ></canvas>
</body>
Wendelin
  • 2,354
  • 1
  • 11
  • 28