2
\$\begingroup\$

The Problem:

If you've ever played or watched some gameplay of a battle royal game (like PUBG, for example), you'll always find an event during any battle called the red zone.

Basically, at a certain point in time during the game, a random zone on the map (possibly a square, possibly a circle) will be bombarded with artillery fire, for around 30 seconds or so. The game will fire, say, 20 shells at random positions within that red zone. If you're in the red zone, and it happens that you are within the splash radius (see below) of the shell, you will instantly die.

My attempt at recreating the Red-Zone feature using the JS canvas:

using an eventListener, whenever the user clicks on the canvas, a random point will be generated and a circle of radius 50 will be plotted around that point, but there's a catch:

If the point generated is less than 50 units away from the border of the zone or the point generated is less than 100 units away from another already generated point, the algorithm will keep generating new points and test it against the 2 previously stated conditions until it meets them, then it will add that point to the array containing all the dots.

The algorithm does work as intended for all test cases, but sometimes the first point, and ONLY the first point generated, violates these 2 conditions. I have no idea why. I've tried using a do-while loop instead of a while loop, (which keeps generating new points until one meets the 2 conditions), but no luck.

Please help me with this issue as fast as possible. Thanks in advance. Here's the code (CodePen): https://codepen.io/Undefined_Variable/full/oQeoqE/

let canvas = document.body.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 0.75 * screen.width;
canvas.height = 0.8 * screen.height;
let CW = canvas.width;
let CH = canvas.height;
canvas.style.top = (screen.height / 2) - (canvas.height / 2) + "px";
canvas.style.left = (screen.width / 2) - (canvas.width / 2) + "px";

let dotArr = [];
let dotSize = 50
let dotDistance = 100;
let borderDistance = 50;
let dotCounter = 0;

class dot {
  constructor(posX, posY) {
    this.posX = posX;
    this.posY = posY;
    this.radius = dotSize;
  }
  drawDot() {
    ctx.save();
    ctx.translate(this.posX, this.posY);
    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }
}

function mainLoop() {

  function drawCircle() {
    ctx.save();
    ctx.translate(CW / 2, CH / 2);
    ctx.strokeStyle = "black";
    ctx.beginPath();
    ctx.arc(0, 0, 0.25 * CW, 0, Math.PI * 2);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }

  function drawDots() {
    for (let dot of dotArr) {
      dot.drawDot();
    }
  }

  ctx.clearRect(0, 0, CW, CH);
  drawCircle();
  drawDots();
  requestAnimationFrame(mainLoop);
}

function addDot() {

  function generateDot() {
    let center_x = CW / 2;
    let center_y = CH / 2;
    let angle = Math.random() * 360;
    let distance = Math.random() * (0.25 * CW);
    let radius = 0.25 * CW;

    return [center_x + Math.cos(angle) * distance, center_y + Math.sin(angle) * distance, center_x + Math.cos(angle) * radius, center_y + Math.sin(angle) * radius]
  }

  function verifyDotPosition(xPar, yPar, xBorder, yBorder) {
    for (let dot of dotArr) {
      if (Math.sqrt(Math.pow(xPar - dot.posX, 2) + Math.pow(yPar - dot.posY, 2)) < dotDistance) {
        return false;
      }
      if (Math.sqrt(Math.pow(xPar - xBorder, 2) + Math.pow(yPar - yBorder, 2)) < borderDistance) {
        return false;
      }
    }
    dotCounter++;
    return true;
  }

  let newDots = generateDot();
  let x1 = newDots[0];
  let y1 = newDots[1];
  let x2 = newDots[2];
  let y2 = newDots[3];

  while (!verifyDotPosition(x1, y1, x2, y2)) {
    newDots = generateDot();
    x1 = newDots[0];
    y1 = newDots[1];
    x2 = newDots[2];
    y2 = newDots[3];
  }

  if (dotCounter < 10) {
    dotArr.push(new dot(x1, y1));
  } else {
    canvas.removeEventListener("click", addDot);
  }
}

mainLoop();

canvas.addEventListener("click", addDot);
canvas {
  border: 1px solid black;
  position: absolute;
}

body {
  margin: 0;
}
<!DOCTYPE html>
<html>

<head>
  <title>redZone-demo</title>
</head>

<body>
  <canvas></canvas>
</body>

</html>


-each shell has a splash radius, if you are inside a circle of a radius equal to that splash radius surrounding the point where the shell hit it, you will die


Clarification:

When I said that sometimes the algorithm doesn't work for the first point, I mean that sometimes the first point is generated too close to the border of the "red zone".

\$\endgroup\$
1
\$\begingroup\$

The issue is in verifyDotPosition:

function verifyDotPosition(xPar, yPar, xBorder, yBorder) {
    for (let dot of dotArr) {
        if (Math.sqrt(Math.pow(xPar - dot.posX, 2) + Math.pow(yPar - dot.posY, 2)) < dotDistance) {
            return false;
        }
        if (Math.sqrt(Math.pow(xPar - xBorder, 2) + Math.pow(yPar - yBorder, 2)) < borderDistance) {
            return false;
        }
    }
    dotCounter++;
    return true;
}

You only check if the generated position is too close to the border inside the for-loop, and that for loop is not evaluated if dotArr is empty, which it is for the first dot to be generated. Simply move the border condition outside of the for loop:

function verifyDotPosition(xPar, yPar, xBorder, yBorder) {
    for (let dot of dotArr) {
        if (Math.sqrt(Math.pow(xPar - dot.posX, 2) + Math.pow(yPar - dot.posY, 2)) < dotDistance) {
            return false;
        }
    }
    if (Math.sqrt(Math.pow(xPar - xBorder, 2) + Math.pow(yPar - yBorder, 2)) < borderDistance) {
        return false;
    }
    dotCounter++;
    return true;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.