Biblyon the Great

This zine is dedicated to articles about the fantasy role-playing game Gods & Monsters, and other random musings.

Gods & Monsters Fantasy Role-Playing

Beyond here lie dragons

The Dream of Poor Bazin

Jerry Stratton

What if the Three Musketeers were journalists in Washington, DC? What if journalists were swashbuckling, swaggering, hard-drinking warriors of truth? Find out in Jerry Stratton’s The Dream of Poor Bazin.

Easier random tables

Jerry Stratton, March 3, 2011

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:

  1. If the firstArgument variable is composed entirely of digits, then convert it to an integer and pop the table out of the argument list.
  2. 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.

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

  2. 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”.

  3. Which means that if your Python doesn’t live at /usr/bin/python, you’ll need to change that line.

  1. Multiple random tables ->