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, ...