0

I have an array of objects that represent creatures in a game I'm trying to develop. These objects have (among others) a unique identifier and a weight (or probability) to spawn.

I'm trying to develop an algorithm to spawn creatures randomly but I fail to come up with a way to use the weights (I really don't know how to do it).

Can anybody help?

An example of creatures array could be:

var creatures = [
    {id: 1, weight: 25},
    {id: 2, weight: 15},
    {id: 3, weight: 5},
    {id: 4, weight: 45},
    {id: 5, weight: 10}
]
btt
  • 131
  • 2
  • 6

2 Answers2

10

I found this nice algorithm implemented in PHP in this blog that I think migth suit your needs.

I just adopted it to JS.

var creatures = [{
    id: 1,
    weight: 25
  }, {
    id: 2,
    weight: 15
  }, {
    id: 3,
    weight: 5
  }, {
    id: 4,
    weight: 45
  }, {
    id: 5,
    weight: 10
  }],
  sumOfWeights = creatures.reduce(function(memo, creature) {
    return memo + creature.weight;
  }, 0),
  selectedWeigths = {};

function getRandom(sumOfWeights) {
  var random = Math.floor(Math.random() * (sumOfWeights + 1));

  return function(creature) {
    random -= creature.weight;
    return random <= 0;
  };
}

for (var i = 0; i < 1000; i++) {
  var creature = creatures.find(getRandom(sumOfWeights));
  selectedWeigths[creature.weight] = (selectedWeigths[creature.weight] || 0) + 1;
}

console.log(selectedWeigths);

Hope it helps.

acontell
  • 6,792
  • 1
  • 19
  • 32
  • Can you give an explanation as to what this does? – evolutionxbox Jan 01 '17 at 20:35
  • @evolutionxbox the algorithm is based on the discrete cumulative density function (CDF) which is the sum of the weights. The idea is to generate a random number between 0 and that sum and keep substracting to that random number the weight of the elements of the array. When it is less than zero (or zero) that means that you've found the element. It's a bit difficult to explain in just a paragraph, I suggest looking up "discrete cumulative density function" on Wikipedia, there's a much better explanation. – acontell Jan 01 '17 at 20:56
  • This solution is promising. Thanks – btt Jan 01 '17 at 21:14
  • I find it interesting how this factory produces a scoped function that's intended solely for a single use within the `.find()` method. – Patrick Roberts Jul 05 '17 at 02:25
  • 2
    Cool function! Though after some testing I do believe `Math.random() * (sumOfWeights + 1)` should be `Math.random() * sumOfWeights + 1` instead – Dockson Feb 23 '20 at 17:20
0

Create a new array and add the id for each creature to the array the number of times it is weighted. Then get a random number between 0 and the size of the array and return the id number at that position.

var creatureIds = [];
for(var i=0;i<creatures.length;i++){
    for(var x=0;x<creatures[i].weight;x++){
        creatureIds.push(creatures[i].id);
    }
}

// get a random index between 0 and the ids length.
var min = 0;
var max = creatureIds.length;
var index = Math.floor(Math.random() * (max - min + 1)) + min;

var randomWeightedCreatureId = creatureIds[index];
Michael Sacket
  • 897
  • 8
  • 13
  • 1
    The only problem about this solution is that you end up with a big array when there might be no need to... but thanks anyway! – btt Jan 01 '17 at 21:16