Battle Mechanics - Distributed Chances

Started by Atelier, Tue 16/08/2011 19:32:32

Previous topic - Next topic

Atelier

Say the player has a maximum damage of 10 (in a battle).

A random number from the range 1-10 is deducted from the opponent's defence/health each turn.

What I'd like to implement is a distributed chance so they're more likely to hit with numbers in the middle of this range (5s and 6s); and only a small chance of hitting 1s or 10s.

For example:

1 - 3%
2 - 4%
3 - 6%
4 - 12%
5 - 25%
6 - 25%
7 - 12%
8 - 6%
9 - 4%
10 - 3%

If the player's maximum damage was constantly 10 I may be able to work something out.
But seeing as maximum damage isn't static (depending on your level and weapon rating it ranges from 10-200+), I'm at a loss where to begin. I know it's more of a maths problem :P

Atelier

Khris

A really simple method would be:

Code: ags
  int damage = Random(5) + Random(5);


0/10:  1 in 36
1/ 9:  2 in 36
2/ 8:  3 in 36
3/ 7:  4 in 36
4/ 6:  5 in 36
   5:  6 in 36

monkey0506

Well, it's pretty simple with the Random function:

Code: ags
int percent = Random(99); // change this to 100 if you want a "miss" condition at 1% chance
int damage;
if (percent < 3) damage = 1; // 0..2
else if (percent < 7) damage = 2; // 3..6
else if (percent < 13) damage = 3; // 7..12
else if (percent < 25) damage = 4; // 13..24
else if (percent < 50) damage = 5; // 25..49
else if (percent < 75) damage = 6; // 50..74
else if (percent < 87) damage = 7; // 75..86
else if (percent < 93) damage = 8; // 87..92
else if (percent < 97) damage = 9; // 93..96
else damage = 10; // 97..99 // else if (percent < 99)
// else damage = 0; // miss


Regarding the dynamic range of the maximum percentage, you could replace the damage = ...; lines with something that would be more appropriate, but it depends what you want. If the maximum damage is 200, should the range be from 191-200, or perhaps, 150-200? That would affect the next stage of developing this idea, so let us know what you need there and we can help you further.

Edit: Khris beat me.

Atelier

@ Khris: Sorry, I must be really slow tonight; where is the 36 from? ;D

@ Monkey: 191-200 sounds more appropriate. Here's how damage is calculated:

BASE damage is player level * 10. (At level 1, base damage is 10, level 2 is 20, etc). Then a percentage of your base damage is added on to give overall max damage. This percentage is defined by your equipped weapon rating. Ratings go from 1-10:

A weapon rating of 1 gives a 5% bonus, 2 gives a 10% bonus... 10 gives a 50% bonus.

For example, at level 3 with a weapon rating of 5, your overall damage will be 37. Therefore max damage is not always round, or even, which may restrict ways of calculating it?

Khris

Random(5) returns a random int in the range of 0 to 5, so there are 6 distinct outcomes, just like throwing a standard 6-faced die.
If you're throwing two dice, each result has a chance of 1/6 * 1/6 = 1/36.

Independent of levels and weapon ratings, there's going to be a range of damage, just like in Diablo, right?
For instance, at level 3 with a weapon rating of 5, the damage dealt will be 33-39 or something like that, correct?

What I'm saying is, calculating the range and calculating the actual damage based on that range are two entirely different issues.

Atelier

Ah I see.

Quote from: Khris on Wed 17/08/2011 03:16:12
Independent of levels and weapon ratings, there's going to be a range of damage, just like in Diablo, right?
For instance, at level 3 with a weapon rating of 5, the damage dealt will be 33-39 or something like that, correct?

Yep, originally I just calculated the range as 1 to Max.

But now I think it would be more appropriate for the range to be (Max-9) to Max.    ie, 28 to 37, 54 to 63, etc.

In which case, there are still only 10 outcomes, so I think it would be only a small step to adapt monkey's code?

Code: ags


int percent = Random(99);

int damage = ((info[player.ID].level*10)+((info[player.ID].level*10)*(gear[equipped.weapon].rating*5)/100));

if (percent < 3) damage -= 9;
else if (percent < 7) damage -= 8;
else if (percent < 13) damage -= 7;
else if (percent < 25) damage -= 6;
else if (percent < 50) damage -= 5;
else if (percent < 75) damage -= 4;
else if (percent < 87) damage -= 3;
else if (percent < 93) damage -= 2;
else if (percent < 97) damage -= 1;

// opponent health - damage;



So to get to the appropriate range you just subtract from damage like this. This is untested but I'd still be unsure whether it's flawed?

monkey0506

When you're calculating the weapon's rating percentage, it might be useful to include one more set of parenthesis due to operator precedence which may be reversed. For example, if the weapon rating is supposed to calculate to 5% of the base damage, but the operator precedence is set to right-to-left (instead of left-to-right) then you'd end up doing 5/100 before you do the base*5, which in integer mathematics would result in doing base*0 instead of (base*5)/100.

For your own projects this probably isn't something you'd need to worry about since you're probably not going to be changing that, but this is the reason why I'm such a big proponent of using more parenthesis. If someone happens to come along and copy your solution but has the setting inverted then they would get different results.

Other than that I can't see any reason why it wouldn't work. Khris' solution is a bit more algorithmic and cool, but I just went with the first thing that came to my head for the values you specified (and although it's probably not a set-in-stone thing, his values don't match yours exactly :P).

Khris

I was thinking about an alternative way of getting to the distribution.
The downside of my sum of randoms solution is that it's really linear. If you plotted result versus frequency you'd get a graph like this: /\

I was thinking about using a function based on logarithms and came up with this:

Code: ags
int chance_dist() {
  int rr = Random(97) + 2;  // 2-99
  float rf = IntToFloat(rr) - 0.5;  // 1.5 - 98.5
  
  float res;
  float base = 2.0;
  float mirror_y = Maths.Log(50.0)/Maths.Log(base);
  // formula
  if (rf < 50.0) res = Maths.Log(rf)/Maths.Log(base);
  else res = 2.0*mirror_y - Maths.Log(100.0-rf)/Maths.Log(base);
  
  // convert result range to 1-10
  res = (res*5.5)/mirror_y;
  return FloatToInt(res, eRoundNearest);
}


Neither clean nor customizable, but functional.

What this does is put the random value through a function, the function graph can be seen to the right in this picture:



As you can see, running the function 500 times produced 5 or 6 almost exactly half the time (248 out of 500), and 1 or 10 came up 25 times out of 500, which is incidentally pretty much exactly the distribution you had in mind.

I should have coded this so you can supply the range but I'm too lazy right now :)

In essence, chance_dist() is the same as Random(9)+1 except results closer to 5.5 are more likely.

SMF spam blocked by CleanTalk