Easier random tables
As it currently stands, our script lets us easily get a random item from a “table” of items. But not as easily as it could let us do it.
- python random --table suns.txt --count 3
The script looks for a “table” option and a “count” option, both of which are optional; leave the table out and it defaults to “suns.txt”. Options are very useful, but they’re probably not appropriate in this case. Once we have a hundred or so tables set up, how often, really, are we going to not have to specify a table? Why require typing --table and --count every time?
It would be a lot easier to be able to type:
- python random 3 snakes
Or even:
- ./random 2 dragons
Let’s take it a step at a time.
Arguments, not options
If you look at the current version of the script, we have:
- (options, args) = parser.parse_args()
We’ve used the options—we used options.table and options.count—but we haven’t used args. Options are very precise. We say “--table” and specify a filename, “--count” and specify a number. Arguments are more freeform. Let’s switch this around to use arguments instead of options.
[toggle code]
- #!/usr/bin/python
- import random
- import optparse
- parser = optparse.OptionParser()
- (options, args) = parser.parse_args()
- count = args.pop(0)
- count = int(count)
- table = args.pop(0)
- table = open(table).read()
- table = table.splitlines()
-
for counter in range(count):
- print counter+1, random.choice(table)
Now we don’t have any options (don’t worry, we’ll have one by the end of this series). The new syntax of the command is “python random number table”:
- $ python random 2 snakes.txt
- 1 coral
- 2 constrictor
- $ python random 3 suns.txt
- 1 Red Sun
- 2 Red Sun
- 3 Blue Sun
- $ python random 1 dragons.txt
- 1 firestorm dragon
Dragons? Create a dragons.txt file with these lines:
- fire dragon
- water dragon
- storm dragon
- forest dragon
- mud dragon
- rotting dragon
- albino dragon
- laughing dragon
- mist dragon
- firestorm dragon
- salt dragon
- amethyst dragon
- Sun Dragon
- Night Dragon
- Cloud Dragon
- Rainbow Dragon
Because dragons are cool. But now, back to the code.
In Python, “args” is a list of the non-option items on the command line. For any list in Python, the way to get the first item off of the list is with listname.pop(0). It removes the first item and “returns” it for you to use however you want, such as, in this case, storing it in a variable called “count”.
Because everything on the command line is a string of characters, even the numbers, we have to convert count to an integer so that Python can do math on it.1
The first item on the list is now what used to be the second item, so .pop(0) grabs the second item off and puts it into our next variable, “table”. At that point, the script works the same as it did before.
Even easier: remove the extension
As long as we’re removing unnecessary typing, why retype “.txt” every time? Add a new line after popping the table from args:
- table = args.pop(0)
- table = table + '.txt'
Now we can type just “dragons” instead of “dragons.txt”:
- $ python random 2 dragons
- 1 firestorm dragon
- 2 Sun Dragon
Just one item?
Most of the time, we’re just asking for one item. So why not assume that, and not have to type “1” before “dragons”?
We can check to see if the first argument is a number, and use it as the count if so; and if not, use it as the table. Replace the popping section with:
[toggle code]
- firstArgument = args.pop(0)
- #if the first argument is a number, it's the number of random items we want
- #otherwise, it is the table and we want one item from it
-
if firstArgument.isdigit():
- count = int(firstArgument)
- table = args.pop(0)
-
else:
- table = firstArgument
- count = 1
- table = table + '.txt'
We have a couple of new things here. The two lines with hashmarks as their first characters are comments. Python ignores them, so we can use comments to make the script more understandable later when we come back to read it. You should comment liberally enough to illuminate your scripts, and not so much as to obscure them. When in doubt, comment more.
The “if” and “else” are like the “for”. We’re saying:
- If the firstArgument variable is composed entirely of digits, then convert it to an integer and pop the table out of the argument list.
- Otherwise, assume only one argument, which is the table, and set count to 1.
- $ python random dragons
- 1 storm dragon
- $ python random 3 dragons
- 1 forest dragon
- 2 rotting dragon
- 3 laughing dragon
The whole shebang
There’s one more thing I want to talk about with scripts before closing this post. At the top of the script, there’s a line I haven’t mentioned yet:
- #!/usr/bin/python
It’s a comment, but it’s a very special comment. Python ignores it, because Python ignores any lines that begin with a hash mark. But your command-line environment does not ignore it under the right circumstances.
If you mark a script as “something that can be run” you can just type the script name itself without the word “python” in front of it. Under Unix-like operating systems such as Linux and Mac OS X, you can mark a script as “something that can be run” by “setting its executable bit”. On the command line where you would normally type “python random”, type:
- chmod u+x random
You can now type “./random” instead of “python random” whenever you use the script. Your operating system2 looks at the first line of the script, sees that it begins with hash-bang (#!) and assumes that the line that follows is the path to the program that knows how to interpret your script. It then runs the script using that interpreter. In this case, using /usr/bin/python.3
- $ ./random dragons
- 1 mud dragon
A minor change, but philosophically very important. It makes our script look like a real command-line program.
In response to Programming for Gamers: Choosing a random item: If you can understand a roleplaying game’s rules, you can understand programming. Programming is a lot easier.
If we did not convert it to an integer, for example, “1” plus “9” would be “19”, because when strings of characters are added together they concatenate. If you look at the --count option, you see we told the option parser that --count was type “int”. Thus, the option parser did the conversion for us. Because we’re now using the more freeform args, we have to do the conversion ourselves.
↑Technically, not your OS but whatever “shell” happens to be your command-line environment. A “shell” is the nearly-invisible program that lets you type commands such as “python random”.
↑Which means that if your Python doesn’t live at /usr/bin/python, you’ll need to change that line.
↑
- Shebang at Wikipedia
- “The shebang was introduced by Dennis Ritchie between Edition 7 and 8 at Bell Laboratories. It was also added to the BSD releases from Berkeley’s Computer Science Research. As AT&T Bell Laboratories Edition 8 Unix, and later editions, were not released to the public, the first widely known appearance of this feature was on BSD.”
More Programming for Gamers
- Are my dice random?
- My d20 appears to have been rolling a lot of ones, a disaster if I were playing D&D but a boon for Gods & Monsters. Is my die really random, or is it skewed towards a particular result? Use the ‘R’ open source statistics tool to find out.
- Programming for Gamers: Choosing a random item
- If you can understand a roleplaying game’s rules, you can understand programming. Programming is a lot easier.
- Programming a Roman thumb
- Before we move on to more complex stuff with the “random” script, how about something even simpler? Choose or die, Bezonian!
- Multiple tables on the same command
- The way the “random” script currently stands, it does one table at a time. Often, however, you have more than one table you know you’re going to need. Why not use one command to rule them all?
- How random is Python’s random?
- Can you trust a script’s random number generator to actually be random?
- 12 more pages with the topic Programming for Gamers, and other related pages
More Python
- Quick-and-dirty old-school island script
- Here’s a Python-based island generator using the tables from the Judges Guild Island Book 1.
- Astounding Scripts on Monterey
- Monterey removes Python 2, which means that you’ll need to replace it if you’re still using any Python 2 scripts; there’s also a minor change with Layer Windows and GraphicConverter.
- Goodreads: What books did I read last week and last month?
- I occasionally want to look in Goodreads for what I read last month or last week, and that currently means sorting by date read and counting down to the beginning and end of the period in question. This Python script will do that search on an exported Goodreads csv file.
- Test classes and objects in python
- One of the advantages of object-oriented programming is that objects can masquerade as each other.
- Timeout class with retry in Python
- In Paramiko’s ssh client, timeouts don’t seem to work; a signal can handle this—and then can also perform a retry.
- 30 more pages with the topic Python, and other related pages
More random tables
- Island Book 1 and old-school tables
- Judges Guild Island Book 1 is a fascinating playground on which to place a sea-going adventure or campaign. It’s also a great example of the usefulness and wildness of old-school encounter tables.
- Random table rolls
- As often as not, when you roll on a random table you are rolling a random number of times. Now that we have a dice library, we can turn the roll count into a die roll.
- Percentage-based random tables
- Our current random item generator assumes that each item shows up as often as any other item. That’s very OD&D-ish. But AD&D uses percentage dice to weight toward some monsters and items more than others.
- Wandering monster chart assistant
- Use the encounter chart assistant to create wandering monster charts using percentages rather than ranges. Copy your tables into this tool to make sure they add up, adjust them, then copy them back out to your word processor. Never worry about missing or overlapping ranges again!
- Multiple tables on the same command
- The way the “random” script currently stands, it does one table at a time. Often, however, you have more than one table you know you’re going to need. Why not use one command to rule them all?
- Three more pages with the topic random tables, and other related pages