home » builders » builder's lessons » imbricated rand progs
Imbricated rand progs
The following lesson was written by Dalvyn after he was exploring the use of 'if rand(%)' to make the actions of mobs less predictable.
First let us look at this example fight prog on a wizard:
>fight_prog 100~ if rand(10) cast 'stone skin' else if rand(50) cast 'magic missile' $n else if rand(10) cast 'shield' else if rand(10) cast 'armor' else if rand(10) cast 'bulls strength' else if rand(10) cast 'fire shield' else cast 'shocking grasp' $n endif endif endif endif endif endif ~
Is there any reason this might be a bad idea? If I had the patience, I could make mobs do some neat stuff with code like this.
The answer below is a rather long and boring explanation for those who want to know the details. If you just want a balanced program (where you have 3, 4, 5, ... options all of them with the same chance of happening) and do not want the details, the answer is further below.
All classes come with a standard fight_prog I think. But if you really want your mob to be efficient and if you don't want it to just do some standard actions / cast some standard spells, I think it's a very good idea to add a fight_prog with some random actions.
A note about imbricated rand tests, that is, rand tests in other rand tests... you have to remember that each "rand" test is like a choice with 2 options: the "if" part and the "else" part. I saw in your program above that you have rand(..) tests with numbers summing up to 100%. This program above won't make the mob cast "magic missile" with 50% chance, or "fire shield" with 10% chance though... it's a bit more tricky.
I'll use the (smaller) program below to explain
if rand(40) cast 'magic missile' else if rand(50) cast 'lightning bolt' else cast 'shocking grasp' endif endif
In this program, the first test is "if rand(40)". That means that there is 40% chance that the mob casts 'magic missile' and thus, 60% chance that the "else" part is used. In this else part, there is 50% chance that the mob casts "lightning bolt" and, if not, the mob casts "shocking grasp". So, if you want to sum it up with a small diagram...
/-- 40% -- magic missile | ------| | /-- 50% -- lightning bolt | | \-- 60% -- first else part ----| | \-- 50% -- shocking grasp
That means that, when this fight_prog is triggered,
- there is 40% chance that the mob casts 'magic missile'
- and 60% chance that the mob does something else.
In these 60%, there are 2 options:
- 50% chance to cast lightning bolt
- 50% chance to cast shocking grasp
/---------------------|----------------------------\ | 40 % | 60 % | \---------------------|----------------------------/ magic missile /--------------|-------------\ | 50 % | 50 % | \--------------|-------------/ lightning bolt shocking grasp
So, finally, there is
- 40% chance for magic missile
- 30% chance for lightning bolt (50% of 60%)
- 30% chance for shocking grasp (the last 50% of 60%)
In your longer program above, you would have
- 10% for stone skin (90% left for the else part)
- 45% (50% of 90%) for magic missile (45% left for the second else part)
- 4.5% (10% of 45%) for shield (40.5% left for the third else part)
- 4.05% (10% of 40.5%) for armor (36.45% left for the fourth else part)
- 3.645% (10% of 36.45%) for bulls strength (32.803% left for the fifth else part)
- 3.2803% (10% of 32.803%) for fire shield (29.5247% left for last else part)
- 29.5247% for shocking grasp
All that sums up to "the numbers in rand() tests can be tricky".
For example, if you want a rand_prog with 4 options that all have the same chance of happening, you can't use:
if rand(25) option 1 else if rand(25) option 2 else if rand(25) option 3 else option 4 endif endif endif
Because that would make
- 25% for option 1 (75% left)
- 18.75% (25% of 75%) for option 2 (56.25% left)
- 14.0625% (25% of 56.25%) for option 3 (42.1875% left)
- 42.1875% for option 4
So, option 4 is very likely to be be chosen in this case. What you want is 25% for each of those options.
- So, rand(25) for option 1, and there's 75% left
- You want 25%, that is, 1/3rd of what is left, so you'll use rand(33) for option 2, then there's 50% left
- You want 25%, that is, 1/2 of what is left, so you'll use rand(50) for option 3
if rand(25) option 1 else if rand(33) option 2 else if rand(50) option 3 else option 4 endif endif endif
That is not too surprising actually... option 1 is a one-in-four chance of happening, so 25%. If option 1 is NOT chosen, you only have 3 options left... so option 2 is a one-in-three chance of happening, rand(33). Then, if neither option1 nor option2 is chosen, you have only 2 options left, so option 3 is rand(50).
For 2 options with the same chance to happen:
if rand(50) option1 else option2 endif
For 3 options with the same chance to happen:
if rand(33) option1 else if rand(50) option2 else option3 endif endif
For 4 options with the same chance to happen:
if rand(25) option1 else if rand(33) option 2 else if rand(50) option3 else option4 endif endif endif
The numbers for the rand checks are (from the most inner one to the most outer one): 50, 33, 25, 20, 17, 14, 12, ...