I\'m a web-game developer and I got a problem with random numbers. Let\'s say that a player has 20% chance to get a critical hit with his sword. That means, 1 out of 5 hits
How about weighting the value?
For example, if you have a 20% chance to critical hit, generate a number between 1 and 5 with one number representing a critical hit, or a number between 1 and 100 with 20 numbers being a critical hit.
But as long as you are working with random or pseudorandom numbers, there's no way to potentially avoid the results you are currently seeing. It's the nature of randomness.
I agree with the earlier answers that real randomness in small runs of some games is undesirable -- it does seem too unfair for some use cases.
I wrote a simple Shuffle Bag like implementation in Ruby and did some testing. The implementation did this:
It is deemed unfair based on boundary probabilities. For instance, for a probability of 20%, you could set 10% as a lower bound and 40% as an upper bound.
Using those bounds, I found that with runs of 10 hits, 14.2% of the time the true pseudorandom implementation produced results that were out of those bounds. About 11% of the time, 0 critical hits were scored in 10 tries. 3.3% of the time, 5 or more critical hits were landed out of 10. Naturally, using this algorithm (with a minimum roll count of 5), a much smaller amount (0.03%) of the "Fairish" runs were out of bounds. Even if the below implementation is unsuitable (more clever things can be done, certainly), it is worth noting that noticably often your users will feel that it's unfair with a real pseudorandom solution.
Here is the meat of my FairishBag
written in Ruby. The whole implementation and quick Monte Carlo simulation is available here (gist).
def fire!
hit = if @rolls >= @min_rolls && observed_probability > @unfair_high
false
elsif @rolls >= @min_rolls && observed_probability < @unfair_low
true
else
rand <= @probability
end
@hits += 1 if hit
@rolls += 1
return hit
end
def observed_probability
@hits.to_f / @rolls
end
Update: Using this method does increase the overall probability of getting a critical hit, to about 22% using the bounds above. You can offset this by setting its "real" probability a little bit lower. A probability of 17.5% with the fairish modification yields an observed long term probability of about 20%, and keeps the short term runs feeling fair.
Unfortunately what you are asking for is effectively an non-random number generator - because you want previous results to be taken into account when determining the next number. This isn't how random number generators work I'm afraid.
If you want 1 out of every 5 hits to be a critical then simply pick a number between 1 and 5 and say that that hit will be a critical.
You could create a list containing the numbers from 1 to 5, and have them sorted by randomness. Then just go through the list you created. You have a guarantee of running into every number at least once... When you're through with the first 5, just create another 5 numbers...
Your best solution might be play-testing with multiple different nonrandom schemes and pick the one that makes players happiest.
You might also try a back-off policy for the same number in a given encounter, e.g., if a player rolls a 1
on their first turn accept it. To get another 1
they need to roll 2 1
s in a row. To get a third 1
they need 3 in a row, ad infinitum.
I recommend a progressive percentage system like Blizzard uses: http://www.shacknews.com/onearticle.x/57886
Generally you roll a RNG then compare it to a value to determine if succeed or not. That may look like:
if ( randNumber <= .2 ) {
//Critical
} else {
//Normal
}
All you need to do is add in a progressive increase in base chance...
if (randNumber <= .2 + progressiveChance ) {
progressiveChance = 0;
//Critical
} else {
progressiveChance += CHANCE_MODIFIER;
//Normal hit
}
If you need it to be more fancy it's pretty easy to add in more. You can cap the amount that progressiveChance can get to avoid a 100% critical chance or reset it on certain events. You can also have progressiveChance increase in smaller amounts each boost with something like progressiveChance += (1 - progressiveChance) * SCALE where SCALE < 1.