A few weeks ago I saw a library floating around that claimed to convert numbers to their spelled-out form. Having done this before, I was curious how the author went about implementing it themselves. As it turned out, the result wasn't all that great. But since I already happened to have Perl code lying around for just such an occasion, I thought, why not convert it to DM?
JRUtils - Numbers was born out of a plugin I wrote for the Movable Type blog system several years ago. The original plugin was designed to do several things: Change a word to its plural form depending on a number it was given, spell out numbers, and add commas as desired. In a blog environment this was a nice thing to have. Movable Type, at least at the time, was based all around setting up templates and it used pseudo-HTML tags to do its work. My plugin created some new tags to use, so if someone wanted their posts to show "three comments" they could do so, but "3 lunches" would have been just as easy. This gave a lot of stylistic freedom to blog authors.
This library can be used in a variety of different games, but will fit in particularly well in board games, MUDs, strategy games, etc. And as a bonus, it's one of the libraries on the no-penalty list for the Cartridge Classic II.
The DM language already includes a very handy macro for pluralizing words, the \s macro. But as I discovered in Incursion, this isn't always ideal. Not every word has a plural form ending in -s. Some have unusual plural forms, like "deer", but most words follow a few basic rules.
- If a word ends in s, ch, sh, or x, the plural should have -es added.
- If a word ends in z, the plural should have -es added, but the z should be doubled if it isn't already.
- If a word ends in y, and the y is not preceded by a vowel, the -y should be replaced with -ies.
- All other words, with only specific exceptions, should have -s added.
A common rule, but one that does not apply to every such word, is that words ending in -o also get an -es instead of just an -s. But even though this is more than norm than the exception, it isn't applied here. A few other common but not universal rules, like -lf to -lves, and -is to -es, are also left out. You're on your own with those.
Now there's a proc that follows those rules, and it leaves the capitalization of the original word intact (as best it can).
If you pass in a word and a number, you'll get the plural version of the word. You don't have to pass in a number; it's just there so that if the number is 1, the word doesn't get changed.
If the word you want to pluralize doesn't have a "normal" plural form, you can add it to the jru_plurals list at runtime like so:
jru_plurals["cactus"] = "cacti"
jru_plurals["half"] = "halves"
jru_plurals["person"] = "people"
Always add the words to the list in lowercase form. Any word in this associative list will be pluralized to the correct form you supply, but with its original capitalization intact.
There's one other bonus here, which is that the number n may be a text string. It doesn't have to be a "true" number.
For longer stretches of text, there is another proc available:
This proc will look for any instance of "[number] [word]" in a block of text and try to pluralize the word if necessary.
It's common to see commas inserted into numbers in formal writing, like 1,000 for a thousand instead of the blunt 1000 that is spit out if you just stick the number directly into the output. Even worse, if you're using large numbers they may just look like "1.0e6" instead of 1000000 or 1,000,000.
While this is easy to soft-code by yourself, why reinvent the wheel?
This proc returns n with commas inserted. The number n may be a number, or it may be a number in text form like "1234" instead of 1234. Decimal points and negatives are left intact.
This applies the commafication to a whole block of text, converting any numbers within to comma form.
Converting numbers to their spelled-out, textual representation is a tricky business because there are so many rules to follow. Fortunately, this routine takes all the guesswork out.
This returns the number n (which again, may be represented as a text string instead of a true number) in a spelled-out format. If the th argument is true, the result will be in an ordinal form (e.g., "first" instead of "one"). The routine only works with whole, positive numbers, so it will discard everything after a decimal point and turn negatives to positives.
Here are some examples:
|n||result (th=0)||result (th=1)|
|140||one hundred forty||one hundred fortieth|
|1234||one thousand, two hundred thirty-four||one thousand, two hundred thirty-fourth|
|9001||nine thousand one||nine thousand first|
|1000002||one million two||one million second|
The routine is smart enough to throw away unnecessary commas, as in "nine thousand one" instead of "nine thousand, one". Also in an ordinal form, it will omit a leading "one" if there is only one other word, like "hundredth".
This routine will convert all numbers in a block of text to their spelled-out equivalents. If the number is followed by st, nd, rd, or th, it will be converted to ordinal form. So "My 1st victory earned 9 points!" would become "My first victory earned nine points!" instead.
A few other routines are included for various helpful purposes.
jru_Leading(n, places=1, fill="0")
Takes a number n, in numerical or string format, and pads it out to the number of places you want with a leading digit or character. This is good for timers, for instance, so you can call "[hours]:[jru_Leading(minutes,2)]:[jru_Leading(seconds,2)]" to output to your timer display.
This splits a line of text into a list. It's used internally to reduce the number of strings that get compiled with your project. Unlike params2list() it will work well with duplicate values, since the result is not an associative list.
jru_SanitizeNum(n, no_negative=0, no_decimal=0)
The number n, in numerical or string format, is converted to a string. If negatives or decimals are not allowed they can be removed. Unlike just using "[n]", this will correctly output a number like 1e6 as "1000000". It's like a version of text2num() that always gets all the digits it can, but it also can take bogus input and clean it up.
Rule Your Numbers!
Hopefully this library will provide just the routines you need to tame numbers in your games, providing classier output and jazzing up your statpanels, grids, and text. What can you do with numbers once they're tamed down?