asprin
asprin

Reputation: 9823

How to get a balanced output when picking a random element from an array?

As a fun project, I'm developing a cricket simulator game and one of the main aspects of it is to have a random outcome on each delivery bowled.

In general, in Test Cricket, the possibility is as follows:

"0", "1" happen very frequently (60% of the time)

"2", "3" happen moderately (25% of the the time)

"FOUR", "SIX", "OUT" happen rarely (10% of the time)

"WIDE BALL", "NO BALL" happen very rarely (2% of the time)

If I have an array such as:

var possible_outcomes = ["0","1","2","3","FOUR","SIX","OUT","WIDE BALL","NO BALL"];

What could be the best way to get the above mentioned probability when pulling a random item from possible_outcomes over a fixed number of iterations, say 60.

PS: I'm sorry if some of you are unaware of the sport. I've used a couple of terms related to Cricket since I didn't know how to explain any better.

Upvotes: 4

Views: 162

Answers (6)

Nina Scholz
Nina Scholz

Reputation: 386620

I suggest to use a continuous check of the probability and the rest of the random number.

This function sets first the return value to the last possible index and iterates until the rest of the random value is smaller than the actual probability.

The probabilities have to sum to one.

function getRandomIndexByProbability(probabilities) {
    var r = Math.random(),
        index = probabilities.length - 1;

    probabilities.some(function (probability, i) {
        if (r < probability) {
            index = i;
            return true;
        }
        r -= probability;
    });
    return index;
}

var i,
    action = ["0", "1", "2", "3", "FOUR", "SIX", "OUT", "WIDE BALL", "NO BALL", "other"],
    probabilities = [0.3, 0.3, 1 / 8, 1 / 8, 1 / 30, 1 / 30, 1 / 30, 0.01, 0.01, 0.03],
    count = {},
    index;

action.forEach(function (a) { count[a] = 0; });

for (i = 0; i < 1e6; i++) {
    index = getRandomIndexByProbability(probabilities);
    count[action[index]]++;
}

console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Rory McCrossan
Rory McCrossan

Reputation: 337570

The technique you want to utilise is called 'weighted randomness'. To achieve this in Javascript you can build an array which is populated with the possible outcomes in amounts which match the given probability of their occurrence. For details on how to do this and why it works, see this question.

In the logic of your cricket game you can then use a loop to pull out random outcomes until the batter is out, something like this:

function weightedRand(spec) {
  var i, j, table = [];
  for (i in spec) {
    for (j = 0; j < spec[i] * 10; j++) {
      table.push(i);
    }
  }
  return function() {
    return table[Math.floor(Math.random() * table.length)];
  }
}

var outcomes = weightedRand({
  '0': 0.3,
  '1': 0.3,
  '2': 0.125,
  '3': 0.125,
  'FOUR': 0.033,
  'SIX': 0.033,
  'OUT': 0.033,
  'WIDE BALL': 0.01,
  'NO BALL': 0.01
});

$('button').click(function() {
  clearInterval(interval);
  $('.innings').empty();
  $(this).prop('disabled', true);

  var interval = setInterval(function() {
    var item = outcomes();
    $('.innings').append('<div>' + item + '<div>');

    if (item == 'OUT') {
      clearInterval(interval);
      $('button').prop('disabled', false);
    }
  }, 1000);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Start innings</button>
<div class="innings"></div>

Note that the probabilities won't quite match what you've specified in the above code as they don't cumulatively add up to 1.00, but the values can easily be amended.

Upvotes: 1

philantrovert
philantrovert

Reputation: 10092

Forgive me for my awkward code but I hope you can consider this as a solution? :D

function repeatArray(value, len) {
  if (len == 0) return [];
  var a = [value];
  while (a.length * 2 <= len) a = a.concat(a);
  if (a.length < len) a = a.concat(a.slice(0, len - a.length));
  return a;
}

var zeroes = repeatArray("0", 30);
var ones = repeatArray("1", 25);
var twos = repeatArray("2", 15);
var threes = repeatArray("3", 10);
var fours = repeatArray("FOUR", 5);
var sixes = repeatArray("SIX", 5);
var wickets = repeatArray("OUT", 5);
var extras = repeatArray("Extra", 5);
var finalArr = [];

finalArr = finalArr.concat(zeroes, ones, twos, threes, fours, sixes, wickets, extras);
for (var i = 0; i < 20; i++) {
  var idx = Math.floor(Math.random() * finalArr.length);
  $("#out").append(finalArr[idx]+", ");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="out"></div>

Upvotes: 1

B&#225;lint
B&#225;lint

Reputation: 4039

An easiely changeable solution would be the following:

var cricket = [
    {
        name: 0,
        weight: 30
    },
    {
        name: 1,
        weight: 30
    },
    {
        name: 2,
        weight: 13
    }
    // ...
];

// Create the chances array by putting as many instances of a value in it, as it's weight

var chances = [];
for (var i = 0; i < cricket.length; i++) {
    for (var j = 0; j < cricket[i].weight; j++) {
        chances.push(cricket[i].name);
    }
}

// getting a value

var value = chances[Math.floor(Math.random() * chances.length)];

Upvotes: 1

piotrwest
piotrwest

Reputation: 2166

IMO the easiest, however not so fancy solution is to create an array of hundred elements with repeated items in it:

var possible_outcomes = ["0","0","0","0","0","0","0","0","0","0",
"0","0","0","0","0","0","0","0","0","0",
"0","0","0","0","0","0","0","0","0","0",
"1","1","1","1","1","1","1","1","1","1",
"1","1","1","1","1","1","1","1","1","1",
"1","1","1","1","1","1","1","1","1","1",
,and so on...,
,"WIDE BALL","WIDE BALL","NO BALL","NO BALL"];

Then you just get the random item from that array. No if's or switch-cases needed.

PS. I don't know the cricket, however if repeating 100 times "0" is not allowed, you can always remove selected option from the array, so it won't happen again.

Upvotes: 1

Amit
Amit

Reputation: 4290

You can generate a random number between 0-100. If 0 < number < 10 then 0 run, if 20-25 then 2 run. Similarly if we get 100 then wicket.

Upvotes: 1

Related Questions