This header defines the tads-gen intrinsic function set. This function set provides some miscellaneous functions, including data conversions, object iteration, regular expressions, and state persistence operations.
This function is essentially the same as concatenating a series of values with the "+" operator, but it's more efficient with three or more values, since the "+" operator has to be applied successively in pairs; that creates and copies an extra intermediate result string at each step. This function only creates one result string and only has to copy each input string once.
Note that firstObj-nextObj loops can retrieve objects that aren't otherwise reachable, because unreachable objects are only removed from memory when the garbage collector runs, which happens intermittently. If you want to ensure that any currently unreachable objects are removed from memory just before a firstObj-nextObj loop, you can do so by calling t3RunGC().
If timeType is GetTimeDateAndTime (or is omitted), this returns the calendar date and wall-clock time, as a list: [year, month, dayOfMonth, dayOfWeek, dayOfYear, hour, minute, second, timer]. Year is the year AD (for example, 2006); month is the current month, from 1 (January) to 12 (December); dayOfMonth is the calendar day of the month, from 1 to 31; dayOfWeek is the day of the week, from 1 (Sunday) to 7 (Saturday); dayOfYear is the current day of the year, from 1 (January 1) to 366 (December 31 in a leap year); hour is the hour on a 24-hour clock, ranging from 0 (midnight) to 23 (11pm); minute is the minute of the hour, from 0 to 59; second is the second of the minute, from 0 to 59; and timer is the number of seconds elapsed since the "Epoch," defined as midnight, January 1, 1970, midnight UTC. (This is the Epoch that Unix-like systems use, so it appears frequently in computer timekeeping systems.) See the Date class for more comprehensive date/time handling.
If timeType is GetTimeTicks, this return the number of milliseconds since an arbitrary starting time. The first call to get this information sets the starting time, so it will return zero; subsequent calls will return the amount of time elapsed from that starting time. Note that because a signed 32-bit integer can only hold values up to about 2 billion, the maximum elapsed time that this value can represent is about 24.8 days; so, if your program runs continuously for more than this, the timer value will roll around to zero at each 24.8 day multiple. So, it's possible for this function to return a smaller value than on a previous invocation, if the two invocations straddle a 24.8-day boundary.
If no arguments are supplied, the result is a random integer distributed evenly over the full range of the 32-bit integer type. The result can be positive or negative.
If exactly one argument is supplied, the result depends on the type of the argument:
- Integer: the function returns an integer from 0 to one less than the argument value. For example, rand(10) returns a number from 0 to 9 inclusive.
- List: the function randomly selects one of the values from the list and returns it.
- String: the function generates a random string by replacing each character of the argument string with a randomly chosen character, selected from a specific range specified by the argument character. For example, each 'a' in the input string is replaced by a random lower-case letter from a to z, each 'A' is replaced by a capital letter, and each 'd' is replaced by a random digit 0 to 9. See the System Manual for the full list of the character codes.
If more than one argument is supplied, the function randomly selects one of the arguments and returns it. Note that this is an ordinary function call, so all of the arguments are evaluated, triggering any side effects of those evaluations.
In all cases, the random numbers are uniformly distributed, meaning that each possible return value has equal probability.
The interpreter automatically makes a call to randomize() (the no-arguments version) when it starts up, unless the user specifies the "-norand" option when launching the interpreter. For most programs, this means that you'll never have to make your own call to randomize() - you can just call rand() when you need random numbers.
This function performs several tasks, depending on how you invoke it:
randomize() - selects the default RNG algorithm (ISAAC), and seeds
the RNG with truly random data from the operating system.
randomize(nil) - retrieves the current state of the RNG. Returns
a list: [id, state], where 'id' is the ID of the currently
selected RNG algorithm, and 'state' is a value containing
its internal state, which can be used in a later call to
randomize([id, state]) to restore the RNG state. The 'state'
value is opaque, meaning that it's not meant to be used
directly; the only thing you should do with it it save it
for later if you should want to restore this state later.
randomize([id, state]) - if you pass in a list that obtained from
an earlier call to randomize(nil), the RNG will be returned
to its state at the time of the randomize(nil) call. This
selects the same RNG algorithm that was in effect then and
restores the internal state of the generator. After calling
this, a series of calls to rand() will return the same
sequence of numbers that were returned after the call to
randomize(id) - selects the RNG algorithm identified by 'id'
(an RNG_Xxx value). This doesn't change the state of
the generator; it simply selects the algorithm.
randomize(id, nil) - selects the RNG algorithm identified by
'id' (an RNG_Xxx value), and seeds the generator with
truly random data obtained from the operating system.
randomize(id, val) - selects the RNG algorithm identified by
'id' (an RNG_Xxx value), and seeds the generator with
the initial value 'val'. This can be either an integer
value or a string; the preferred format varies by
algorithm, but they'll all accept either format. After
you set a given seed value, rand() will return a sequence
of numbers that's repeatable every time you set the same
seed value. This can be useful for things like testing,
where you want a sequence of numbers that's statistically
random, but which can be reproduced on demand.
Most programs that require random numbers want a truly unpredictable series of numbers - that is, numbers that have a statistically random distribution, with no discernible patterns, and which will be different every time the program is run. There are two separate parts to this proposition, and they're handled by separate functions. rand() fulfills the first part: it uses a mathematical formula to generate a series of numbers that are statistically distributed in a random fashion (for example, so that "1" occurs as often as "10" or any other number, any given sequence of 2, 3, or more numbers is equally likely, and so on: mathematicians have many formal tests that RNGs must satisfy to be considered random.) randomize() fulfills the second part, which is making sure that the sequence of numbers is different every time you run the program. The reason this second part is important is that rand() is by its nature deterministic: it's defined entirely in terms of a formula, so given the same initial conditions, it'll always crank out the same sequence of numbers. The trick is to randomize its initial conditions - and what makes it tricky is that we can't just turn to rand(), since it's the thing we're trying to randomize!
This is where the "seed" values come in. randomize() and randomize(id, nil) ask the operating system for truly random data to use for the initial conditions. The degree of entropy in this OS seed data varies by system; some systems have better entropy sources than others. But whatever the source, the seed data should be different each time you run the program. randomize() feeds this seed data into the RNG to set its initial conditions, so each time you run, rand() will be starting from a different initial state. This makes for a different series of numbers from rand() on each run.
Note that it's not necessary (or desirable) to call randomize() every time you want a random number. Once you seed the RNG, rand() is all you need to call. It can be slow to gather the true random data that randomize() uses, since this sometimes requires interacting with hardware devices or scanning large amounts of system data. rand() is quite fast, since it just calculates a number using a formula. Depending on the system, rand() might also be more reliable at producing high volumes of statistically random data than the OS sources. The operating system sources of true entropy don't always change quickly, so it's better to use them infrequently, such as just once at the start of program execution, than to use them as a routine source of random numbers.
The fixed seed values, with randomize(id, val), do something a little different. Rather than making the RNG produce different sequences on each run, a fixed seed makes rand() generate the same series of numbers every time. The numbers will still be statistically random, but each time you run the program, you'll get the same seaquence. (The sequence is a function of the seed value. You'll get a different sequence for each different seed value.)
Why would you want a fixed series of rand() results? One big reason is testing. One popular way to test software is regression testing, where you run the program and compare its output to a reference version that you know is correct. If there are no differences, you know that changes you've made to the program since haven't broken anything in the test script. Randomization interferes with this kind of testing, because it makes the output different on each run - it's useless to do a simple mechanical comparison of the new and old output because they'll always differ. Fixed seeds to the rescue. Using a fixed seed, you can still exercise the program's random behavior, but the sequence of random behavior will repeat on every run, so you run those regression comparisons after all. The really great thing is that you don't have to make big changes to the program if you want to switch between test mode and real randomness - all you have to do is change the one call to randomize().
The search pattern can also be given as a *list* of search patterns. In this case, we'll search for each of the patterns and replace each one with the corresponding replacement text. If the replacement is itself given a list in this case, each element of the pattern list is replaced by the corresponding element of the replacement list. If there are more patterns than replacements, the extra patterns are replaced by empty strings; any extra replacements are simply ignored. If the replacement is a single value rather than a list, each pattern is replaced by that single replacement value.
'flags' is a combination of the ReplaceXxx bit flags, using '|'. If the flags include ReplaceAll, all occurrences of the pattern are replaced; otherwise only the first occurrence is replaced.
ReplaceOnce and ReplaceAll are mutually exclusive; they mean, respectively, that only the first occurrence of the match should be replaced, or that every occurrence should be replaced. ReplaceOnce and ReplaceAll are ignored if a 'limit' value is specified (this is true even if 'limit' is nil, which means that all occurrences are replaced).
If ReplaceIgnoreCase is included, the capitalization of the match pattern is ignored, so letters in the pattern match both their upper- and lower-case equivalents. Otherwise the case will be matched exactly. If ReplaceFollowCase AND ReplaceIgnoreCase are included, lower-case letters in the replacement text are capitalized as needed to follow the capitalization pattern of the actual text matched: if all the letters in the match are lower-case, the replacement is lower case; if all are upper-case, the replacement is changed to all upper-case; if there's a mix of cases in the match, the first letter of the replacement is capitalized and the rest are left in lower-case.
The ReplaceSerial flag controls how the search proceeds when multiple patterns are specified. By default, we search for each one of the patterns, and replace the leftmost match. If ReplaceOnce is specified, we're done; otherwise we continue by searching again for all of the patterns, this time in the remainder of the string (after that first replacement), and again we replace the leftmost match. This proceeds until we can't find any more matches for any of the patterns. If ReplaceSerial is included in the flags, we start by searching only for the first pattern, replacing one or all occurrences depending on the ReplaceOnce or ReplaceAll flag. Next, if ReplaceAll is specified OR we didn't find any matches for the first pattern, we start over with the result and search for the second pattern, replacing one or all occurrences of it. We repeat this for each pattern.
If the flags are omitted entirely, the default is ReplaceAll (which means replace all occurrences, exact case matches only, parallel searching).
'index', if provided, is the starting character index of the search; instances of the pattern before this index will be ignored. Returns the result string with all of the desired replacements. When an instance of the pattern is found and then replaced, the replacement string is not rescanned for further occurrences of the text, so there's no danger of infinite recursion; instead, scanning proceeds from the next character after the replacement text.
'limit', if specified, is an integer indicating the maximum number of matches to replace, or nil to replace all matches. If the limit is reached before all matches have been replaced, no further replacements are performed. If this parameter is specified, it overrides any ReplaceOnce or ReplaceAll flag.
The replacement text can use "%n" sequences to substitute group matches from the input into the output. %1 is replaced by the match to the first group, %2 the second, and so on. %* is replaced by the entire matched input. (Because of the special meaning of "%", you must use "%%" to include a percent sign in the replacement text.)
If a match is found, the return value is a list: [index, length, string], where index is the starting character index of the match, length is the length in characters of the match, and string is the text of the match.
This does the same work as rexSearch(), but searches the string backwards, from the end to the start. The match must end before the starting index, which allows for repeated searches: simply pass the match index from the previous search as the 'index' value for the next search to find the next earlier match that doesn't overlap the previous match.
The meanings of the <FirstBegin> and <FirstEnd> flags for a reverse search are essentially the mirror image of their meanings in a regular forward search. This is easiest to understand by thinking about the flags in the abstract. <FirstBegin> means that the winning match is the one whose "near" endpoint is closest to the starting index; <FirstEnd> means that the winner is the match whose "far" endpoint is closest to the starting index. The near endpoint in a forward search is the start of the match, whereas it's the end of the match in a reverse search. Similarly, the far endpoint is the end of the match in a forward search and the start of the match in a reverse search. So in a reverse search, <FirstBegin> means that the winner is the match whose ending index is highest, and <FirstEnd> means that the winner is the one whose starting index is highest.
'metatab' is an optional LookupTable containing string key/value pairs to be saved with the file as descriptive metadata. The interpreter and other tools can display this information to the user when browsing a collection of saved game files, to help the user remember the details of each saved position. It's up to the game to determine what to include; the list can include any information relevant to the game that would be helpful when reviewing saved position files, such as the room name, score, turn count, chapter name, etc.
'format' is the format string. Most characters of the format string are simply copied verbatim to the result. However, each '%' in the format string begins a substitution parameter; the '%' is followed by one or more optional qualifiers, then by a type code letter. The corresponding value from the argument list is formatted into a string according to the type code, and then replaces the entire '%' sequence in the result string. By default, the first '%' parameter corresponds to the first additional argument after 'format', the second '%' corresponds to the second additional argument, and so on. You can override the default argument position of a '%' using the '$' qualifier - see below.
The arguments following 'format' are the values to be substituted for the '%' parameters in the format string.
The return value is a string containing the formatted result.
See the System Manual for the list of '%' codes.
If 'val' is a string, the function parses the string's contents as an integer in the numeric base given by 'radix, which can be any integer from 2 to 36. If 'radix' is omitted or nil, the default is base 10 (decimal). The value is returned as an integer. If the number represented by the string is too large for a 32-bit integer, a numeric overflow error occurs.
If 'val' is true, or the string 'true', the return value is 1. If 'val' is nil, or the string 'nil', the return value is 0. Leading and trailing spaces are ignored for these strings.
If 'val' is a BigNumber value, the value is rounded to the whole number, and returned as an integer value. A numeric overflow error occurs if the number is out of range for a 32-bit integer. (If you want to round a BigNumber to the nearest integer and get the result as another BigNumber value, use the getWhole() method of the BigNumber.)
See also toNumber(), which can also parse floating point values and whole numbers too large for the ordinary integer type.
If 'val' is an integer or BigNumber value, the return value is simply 'val'.
If 'val' is a string, the function parses the string's contents as a number in the given 'radix', which can be any integer from 2 to 36. If 'radix' is omitted, the default is 10 for decimal. If the radix is decimal, and the number contains a decimal point (a period, '.') or an exponent (which consists of the letter 'e' or 'E', an optional '+' or '-' sign, and one or more digits), the value is parsed as a floating point number, and a BigNumber value is returned. For any other radix, decimal points and exponents are considered non-number characters. For an integral value, the result will be an integer if the number is within the range that fits in an integer, otherwise the result is a BigNumber. The routine will simply stop parsing at the first non-number character it encounters, so no error will occur if the string contains text following the number. If the text doesn't contain any number characters at all, the result is zero.
If val is true or the string 'true', return 1; if nil or the string 'nil', returns 0. Leading and trailing spaces are ignored in the string versions of these values.
Note that when working with BigNumber values, you might prefer to use BigNumber.formatString(), as that gives you more control over the formatting style.
'radix' is only meaningful with numeric values (integers and BigNumbers). For BigNumbers, only whole integer values can be displayed in a non-decimal radix; if the number has a fractional part, the radix will be ignored and the number will be shown in decimal.
'isSigned' indicates whether or not the value should be treated as "signed", meaning that negative values are represented with a "-" sign followed by the absolute value. If 'isSigned' is nil, a negative value won't be converted to its absolute value before being displayed, but will instead be re-interpreted within its type system as an unsigned value. For regular integers, this means that the result depends on the native hardware storage format for negative integers. Most modern hardware uses two's complement notation, which represents -1 as 0xFFFFFFFF, -2 as 0xFFFFFFFE, etc. Most types other than integer don't have distinct signed and unsigned interpretations, so 'isSigned' isn't meaningful with most other types. With BigNumber in particular, the only effect is to omit the "-" sign for negative values.
(ObjInstances | ObjClasses)
RNG_ISAAC - ISAAC is the default generator. It's designed for both cryptographic application and more mainstream uses. Crypto applications generally have more stringent requirements for an RNG than ordinary applications. ISAAC does well with the usual statistical tests for RNGs and is reasonably fast.
ISAAC's preferred format for a fixed seed is a string value.
The LCG's preferred format for a fixed seed is an integer value.
MT's preferred format for a fixed seed is a string value.