4

I'm trying to spawn enemies just outside the bounds of a rectangle. Here's a picture:

enter image description here

That is, the grey area is the playing area that the user can see, and the green is outside the rendering bounds. I'm looking for a way to calculate a spawn position in this green area.

I have a tentative solution, but it's pretty long and involves a bunch of if statements. Is there a more efficient or elegant way of calculating this?

function calcEnemySpawnPos(r) {
  const roll = Math.random();

  const left = -r;
  const right = canvas.width + r;
  const top = -r;
  const bottom = canvas.height + r;

  if (roll <= 0.25) {
    return { x: left, y: getRandomInt(top, bottom) };
  } else if (roll <= 0.5) {
    return { x: right, y: getRandomInt(top, bottom) };
  } else if (roll < 0.75) {
    return { x: getRandomInt(left, right), y: top };
  } else {
    return { x: getRandomInt(left, right), y: bottom };
  }
}
Ryan Peschel
  • 11,087
  • 19
  • 74
  • 136

2 Answers2

2

I have a slight improvement, but still not amazingly elegant. This is pseudocode since I'm not sure of the js syntax:

const rollLeft = Math.random() - 0.5;
const rollTop = Math.random() - 0.5;

if (rollLeft > 0){
    x = getRandomInt(-r, 0)
} else {
    x = getRandomInt(canvas.width, canvas.width + r)
}
    
if (rollRight > 0){
    y = getRandomInt(-r, 0)
}   else {
    y = getRandomInt(canvas.height, canvas.height + r)
}

return {x, y}
faraday703
  • 141
  • 5
1

There are 200,000 possible positions. You can generate just one random number and map it to a valid coordinate. You can specify four valid ranges, defined by top-left and bottom-right corners, then use your random number to get a range (weighted by area) and then convert the number to a point in that range.

function startPos(ranges, totalSize) {
  let n = Math.trunc(Math.random() * totalSize);
  const {x: j, y: k, w} = ranges.find(r => n < r.size || void(n -= r.size));
  const x = n % w, y = (n - x) / w;  // remainder/quotient of dividing by width
  return [x + j, y + k];             // translate to start of range
}

[x, y] = startPos([
  {x: -100, y: -100, w: 600, h: 100, size: 600 * 100},
  {x:  500, y: -100, w: 100, h: 400, size: 100 * 400},
  {x:    0, y:  300, w: 600, h: 100, size: 600 * 100},
  {x: -100, y:    0, w: 100, h: 400, size: 100 * 400},
], 200_000);

The ranges.find(...) predicate is a little hard to read. Could also be written like this:

ranges.find(({size}) => {
  if (n < size) return true;
  else n -= size;
});

Note that this algorithm gives every pixel equal probability of being the spawn point, in contrast with your solution where each quadrant has equal probability of containing the spawn point, so pixels on the shorter sides have higher probability than pixels on the longer sides.

Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109