I spent the last week reading about Luck Mitigation and what a dev can do to avoid extreme cases where the player's psychology will be messed up.
I came up to this question How do I avoid “too” lucky / unlucky streaks in random number generation?, plus some others outside GameDev site. To illustrate my question, let's have this simple game example.
We have a turned base fighting game, like BiteFight. Two characters have their attributes and the match outcome is based entirely to those attributes.
In specific:
Player 1: Str: 10 Def: 15 Agl: 20
Player 2: Str: 12 Def: 20 Agl: 15
The process is simple:
If rnd() * P1_Agl >= rnd() * P2_Agl:
P1 will attack
else:
P1 will attack
Then with the same way in case of Player 1 is attacking
If rnd() * P1_Str >= rnd()* P2_Def:
reduce life points from P2
Repeat the same unlimited times until one of players' life reach zero.
Solutions I found:
Instead of 1
rnd()
call, use x-timesrnd()
and get the average. Something like( rnd() + rnd() + rnd() ) / 3
. This will move the results closer to 0.5 and make the whole process less "random". However, it is still possible enough to have streaks of lucky or unlucky events.Keep track of the results and if you have x-times the same result then hard-code the other. For example, if on the above, P1 won the Agility test 3 times in the row, then the 4th time will always go to P2. What I didn't like on this are the predictability. Users might understand that behavior through time. But the most important is what would happen if P1 uses an item that changes his attributes on the 4th step? When you hard-code the outcome, the result would be based on different attributes.
I read many times the suggestion to use a Moving Average or a Cumulative Moving Average on the
rnd()
to guess if their is a streak of lucky results. For example, if Player 1 keeps gettingrnd()
above 0.9, it is a lucky streak. I wrote a script to calculate those two and here is an example for CMA and MA
Cumulative Moving Average
0 : Random number: 0.348 CMA: 0.348
1 : Random number: 0.74 CMA: 0.544
2 : Random number: 0.967 CMA: 0.685
3 : Random number: 0.578 CMA: 0.659
4 : Random number: 0.932 CMA: 0.713
5 : Random number: 0.134 CMA: 0.617
6 : Random number: 0.397 CMA: 0.585
7 : Random number: 0.537 CMA: 0.579
8 : Random number: 0.861 CMA: 0.611
9 : Random number: 0.893 CMA: 0.639
Moving Average
0 : Random number: 0.295 Moving Average: 0.098
1 : Random number: 0.866 Moving Average: 0.354
2 : Random number: 0.768 Moving Average: 0.492
3 : Random number: 0.888 Moving Average: 0.624
4 : Random number: 0.94 Moving Average: 0.729
5 : Random number: 0.325 Moving Average: 0.595
6 : Random number: 0.823 Moving Average: 0.671
7 : Random number: 0.208 Moving Average: 0.517
8 : Random number: 0.743 Moving Average: 0.592
9 : Random number: 0.038 Moving Average: 0.407
So, I end up to my 2 questions
1) Even if I read many times the suggestion of Moving Average, no-one mentioned how exactly you use it. So, my guess is that each time, you compare the rnd()
with the moving average and based on that you take some actions. But what exactly you can do?
2) In the above example, you can see that we have two comparisons. One for Agility and one for Strength. Should one use two different Moving Averages to "identify" lucky streaks for each case, or it should be one for every case that RNG comes to the game?