/    Sign up×
Community /Pin to ProfileBookmark

How to make a rarities system in JS?

So for my game there are many occasions where I want to mark a percent for each item. Example:

“`javascript
const items = [“Test”, “Test 2”, “Another Test”]
const rarities = [“Common”, “Rare”, “Legendary”]
const percents = [“80%”, “19%”, “1%”]
// “Test” would be Common with 80% chance, “Test 2” would be Rare with 19% chance, etc.

// Then here, somehow get a random item based on their percentages.
“`

I’m wondering how I could do something like that. Thanks!

to post a comment
JavaScript

43 Comments(s)

Copy linkTweet thisAlerts:
@SempervivumJan 20.2022 — I encountered such task some time ago and used the following procedure:

Create a new array containing "Test" 80 times (according percentage), "Test2" 19 times and "Another Test" 1 times. Then use Math.random() in order to pick a random item from that array.
Copy linkTweet thisAlerts:
@BostauthorJan 20.2022 — @Sempervivum#1641717 Does it matter what order they're in? Also, do you mind giving me a function for that?
Copy linkTweet thisAlerts:
@SempervivumJan 20.2022 — I assume that the order is not relevant when accessing the second array through a random index. I'll prepare some sample code ASAP.
Copy linkTweet thisAlerts:
@SempervivumJan 20.2022 — This is the code I prepared:
``<i>
</i> const items = ["Test", "Test 2", "Another Test"]
const rarities = ["Common", "Rare", "Legendary"]
const percents = ["80%", "19%", "1%"]
let helpArray = [],
iHelpArray = 0
// for each entry in array "percents":
percents.forEach((item, idx) =&gt; {
// enter current item from "items" multiplely into
// helpArray. Number according to percentage in "percents":
for (let j = 0; j &lt; parseInt(item); j++, iHelpArray++) {
helpArray[iHelpArray] = items[idx]
}
})
console.log(helpArray)

// Get item from random position in helpArray:
function pickItem() {
const idx = Math.floor(Math.random() * helpArray.length)
return helpArray[idx];
}
// for testing:
for (let k = 0; k &lt; 100; k++) {
console.log(pickItem())
}<i>
</i>
``

Give it a try.
Copy linkTweet thisAlerts:
@BostauthorJan 21.2022 — @Sempervivum#1641761 Thanks!!
Copy linkTweet thisAlerts:
@BostauthorJan 21.2022 — @Sempervivum#1641761 So basically, helpArray just duplicates them for me and the other function just gets random one? Nice, thanks again!
Copy linkTweet thisAlerts:
@SempervivumJan 21.2022 — You're welcome!
>So basically, helpArray just duplicates them for me and the other function just gets random one?

Yes, item "Test" is in there 80 times and item "Test 2" 19 times.
Copy linkTweet thisAlerts:
@BostauthorFeb 01.2022 — @Sempervivum#1641773 I have one more question. What if I was to set one of the percentages to something with a decimal? Would it have to be out of 1000 instead of 100?
Copy linkTweet thisAlerts:
@daveyerwinFeb 01.2022 — @Sempervivum#1641761

I solved it like this ...
``<i>
</i>&lt;5CRIPT&gt;
var a, b=0, c=0, d=0
for(let i=0;i&lt;2000;i++){
a = Math.random();
if(a &lt; .1)a=1;
if(a &lt; .4)a=2;
switch(a){
case 1: //about 10%
b++;
break;
case 2: //about 30%
c++;
break;
default: //about 60%
d++;
}
}
alert( b + 'n' + c + 'n' + d )
&lt;/5CRIPT&gt;<i>
</i>
`</CODE>
<POSTMENTION discussionid="398832" displayname="Bost" id="1641767" number="7" username="Bost">@Bost#1641767</POSTMENTION> <br/>
by adjusting these lines you can have any %s you like
<CODE>
`<i>
</i> if(a &lt; .1)a=1;
if(a &lt; .4)a=2;<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumFeb 01.2022 — @DaveyErwin#1642081 That's a fine solution, thanks for posting.
Copy linkTweet thisAlerts:
@BostauthorFeb 02.2022 — @Sempervivum#1642111 Also, is there a better way to calculate percentages than distributing them manually (e.g. is there a calculator that would give Uncommon a 37%, etc. but based on how many items there are?) Thanks.
Copy linkTweet thisAlerts:
@SempervivumFeb 02.2022 — @Bost#1642119 Do I understand this correctly? You have a collection of items like this:

`const items = ["item2", "item1", "item2", "item3", "item1", "item2", "item1", "item1", "item2", "item1"];`

and intend to calculate the rarity or probability for each of them?
Copy linkTweet thisAlerts:
@NogDogFeb 02.2022 — I suck at JS, so I'm going to throw you this bit of PHP, and see if it makes sense for what you're really trying to solve. If so, then one of the JS experts can convert it if feasible. :)
[code=php]
// group items by rarity in sub-arrays:
$items = [
"common" => ["this", "that", "the other thing"],
"rare" => ["cool stuff", "pretty thing"],
"legendary" => ["awesome", "omg"]
];

$dice = rand(1, 100);
if($dice == 1) { $key = "legendary"; }
elseif($dice <= 20) { $key = "rare"; }
else { $key = "common"; }
// grab a random element of the applicable sub-array:
$item = $items[$key][array_rand($items[$key])];

echo "You rolled a $dice and recevied a '$item'.n";
[/code]

Sample output:
[code=text]
01:16 $ php -f random_item.php
You rolled a 65 and recevied a 'this'.
~
01:17 $ php -f random_item.php
You rolled a 58 and recevied a 'this'.
~
01:17 $ php -f random_item.php
You rolled a 6 and recevied a 'pretty thing'.
~
[/code]
Copy linkTweet thisAlerts:
@daveyerwinFeb 02.2022 — > @Bost#1642119 is there a better way to calculate percentages than distributing them manually

takes an array of any length and calls the array items

probability based on part of a sine wave (first 90 degrees about)

with the highest index given the most calls

``<i>
</i>&lt;sctipt&gt;
var targets=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'];
var num=targets.length;
var step = 1/num;
var steps = [];
var offset;
steps.push(Math.sin(step));
for(let i=1;i&lt;num;i++){
steps.push((Math.sin(step*i)+steps[i-1]));
}
offset = 1 / steps[num-1];
var obj={};
for(let i=0;i&lt;num;i++)obj[targets[i]]=0;

for(let i=0;i&lt;2000;i++){
var rnd = Math.random();
for(let i=0;i&lt;num;i++){
if(rnd&lt;steps[i]*offset){
obj[targets[i]]++;
break;
}
}
}
console.log(obj)
&lt;/sctipt&gt;<i>
</i>
``



sample output

a: 15 === .75%

b: 20 === 1%

c: 35 === 1.75%

d: 43 === 2.15%

e: 64 === 3.2%

f: 96

g: 107 === 5.35%

h: 126

i: 147

j: 140

k: 159 === 7.95%

l: 191

m: 211

n: 198

o: 219 === 10.95

p: 229 === 11.45%
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — @DaveyErwin#1642157 If it's big to small, then why are the numbers not like that? Also, would I just replace each letter with an item?
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — @Sempervivum#1642120 Yes, based on rarities ("Uncommon", "Rare", "Epic", "Legendary", etc.) and on amount there is, because distributing them equally is really hard.
Copy linkTweet thisAlerts:
@SempervivumFeb 03.2022 — @Bost#1642179 Now it seems to me that I understood the question in your first posting completely wrong: You did not intend to have a function that **picks** an item with a rarity given but instead you need to calculate the rarity for a given set of items. Thus your array `items</C> was just an example and in reality there are much more items.
<CODE>
`<i>
</i> const percents = [
"80.5%", // rarity is higher than 18.5 % but lower than 80.5 %
"18.5%", // rarity is higher than 1% but lower than 18.5 %
"1%" // rarity is lower than 1%
];<i>
</i>
``
Copy linkTweet thisAlerts:
@daveyerwinFeb 03.2022 — > @Bost#1642178 would I just replace each letter with an item

well, what is an item,

is it a string?

is it an Object?

is it a Number?
Copy linkTweet thisAlerts:
@daveyerwinFeb 03.2022 — > @Bost#1642178 If it's big to small, then why are the numbers not like that?

a: 17

b: 21

c: 34

d: 51

e: 71

f: 85

g: 113

h: 132

i: 156

j: 150

k: 173

l: 193

m: 175

n: 213

o: 210

p: 206

> @DaveyErwin#1642157 with the highest index given the most calls

p: is at the highest index value in the array

there is a random wiggle in the data sometimes

because I use thr random function
Copy linkTweet thisAlerts:
@SempervivumFeb 03.2022 — @Bost#1642179 I coded this, hope that I understand your intention correctly now:
``<i>
</i> const rarities = ["Common", "Rare", "Legendary"];
const percents = [
"70%", // rarity is higher than 28 % but lower than 70 %
"28%", // rarity is higher than 2% but lower than 28 %
"2%" // rarity is lower than 2%
];

// First we get the number of each item:
let nrs = {};
items.forEach(item =&gt; {
if (nrs[item]) {
nrs[item].nr++
} else {
nrs[item] = {};
nrs[item].nr = 1;
}
});
// Now we determine the rarity for each item:
for (item in nrs) {
// get percentage of this item:
itemPercent = nrs[item].nr / items.length * 100;
let limit = 0;
// loop through percentages for the rarities:
for (i = percents.length - 1; i &gt;= 0; i--) {
// update limit:
limit += parseFloat(percents[i]);
// if item's percentage is lower than limit:
if (itemPercent &lt; limit) {
// add the rarity to the record in "nrs":
nrs[item].rarity = rarities[i];
// leave loop;
break;
}
};
};
console.log(nrs);<i>
</i>
``

Result:

item010: {nr: 11, rarity: 'Legendary'}

item185: {nr: 180, rarity: 'Rare'}

item805: {nr: 809, rarity: 'Common'}

(I created 1000 items and named them according their percentage)
Copy linkTweet thisAlerts:
@daveyerwinFeb 03.2022 — slight tweak
``<i>
</i>&lt;scripr&gt;
var targets=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'];
var num=targets.length;
var step = 1/num;
var steps = [];
var offset;
steps.push(Math.sin(step));
for(let i=1;i&lt;num;i++){
steps.push((Math.sin(step*i)+steps[i-1]));
}
offset = 1 / steps[num-1];
var obj={};
for(let i=0;i&lt;num;i++)obj[targets[i]]=0;

for(let i=0;i&lt;10000;i++){
var rnd = Math.random();
for(let i=0;i&lt;num;i++){
if(rnd&lt;steps[i]*offset){
obj[targets[i]]++;
break;
}
}
}
for (key in obj) console.log(key,'=',obj[key]/100,'%');
}
&lt;/scripr&gt; <i>
</i>
``

outputs

a = 0.87 %

b = 1.07 %

c = 1.93 %

d = 2.5 %

e = 3.37 %

f = 4.43 %

g = 5.12 %

h = 5.63 %

i = 6.59 %

j = 8.01 %

k = 8.59 %

l = 8.96 %

m = 9.6 %

n = 11.05 %

o = 10.5 %

p = 11.78 %

sum of all %s equals 100%
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — > @Sempervivum#1642180 Now it seems to me that I understood the question in your first posting completely wrong: You did not intend to have a function that picks an item with a rarity given but instead you need to calculate the rarity for a given set of items.

Well it's kind of both. Because that first example that you replied with did work, and I've been implementing it in some places of my website, but I just wanted to be able to have a script or something distribute them for me.
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — @Sempervivum#1642184
>Wait I am confused. Where did you get the percentages in the beginning variable? That's what I'm trying to calculate.

These values define how a rarity is rated: E. g. if the number of occurrences is between 2% and 28% that item is considered to be Rare. The values in this array are not based on the occurrences being counted.
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — @Sempervivum#1642180 I don't need to change the code I don't think, I just wanted a calculator or script that can calculate it for me, because it's hard to distribute them.
Copy linkTweet thisAlerts:
@BostauthorFeb 03.2022 — @Sempervivum#1642180 I don't need to change the code I don't think, I just wanted a calculator or script that can calculate it for me, because it's hard to distribute them.
Copy linkTweet thisAlerts:
@BostauthorFeb 10.2022 — @Sempervivum#1642184 I tried that code just now and it all said common...
Copy linkTweet thisAlerts:
@SempervivumFeb 11.2022 — @Bost#1642347

I tested this again and created the items manually this time:
``<i>
</i> const items = [
// item60 60 times:
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60', 'item60',
// item20 20 times:
'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20',
'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20', 'item20',
// item02 2 times:
'item02', 'item02'
];
console.log(items);
const rarities = ["Common", "Rare", "Legendary"];
const percents = [
"70%", // rarity is higher than 25 % but lower than 70 %
"25%", // rarity is higher than 5% but lower than 25 %
"5%" // rarity is lower than 5%
];

// First we get the number of each item:
let nrs = {};
items.forEach(item =&gt; {
if (nrs[item]) {
nrs[item].nr++
} else {
nrs[item] = {};
nrs[item].nr = 1;
}
});
// Now we determine the rarity for each item:
for (item in nrs) {
// get percentage of this item:
itemPercent = nrs[item].nr / items.length * 100;
let limit = 0;
// loop through percentages for the rarities:
for (i = percents.length - 1; i &gt;= 0; i--) {
// update limit:
limit += parseFloat(percents[i]);
// if item's percentage is lower than limit:
if (itemPercent &lt; limit) {
// add the rarity to the record in "nrs":
nrs[item].rarity = rarities[i];
break;
}
};
};
console.log(nrs);<i>
</i>
``

Result:
>Object

item02: {nr: 2, rarity: 'Legendary'}

item20: {nr: 20, rarity: 'Rare'}

item60: {nr: 60, rarity: 'Common'}


How did you perform your test and create the items?
Copy linkTweet thisAlerts:
@daveyerwinFeb 11.2022 — takes an array of any length

``<i>
</i>'use strict';
var targets = ['a','b','c','d','e','f','g','h','i','j','k','l','m'];
var num = targets.length;
var step = 1/num;
var steps = [];
var offset=Math.sin(step);
steps.push(offset);
for(let i = 1; i &lt; num; i++){
let a = Math.sin(step*i)+steps[i-1];
offset += a;
steps.push(a);
}
offset = 100/(offset);
for(let i=0;i&lt;num;i++){
steps[i]=steps[i]*offset;
var s = 'common';
if(steps[i]&lt;1)s='legend';
else if(steps[i]&lt;6)s='rare';
console.log(targets[i],'---&gt; perCent =',steps[i],s)
}<i>
</i>
``


logs ...

a ---> perCent = 0.27805993868239276 legend

b ---> perCent = 0.5561198773647855 legend

c ---> perCent = 1.110595240792887 rare

d ---> perCent = 1.9382067271477215 rare

e ---> perCent = 3.034059641314561 rare

f ---> perCent = 4.3916728432973215 rare

g ---> perCent = 6.00301707925287 common

h ---> perCent = 7.858562468446208 common

i ---> perCent = 9.947334865277153 common

j ---> perCent = 12.256980763039826 common

k ---> perCent = 14.773840355558356 common

l ---> perCent = 17.483028324594574 common

m ---> perCent = 20.368521875231355 common

sum of all %s = 100%
Copy linkTweet thisAlerts:
@BostauthorFeb 11.2022 — @Sempervivum#1642358 I'm not trying to find out rarities, I'm trying to find percentages from rarities and amount. If I had, **for example**, 3 uncommon items, 3 rare items, and 2 legendary items, I want to find out the percentage that each one would get, maybe by dividing the remaining by 3 to get the percentages of each uncommon or something? I don't really know which is why I'm asking, but this is what I'm trying to find. Also, it doesn't have to be a script, it could just be a math equation I can punch into a calculator.
Copy linkTweet thisAlerts:
@BostauthorFeb 11.2022 — @DaveyErwin#1642367 Neat, but why are they such big numbers? Is there some way I can round them without them not summing up to 100%? Thanks
Copy linkTweet thisAlerts:
@daveyerwinFeb 11.2022 — > @Bost#1642379 Is there some way I can round them

sure, several ways heres one

toFixed () method

but they are not for display

I can't imagine why you would display them in your program?
Copy linkTweet thisAlerts:
@daveyerwinFeb 11.2022 — > @Bost#1642378 If I had, for example, 3 uncommon items, 3 rare items, and 2 legendary items, I want to find out the percentage that each one would get,

okay, i sorta of get a glimmer from this

so in my code each item already has a per centage assigned to it

so maybe just add the per centages that are already assigned

to the items you have

when you have every item the per centages will add to 100
Copy linkTweet thisAlerts:
@BostauthorFeb 12.2022 — > @DaveyErwin#1642382 I can't imagine why you would display them in your program?

It's sort of like a crate of pets kind of thing, and the users probably need to know the percentage of each item to know how rare it is to get.
Copy linkTweet thisAlerts:
@daveyerwinFeb 12.2022 — > @Bost#1642385 users probably need to know the percentage of each item to know how rare it is to get.



each item is already designated as legendary, rare or common

yeah I get it

so use toFixed()

https://www.w3schools.com/jsref/jsref_tofixed.asp

when it's time to display the per centage
Copy linkTweet thisAlerts:
@BostauthorFeb 12.2022 — @DaveyErwin#1642367 So how can I pick a random one based on percentage if these numbers have so much after the decimal?
Copy linkTweet thisAlerts:
@daveyerwinFeb 12.2022 — > @Bost#1642390 So how can I pick a random one based on percentage if these numbers have so much after the decimal?

``<i>
</i>&lt;!DOCTYPE html&gt;
&lt;button onclick=clicked()&gt;click&lt;/button&gt;
&lt;div id=dsply&gt;&lt;/div&gt;


&lt;__script&gt;
var targets=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'];
var num=targets.length;
var step = 1/num;
var steps = [];
var offset=Math.sin(step);
steps.push(offset);
for(let i=1;i&lt;num;i++){
let a = Math.sin(step*i)+steps[i-1];
offset += a;
steps.push(a);
}
offset = 100 / offset;
for(let i=0;i&lt;num;i++)steps[i]=steps[i]*offset;
var obj={};
for(let i=0;i&lt;num;i++){
let a=obj[targets[i]]={};
a.perCent=steps[i];
let b="common";
if(steps[i] &lt; .6)b="legendary";///////
else if(steps[i] &lt; 3.5)b="rare";///////
a.rareity=b;
}
function clicked(){
let rnd=Math.random()*steps[steps.length-1];
for(let i=0;i&lt;num;i++){
if(rnd&lt;steps[i]){
dsply.textContent=targets[i]+' '+obj[targets[i]].rareity+" "+obj[targets[i]].perCent.toFixed(2);
break;
}
}
}
&lt;/__script&gt; <i>
</i>
``
Copy linkTweet thisAlerts:
@BostauthorFeb 12.2022 — @DaveyErwin#1642391 But I don't want a whole script to pick them. I just want a math function or something to calculate the percentages. I still want to use @Sempervivum's method.
Copy linkTweet thisAlerts:
@daveyerwinFeb 12.2022 — > @Bost#1642414 But I don't want a whole script to pick them

yes

the whole button and dsply thing was only

for the purpose of demonstrating that

the perCentages were calculated, stored and usable

the distribution of the perCentages is based on the

first 90 degrees of a sine wave
Copy linkTweet thisAlerts:
@BostauthorFeb 14.2022 — > @DaveyErwin#1642416 the distribution of the perCentages is based on the

> first 90 degrees of a sine wave


What would I type in a calculator to get the value of each one? Sorry I'm bad at math
Copy linkTweet thisAlerts:
@delonbestApr 26.2022 — > @DaveyErwin#1642388 so use toFixed()

> [https://www.w3schools.com/jsref/jsref_tofixed.asp](https://www.w3schools.com/jsref/jsref_tofixed.asp)


toFixed() will NOT round correctly in some cases.

To correct the rounding problem with the previous Math.round() and toFixed(), you can define a custom [JavaScript round](http://net-informations.com/js/progs/round.htm) function that performs a "nearly equal" test to determine whether a fractional value is sufficiently close to a midpoint value to be subject to midpoint rounding. The following function return the value of the given number rounded to the nearest integer accurately.

``<i>
</i>Number.prototype.roundTo = function(decimal) {
return +(Math.round(this + "e+" + decimal) + "e-" + decimal);
}<i>
</i>
`</CODE>

<CODE>
`<i>
</i>var num = 9.7654;
console.log( num.roundTo(2)); //output 9.77<i>
</i>
``
Copy linkTweet thisAlerts:
@BillyAkinbredalikMay 13.2022 — Usually there are additional teirs that are really rare and not limited to 1 peice at a time.

Usually this niche is associated with set bonuses as well.

Destiny is getting close to adding these things, with combos like Thorn and Necrotic as well as mod combos like with Aeon.

Set bonuses would bridge the big open gap in the rarity system between Legendaries and Exotics atm.
Copy linkTweet thisAlerts:
@StevenMBrunMay 19.2022 — Very interesting post, thank you very much.
Copy linkTweet thisAlerts:
@carlosridgAug 12.2022 — > @DaveyErwin#1642388 yeah I get it

> so use toFixed()


It seems like Math.round() is a better solution, but it is not! In some cases it will NOT round correctly. Also, toFixed() will NOT round correctly in some cases.

To correct the rounding problem with the previous Math.round() and toFixed(), you can define a custom [JavaScript round](http://net-informations.com/js/progs/round.htm) function that performs a "nearly equal" test to determine whether a fractional value is sufficiently close to a midpoint value to be subject to midpoint rounding. The following function return the value of the given number rounded to the nearest integer accurately.

``<i>
</i>Number.prototype.roundTo = function(decimal) {
return +(Math.round(this + "e+" + decimal) + "e-" + decimal);
}

var num = 9.7654;
console.log( num.roundTo(2)); //output 9.77<i>
</i>
``
×

Success!

Help @Bost spread the word by sharing this article on Twitter...

Tweet This
Sign in
Forgot password?
Sign in with TwitchSign in with GithubCreate Account
about: ({
version: 0.1.9 BETA 3.29,
whats_new: community page,
up_next: more Davinci•003 tasks,
coming_soon: events calendar,
social: @webDeveloperHQ
});

legal: ({
terms: of use,
privacy: policy
});
changelog: (
version: 0.1.9,
notes: added community page

version: 0.1.8,
notes: added Davinci•003

version: 0.1.7,
notes: upvote answers to bounties

version: 0.1.6,
notes: article editor refresh
)...
recent_tips: (
tipper: @darkwebsites540,
tipped: article
amount: 10 SATS,

tipper: @Samric24,
tipped: article
amount: 1000 SATS,

tipper: Anonymous,
tipped: article
amount: 10 SATS,
)...