Selecting from an array based on size - javascript

I'm currently working on a small game and got stuck on something I can't figure out.
I have an array with 5 items in, these items represent different positions, like this:
[1, 2, 3, 4, 5]
These are basically different lines that are used to place objects as the players goes further into the game.
Every 5 second new objects should appear, these come from another array, that looks something like this:
[1, 2]
// 1 = single item
// 2 = double item
1 and 2 are different types of objects, both require a different amount of lines.
The problem is that these items sometimes overlap and I can't figure out how to stop this.
You can see the problem from the output:
Items: [1, 2]
[Item of type 1] line: 3
[available lines] [1, 2, 3, 4, 5]
[Item of type 2] line: 2
[available lines] [1, 2, 4, 5]
[1, 5]
So item 1 (1 line) is placed on line 3, but for item 2 (2 lines), line 2 is select, because it's 2 lines wide, this will overlap with my first item.
So basically for item 2 only line 1 and 4 could be selected, but I can't seem to find any logic that would only select these.
It's probably going to be expanded into something around 20 lines with more objects, so I would need to find a way to select a line where the next one is available too.
Any ideas on how I can get started on this?
What I currently use is this:
var random = Math.floor(Math.random() * (start_lines.length - 1));
var line = start_lines[random];
// If it's a barrier, delete 2 rows instead (35)
if(type == 2) {
if(line == 5) {
xPos = this._lines[random - 1] - 35;
start_lines.splice(random - 1, 2);
} else {
xPos = this._lines[random] - 35;
start_lines.splice(random, 2);
}
} else {
start_lines.splice(random, 1);
}

What I did for now was first loop trough the array with lines and check if the next line is available, I then push those 2 lines into another array which I can select from.
if(type == 2) {
var options = [];
for(var i = 0 ; i < start_lines.length; i++) {
var f = start_lines[i];
var n = start_lines[i + 1];
if((f + 1) == n) {
options.push([f, n]);
}
}
var random = Math.floor(Math.random() * (options.length - 1));
var option = options[random];
var f = option[0];
var n = option[1];
xPos = this._lines[f] - 30;
start_lines.splice(f - 1, 2);
start_lines.splice(n - 1, 2);
} else {
var random = Math.floor(Math.random() * (start_lines.length - 1));
var line = start_lines[random];
start_lines.splice(random, 1);
}

I don't know whether this approach fits for your requirement, but it would be better if you can set the lines initially and randomly pick one when needed. This avoids a lot of confusion.
function getLines(){
var start_lines = [1, 2, 3, 4, 5], lines = [];
while(start_lines.length){
var type = Math.ceil(Math.random() * 2);
lines.push(start_lines.splice(0, type));
}
return lines;
}
var lines = getLines();
var random = Math.floor(Math.random() * lines.length);
console.log(lines.splice(random, 1));
Here I am selecting type randomly - either 1 or 2. You will have to make some changes in this.
jsbin : http://jsbin.com/upabom/3/edit

Related

2 random values from array project

Okey ive got to make a program that counts togheter 2 random values.
In the program there is supposed to be a list (1-9) in a function. From this list im supposed to get 2 random values (im recommended to use array.splice()).
After the 2 random values have been choosen the program is supposed to calculate them (addition) into a total value randomvalue1 + randomvalue2 = totalvalue;
THE CATCH!
While executing the 2 randomvalues cant be of the same value (5+5, 3+3, 2+2, and so on is invalid)
THE SECOND CATCH!
the random values are not allowed to be executed 2 times in a row. what i mean is that the program should not allow for randomvalue1 to be equal to the same value two times (or more) in a row (this also applies for randomvalue2)
So far i got suggested this code but it doesnt check if the same values appear x amount of times in a row
function makeRandom(list) {
function getRandomIndex() {
return Math.floor(Math.random() * list.length);
}
let index1 = getRandomIndex(),
index2 = getRandomIndex();
while (index1 === index2) index2 = getRandomIndex();
return list[index1] + '+' + list[index2];
}
console.log(makeRandom([1, 2, 3, 4, 5, 6, 7, 8, 9]));
You could take the indeces and loop until you get a different index for getting the value from the array.
function makeRandom(list) {
function getRandomIndex() {
return Math.floor(Math.random() * list.length);
}
let index1 = getRandomIndex(),
index2 = getRandomIndex();
while (index1 === index2) index2 = getRandomIndex();
return list[index1] + '+' + list[index2];
}
console.log(makeRandom([1, 2, 3, 4, 5, 6, 7, 8, 9]));
Approach with excluding some indices
function makeRandom(list, exclude = []) {
function getRandomIndex() {
return Math.floor(Math.random() * list.length);
}
function getFreeIndex() {
let index;
if (exclude.length >= list.length) return;
do index = getRandomIndex();
while (exclude.includes(index))
exclude.push(index);
return index;
}
return getFreeIndex() + '+' + getFreeIndex();
}
console.log(makeRandom([1, 2, 3, 4, 5, 6, 7, 8, 9], [2, 3]));
Assumptions
You have an array of numbers as input
Every call to makeRandom should randomly select two numbers from your array and output the sum
Every subsequent call to makeRandom should not use any number (id) already used
list = [1,1,2,3,4,5];
// The number 1 could be used twice (but only in different calls to makeRandom)
// The numbers 2,3,4,5 can only be used once
list = [1,1];
// Shouldn't work because 1 == 1
JS Code
var list = [1,2,3,4,5,6,7,8,9]; // Your seed list
var usable = list.slice(0); // Copy of your seed list {which we will alter with makeRandom}
function makeRandom(){
// Check it's possible...
let counts = {}; // Create object to count unique numbers
for (var i = 0; i < usable.length; i++) {
counts[usable[i]] = 1 + (counts[usable[i]] || 0); // Iterate array and fill counts
}
if(Object.keys(counts).length < 2){ // Check there are at least two unique numbers
console.log("List is too short!"); // Log error if <2
return false; // Exit function if <2
}
// Get random numbers and output sum...
let id = Math.floor(Math.random() * usable.length) // Randomly select an id from usable numbers
let a = usable[id]; // Set first number
usable.splice(id, 1); // Remove 1st number from usable numbers
let b;
while(true){ // Loop until numbers are different
id = Math.floor(Math.random() * usable.length); // Randomly select an id from usable numbers
b = usable[id]; // Set second number
if(a !== b)break; // Check 1st number isn't the same as the second number
}
usable.splice(id, 1); // Remove 2nd number from usable numbers
// console.log(a + " + " + b + " = " + (a+b)); // Log for debugging if required
return a+b; // Return sum of 1st and 2nd numbers
}
Note: I've typed out the while loop in full for ease of understanding; it could be shortened using do...while(...):
let b;
do b = list[Math.floor(Math.random() * list.length)]; // Set second number
while(a==b) // Re-set second number if a == b OR if b was used in the last call to makeRandom
Example
var list = [1,2,3,4,5,6,7,8,9];
var usable = list.slice(0);
function makeRandom(){
let counts = {};
for (var i = 0; i < usable.length; i++) {
counts[usable[i]] = 1 + (counts[usable[i]] || 0);
}
if(Object.keys(counts).length < 2){
console.log("List is too short!");
return false;
}
let id = Math.floor(Math.random() * usable.length)
let a = usable[id];
usable.splice(id, 1);
let b;
while(true){
id = Math.floor(Math.random() * usable.length);
b = usable[id];
if(a !== b)break;
}
usable.splice(id, 1);
console.log(a + " + " + b + " = " + (a+b));
return a+b;
}
// Make several calls to function
makeRandom();
makeRandom();
makeRandom();
makeRandom();
makeRandom();
makeRandom();
// Change the seed lists
var list = [1,1,1,1,1,9];
var usable = list.slice(0);
// Make several calls to function
makeRandom();
makeRandom();
makeRandom();
Example Output
__Example 1__
list = [1,2,3,4,5,6,7,8,9]; // Example list used
// console.log return
> makeRandom(); // 4 + 2 = 6 6
> makeRandom(); // 1 + 8 = 9 9
> makeRandom(); // 9 + 3 = 12 12
> makeRandom(); // 6 + 7 = 13 13
> makeRandom(); // List is too short! false
> makeRandom(); // List is too short! false
__Example 2__
list = [1,1,1,1,1,9]; // Example list used
// console.log return
> makeRandom(); // 1 + 9 = 10 10
> makeRandom(); // List is too short! false
> makeRandom(); // List is too short! false
Alternative
Having had a look at the updates to your question...
I'm not clear on whether the numbers can only be used once or if they just can't be used back to back.
list = [1,2,3,4,5];
// Choose from // Chosen
> makeRandom(); // 1, 2, 3, 4, 5 // 1, 4
> makeRandom(); // 2, 3, 5 // 2, 3
> makeRandom(); // 1, 4, 5 // 1, 5
If that's the case then the following is likely more useful
var list = [1,2,3,4,5,6,7,8,9]; // Your seed list
var used = []; // Last used pair of numbers
function makeRandom(){
// Check it's possible...
let counts = {}; // Create object to count unique numbers
for (var i = 0; i < list.length; i++) {
counts[list[i]] = 1 + (counts[list[i]] || 0); // Iterate array and fill counts
}
if(Object.keys(counts).length < 4){ // Check there are at least four unique numbers: any less and we'll end up in an infinite loop on the second call of makeRandom
console.log("List is too short!"); // Log error if <2
return false; // Exit function if <2
}
// Get random numbers and output sum...
let a;
do a = list[Math.floor(Math.random() * list.length)]; // Set first number
while(used.includes(a)) // Reset first number if a was used in the last call to makeRandom
let b;
do b = list[Math.floor(Math.random() * list.length)]; // Set second number
while(a==b || used.includes(b)) // Re-set second number if a == b OR if b was used in the last call to makeRandom
used = [a, b]; // Set last used numbers
console.log(a + " + " + b + " = " + (a+b)); // Log for debugging if required
return a+b; // Return sum of 1st and 2nd numbers
}
// Make several calls to function
makeRandom();
makeRandom();
makeRandom();
makeRandom();
makeRandom();
makeRandom();
// Change the seed lists
var list = [1,2,3,4];
// Make several calls to function
// Notice with only 4 numbers once the first pair is selected (e.g. 1 & 3) the pattern cycles 1,3 -> 2,4 -> 1,3 -> 2,4
makeRandom();
makeRandom();
makeRandom();
Updated Answer
An edit to the questions made clear some additional requirements, and that means the the original solution (below) doesn't do what's requested. This version seems to be useful.
We introduce a function that will randomly choose from a fixed array, but at no time pick any of the n-most recently used items. So if we call this with n of 4 and arr of [1, 2, 3, 4, 5, 6, 7, 8, 9], we will get back a function that will randomly select one of the values, and on the next call will randomly select another, and on the third call still another, and the fourth yet one more. There will be no duplicates among these four. The fifth one might be the same as the first or could be any of the other values not in that list. And the sixth call might be the same as the second (or if it's not been reused in the fifth round, the same value as the first), and so on.
const noRepeatRandom = (n, arr) => {
// TODO: error if n >= arr.length
let available = [...arr]
let used = []
return () => {
const nextIdx = Math .floor (Math .random() * available .length)
const next = available [nextIdx]
used .push (next)
available .splice (nextIdx, 1)
if (used .length >= n) {
available .push (used .shift())
}
return next
}
}
const myRandom = noRepeatRandom (4, [1, 2, 3,4, 5, 6, 7, 8, 9])
// display the results of a bunch of calls to `myRandom()`
console .log (...Array.from({length: 30}, myRandom))
We keep track of two lists: the ones not recently used and the ones we can't reuse right now (available and used.) Initially we just fill up used, but once it reaches n, we start taking the oldest element off the used list and adding it to available.
Note that there should be error checking for n and the length of the array. If n >= arr.length, this should just fail. I'll leave that as an exercise. Also note that if n == arr.length - 1, you will get an endlessly repeating cycle of the same arr.length items, as each time there will only be one option to choose from.
Original Answer
This version does not match the clarified requirements. It's still a useful technique, but it doesn't solve the problem.
I would do a partial shuffling of the array. The code below is a recursive version of the Fisher-Yates shuffle, modified to stop after n elements are chosen. It does not modify your input array.
We then build pickTwo on top of this by partially applying the 2 to our (curried) function.
const partialShuffle = (n) => (xs, i = Math.floor (Math .random () * xs .length)) =>
n <= 0 || n > xs .length || xs .length == 0
? []
: [xs[i], ... partialShuffle (n - 1) ([... xs .slice (0, i), ... xs .slice (i + 1)])]
const pickTwo = partialShuffle (2)
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for (let i = 0; i < 5; i ++) {
console.log(...pickTwo(arr))
}
console.log('Original not modified:', ...arr)
.as-console-wrapper {max-height: 100% !important; top: 0}

JS inverting probabilities

I'm generating a grid of objects, each of which has one of 3 colors. Assume I'm filling a particular grid cell. I know that around this cell there are, for example, 1 object of color 0, 1 of color 1 and 2 of color 2, so:
const surroundings = { 0: 1, 1: 1, 2: 2 }
Now I want to assign a color to the current cell I'm working with. I need to count probabilities for each color to be used. To make it look pleasing, I want it more likely to be different from its surroundings. So in the case above the probabilities could be { 0: 0.4, 1: 0.4, 2: 0.2 }.
I'm sure there is an implementation of such operation in the probability theory, but I'm having trouble finding the correct term for it. In the example I gave the probabilities could be different, because I have no idea how to calculate them. But for colors 0 and 1 they should certainly be equal and for color 2 it should be the smallest.
You could get the reciprocal and the sum and return the part of it.
function getPro(array) {
var inv = array.map(v => 1 / v),
sum = inv.reduce((a, b) => a + b);
return inv.map(v => v / sum);
}
console.log(getPro([1, 1, 2]));
console.log(getPro([1, 1, 1]));
console.log(getPro([2, 4, 6]));
I would use the sum of surrounding frequency (usually 4, except on grid boundaries) to get the inverse:
let surrounding = [1, 2, 1];
let sum = surrounding[0] + surrounding[1] + surrounding[2];
let inverted = [sum-surrounding[0], sum-surrounding[1], sum-surrounding[2]];
console.log(inverted);
// Pick a random color according to inverted probabilities:
let invSum = 2*sum;
let rnd = Math.floor(Math.random() * invSum);
let color = rnd < inverted[0] ? 0
: rnd < inverted[0] + inverted[1] ? 1
: 2;
console.log(color);
This will also work when a particular color does not occur at all in the surroundings, for example with surrounding = { 0: 0, 1: 2, 2: 2 }

Producing a random number that is different each time

I need to produce a random number ranging between 2 and 5 which is different each time. For example, I don't want two 3's in a row, or two 5's in a row. I then have to feed this into a for() loop, like this:
for(var i = 0; i < noBoxes.length; i+=randNumber) {
//....
}
How do I go about doing this?
Generate a random number up to n-1 and add it on, modulo the original range (shifting because the min is not 0):
i = some random int from 2 to 5
delta = randInt(3) // range of possible values from 2 to 5 is 4, minus 1 to
// prevent getting all the way round to i again
nextval = (i-2+delta)%4+2 // shift i down by the minimum, add the
// delta and modulo the range
This works because it adds up to 1 below the range, so it can never get back to the original number. For example, i=3, random int 0 to 2, so the max is (i-2+2)%3+2=3%3+2=0+2=2.
function differentRandInt(min,max,current) {
var shiftedcurrent = current-min;
var range = max-min+1;
var delta = Math.floor((Math.random()*(range-1))+1);
return (shiftedcurrent + delta)%range + min;
}
So if i=3, then i-min is 1, the range after adding the delta is 2,3,4, modulo 4 yielding 2,3,0, so adding the min gives us 4,5,2.
Edit: more complete solution, and fixed off-by-one error:
var prev, randNumber;
for(var i = 0; i < noBoxes.length; i+=randNumber) {
do {
randNumber = 2 + Math.floor(Math.random() * 4);
} while(randNumber === prev);
prev = randNumber;
}
Like so: http://jsbin.com/eyitav/
var loopingValues = [2, 3, 4, 5],
len,
value,
rand;
while ( (len=loopingValues.length) ) {
rand = ~~( ( Math.random() % 1 ) * len);
value = loopingValues.splice(rand, 1)[0];
//alert( value )
}
Now you have your loop with value as the value 2, 3, 4 and 5 in random order.
Or easier to read: http://jsbin.com/ejizuw/
var loopingValues = [2, 3, 4, 5],
value,
rand;
while ( loopingValues.length ) {
rand = Math.random();
if ( rand === 1 ) {
rand = 0;
}
rand = rand * loopingValues.length;
rand = Math.floor(rand);
value = loopingValues[rand];
loopingValues.splice(rand, 1);
alert(value);
}
You can get your series in an array before you start looping-
var noBoxes={length:100};
var R= [2], r= 2, n= 2;
while(R.length<noBoxes.length){
n= R[R.length-1];
while(r== n) r= 2+Math.floor(Math.random()*4)
R.push(r);
}
And then loop through the array-
for(var i = 0; i < noBoxes.length; i+=R[i]) {
....
}

Self adjusting random list

I have a grid of 9 columns by 3 rows (so each column has 3 slots). A minimum of one slot in every column must be marked and no more than 3 can be marked. A mark is represented by a 1,2 or 3 digit.
There must always be 15 marked slots in total. To achieve this I tried the following:
var valueLeft = 15;
while (valueLeft > 0)
{
var ranValue = utils.getRandomInt(1,3);
console.log('slots remaining: ' + (valueLeft - ranValue));
if (ranValue >= valueLeft)
{
ranValue = valueLeft;
}
valueList.push(ranValue);
valueLeft -= ranValue;
}
console.log(valueList);
But this often gives me an array of numbers with less than 9 elements inside. I can see that my logic is flawed but I am pulling my hair out trying to figure out how I can get it to always give a set of 9 elements which all add up to 15.
For example what I might end up with is:
[2, 1, 1, 1, 1, 2, 3, 1, 2, 1]
When what I need for example is:
[2, 2, 1, 1, 1, 1, 3, 2, 2]
or
[2, 2, 1, 2, 1, 1, 3, 2, 1]
and so on.
Any advice appreciated. Feel free to flame me for my poor sense of logic :)
This answer shows a similar approach to many of those already posted, but I feel as though they're making it too complicated. It can be very straightforward:
function pad(list, size) {
var total = list.length;
while (total != size) {
var i = utils.getRandomInt(0, 8);
if (list[i] < 3) {
list[i]++;
total++;
}
}
return list;
}
var valueList = pad(new Array(1,1,1,1,1,1,1,1,1), 15);
You don't need a lot of cases. Just -- as many others have already said -- init the array with 1's. Then, simply add one to random elements (whose value isn't already 3) until the total is 15.
why don't you do this:
start off with an array that looks like this: 1,1,1,1,1,1,1,1,1
then make a function that picks a random number between 0 and 8 6 times.
if the same number has been picked more than twice, skip it and pick a new one
then correlate those 6 numbers to the index of the array and add 1 for each time it picks that number.
var i; var valueList = new Array(1,1,1,1,1,1,1,1,1);
for(i=0;i<6;i++)
{
var ranNum = utils.getRandomInt(0,8);
if(valueList[ranNum]<3) valueList[ranNum]+=1;
else i--;
}
just tested it, changed <=6 to <6 and it's working for me. Good luck!
Following logic should work. You should select a random value (within 1-3) such that choosing that would not lead us to not able to select a random value for further slots.
var gridLeft = 9
var valueLeft = 15
while(gridLeft>0) {
var ranValue
while(true) {
ranValue = utils.getRandomInt(1,3);
if (valueLeft-ranValue > 3*(gridLeft-1))
continue;
else if (valueLeft-ranValue < 1*(gridLeft-1))
continue;
else
break;
}
valueList.push(ranValue);
valueLeft -= ranValue;
gridLeft -= 1
}

how to randomize an integer variable, with odds on certain numbers appearing

To get a random whole number between 0 and 4 inclusive, I use the following code:
var random = Math.floor(Math.random() * 5);
However I want the number 4 to be chosen twice as often as the other numbers. How would I go about doing getting a random number between 0-4 inclusive, but with the number 4 appearing twice as many times as 0-3?
I think the easist way is to add the number twice to the array:
var nums = [0, 1, 2, 3, 4, 4];
var index = Math.floor(Math.random() * 6);
var random = nums[index];
But for something more generic, you may have a table:
var nums = [0, 1, 2, 3, 4];
var odds = [0.16667, 0.16667, 0.16667, 0.16667, 0.33333];
var r = Math.random();
var i = 0;
while(r > 0)
r -= odds[i++];
var random = nums[i];
Just do it like this:
var random = Math.floor(Math.random() * 6);
if (random == 5) random = 4;
Is this of any use:
http://www.javascriptkit.com/javatutors/weighrandom2.shtml
Math.floor(Math.random() * 2) == 0 ? Math.floor(Math.random() * 5) : 4
Here, 4 has 50% chance coz of first random selector, adding up 20% for second random selector.
I like sje397's initial answer with the correction that Paddy is suggesting:
var nums = [0, 1, 2, 3, 4, 4];
var random = nums[Math.floor(Math.random() * 6)];
The technique behind your requirement is to use a Shuffle Bag random generator. More details: http://web.archive.org/web/20111203113141/http://kaioa.com:80/node/53

Categories

Resources