Setup for Automatic Random Number Generation (Czar)

This describes what the Production Czar should do to set things up so that the instructions about manual use of generated random numbers works. You should have read that material already before reading this.

Contents:

  1. spec files and generation
  2. list of syntax
  3. notes for hackers


Spec files

Let's say you want to implement the sample encoding mechanics given in numbers-use for item numbers:

In LaTeX/Numbers/ make a file itemnum-spec as

digits: 4

flag ingame
$a =~ /0/

flag magic
$b =~ /1|3|5|7|9/

value recharge
&magic and $b 

flag psi
($b + $c) % 3 == 0

value age
($d > $a) and ($d - $a)
and run
gmX numbers itemnum
which will ask if you want to generate new random itemnum's; answer yes. (If there aren't any generated itemnum files already, yes will be the default; if there are, no is the default, and if you try it'll make sure it doesn't overwrite anything valuable.)

What does this do? Since you said "digits: 4," the script goes through all numbers for 0000 to 9999, assigning the first digit to the variable $a, the second to $b, etc. Then it sees which flags the number satisfies. For the 'ingame' flag, we said "$a =~ /0/", which means "the first digit ($a) is a '0'." The 'magic' flag uses the 'or' symbol `|' to see if the second digit ($b) is 1 or 3 or 5 or 7 or 9, i.e. is an odd number. The 'psi' flag adds the second and third digits ($b and $c), takes them modulo three (% 3), and sees if the result is 0.

Further, it calculates the value of each 'value' entry in itemnum-spec. 'recharge' is "&magic and $b" --- meaning "look at what the 'magic' flag is for this number (whether or not we've listed it yet); if it's True, then be the second digit, and if not just be false (zero)." So if the item is magic, its recharge is just the second digit, and if it's not magic, the recharge is always zero. 'value' entries must always come out to non-negative integer values, not words or whatever.

The 'age' value involves both numerical values and Boolean (true/false) values. When these both come up, the rules are, as usual:

(foo) and (bar) is the same as (foo) if (foo) is false (since then it doesn't matter to 'and' what (bar) is, so it ignores it), and the same as (bar) if (foo) is true (since then 'and' does care). Let's put this all together for
($d > $a) and ($d - $a)
$a is the first digit and $d is the fourth. If ($d > $a) is false, then the 'and' will have to be false, so it doesn't bother to look at the other side; it just stays at false, which is numerically zero. If ($d > $a) is true, then it does look at the other side, which is ($d - $a); whatever the value of that is will be the final value.

Entries must be separated by blank lines.

The generated numbers are saved in files whose names tell you what flags they satisfy. Those that don't satisfy any of your special flags go in itemnum.plain. Psi ones go in itemnum.psi, magic in itemnum.magic, etc; psi magic items go in itemnum.magic.psi, ingame magic psionic items go in itemnum.ingame.magic.psi, etc.

Within each file, the items are sorted by the 'recharge' value (since that came first in the file) and those of the same recharge are sorted by age. In each chunk of identical flags and values the numbers are randomized. Now you can see why we made 'recharge' "&magic and $b" instead of just $b; $b itself would cause the non-magic items to have different recharge values, so it would sort them, but we want them randomized. Since "&magic and $b" is zero for all non-magic items, we avoid a spurious sort.

To limit the space the output files take up, you can put a "memory: 300" line before your "digits: " line. The '300' (for instance) is approximately how many kilobytes the files will be limited to total. If you have more than four digits, you must specify this. (Not that four digits isn't a lot of room to encode information.) This does not speed execution; see the KEEP flag below.

Most things you might want to encode you can probably do just from the examples above. What are the general rules? As some of you have figured out, the syntax for flag and value specifications is Perl syntax. Don't be scared by this; you're not writing programs, just simple expressions. Below is a list of some useful bits of syntax; if you really need to do something beyond them, just ask anyone who knows Perl for the appropriate expression.

As indicated above with 'recharge,' you can use one property in the expression for another, (no matter which order they're defined in). That is, in addition to the variables $a, $b, $c etc, if you define the flag 'magic' somewhere, you can use '&magic' to access its value; you should only use this as a Boolean, not as a number, since it could be True by being 1 or True by being -1 or... etc. 'value' properties hopefully have numeric values.

Note that you access the base variables with a $ ($a, $b, etc, or $_ for the current number as a whole) and the spec-defined properties with an & (&magic, &recharge, &psi, &age, etc).

Make sure you don't create circular definitions unless you know what you're doing.

Sometimes there's a large section of numbers you don't care about --- Reign of Terror used one digit of badge numbers as social status, and it ranged only from 1 to 5, so generating other numbers would waste time and space. To avoid them, define the special flag KEEP:

flag KEEP
$d >= 1 and $d <= 5
says to only keep numbers whose fourth digit is 1 to 5, and to not even look at the flags and values for other numbers, nor save them anywhere. Since this eliminates half the numbers, it cuts the runtime by half and the output takes up half as much space. KEEP is used only for this.


List of syntax for those who don't know Perl

Here's some of those useful bits of syntax. No need to read them until you need to do something you can't see just from the examples, unless you want to.

$a =~ /3/
True if $a (first digit) is '3'.

$a =~ /3|5|8/
True if $a is '3' or '5' or '8'.

"$a$c" =~ /34|57/
True if "$a$c" (first digit followed by third) is '34' or '57'.

"$a$c" =~ /(1|2|3)(7|8|9)/
True if "$a$c" is a '1', '2', or '3' followed by a '7', '8', or '9'.

$a =~ /1|2|3/ and $c =~ /7|8|9/
Equivalent to previous one, but easier to read.

$a =~ /1|2|3/ or $c =~ /7|8|9/
True if first digit is 1-3 OR third digit is 7-9, or both.

$_ =~ /23/
True if "23" appears anywhere in the number as a whole --- $_ is the same as "$a$b$c$d" (for as many digits as you have, that is). So 23xy, x23y, and xy23 all match for any x, y, z.

/23/
Same as above --- "/pattern/" by itself means "$_ =~ /pattern/".

!(some expression)
True (1) if the enclosed expression is false (zero).
False (0) if the enclosed expression is true (nonzero).

$a == 7
$a == $b
Test for equality. Use TWO =, not one, or you'll be doing assignment instead!

$a != 7
$a != $b
Test for inequality.

$a + $b
$a - $b
$a * $b
$a / $b
Sum, difference, product, or quotient of first two digits.

Don't take the quotient if $b might be zero! You could avoid this with something like

		value age
		($b != 0) and ($a / $b) 
	
Since an 'and' is only true if both sides of it are true, it doesn't bother to look at the second one if the first is false. So if $b is zero, the first part is false (0), so the whole thing is just 0 without looking at the second part and doing that dangerous division. If $b isn't zero, the first part is true, so it looks at the second part ($a / $b); the value of the second part is then the final value.

Similarly, since an 'or' is only false if both sides are false, (foo or bar) will be equal to whatever foo is if it's nonzero (true) since there's no reason to look at bar then; if foo is false/zero then (foo or bar) will be bar.

$a % 4
$a modulo four.

$a ** 2
$a squared.

($a > $b) ? $a : $b
The greater of $a, $b. (First ? Second : Third) looks at First; if it's true it evaluates Second, otherwise it evaluates Third instead.


More details for those who do know Perl

When you say

	(flag or value) foo
	blah blah blah
	blah blah blah blah blah
all that happens is that
	sub foo { 
	  blah blah blah
	  blah blah blah blah blah
	}
gets defined (in a seperate package, to preserve namespace).

You see why &foo gets you that property's value.

You should also see that having a single line with no semicolon and no 'return,' as in the sample, is a special case, even though it's almost always enough; if you write a bigger subroutine you'll need semicolons except on the last line as usual, and you might want explicit 'return's instead on relying on return-the-last-thing-evaluated.

If you write bigger subroutines, DON'T PUT IN BLANK LINES! A blank lines means you're starting a new flag/value entry!

All of the above holds for both flags and values --- the difference is that flags are used to build up a string which is the name of the file to save in, and values to build up a string that heads a chunk of the file and which is used to sort inside the file.

The flag/value subroutines are evaluated inside a nested loop with $a, $b, etc each running from '0' to '9', and their concatenation ("$a$b$c...") put into $_ for convenience.

DON'T MODIFY THE CONTENTS OF $a, $b, ETC, NOR OF $_ IN YOUR FLAGS/VALUES! Treat those as read-only or you will regret it!