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

Multiple tables on the same command

Jerry Stratton, March 14, 2011

So now, what about doing more than one table at a time? Getting three dragons and one snake from the same command line? As soon as you start thinking about using a type of “thing” more than once with different properties, you’re talking about a class of item. Classes are used to take some pieces of code and variables, and put them all together to define that kind of thing. Once we do that, we can use them a lot like we have been using lists and randoms already.

So the first step is to separate the table code into a class:

[toggle code]

  • #!/usr/bin/python
  • import random
  • import optparse
  • parser = optparse.OptionParser()
  • (options, args) = parser.parse_args()
  • #a table is a file of items that we choose randomly from
  • class Table(object):
    • #load the table and save it
    • def __init__(self, name):
      • filename = name + '.txt'
      • items = open(filename).read()
      • self.items = items.splitlines()
    • #print some random items from the table
    • def choose(self, count):
      • for counter in range(count):
        • print counter+1, random.choice(self.items)
  • 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(table)
  • table.choose(count)

If you run this from the command line now, it will do exactly what it did before. You should test it to make sure.

What’s new? The word “def” stands for “define function”. We’ve created a class and defined some functionality on it. The “__init__()” function is a special function that runs every time you create a new object from a class of something; in this case, every time we create a new table it will run—initialize itself using—that code. So we use that to load the file as a list of items and save it on the object (“self”) for later use.

Now that we’ve separated the table code into a class, however, we can make a more readable multiple-table command line.

[toggle code]

  • #!/usr/bin/python
  • import random
  • import optparse
  • parser = optparse.OptionParser()
  • (options, args) = parser.parse_args()
  • #a table is a file of items that we choose randomly from
  • class Table(object):
    • #load the table and save it
    • def __init__(self, name):
      • filename = name + '.txt'
      • items = open(filename).read()
      • self.items = items.splitlines()
    • #print some random items from the table
    • def choose(self, count):
      • for counter in range(count):
        • print counter+1, random.choice(self.items)
  • while args:
    • 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(table)
    • table.choose(count)
    • print

Note that the only thing different about the lines between “while args” and “print” at the end are that they are indented one tab. Remember that in Python, you indent blocks to show what belongs to what. We did it with the “for”, we did it with the “if” and the “else”, we did it with the “class” and the “def” inside the class, and we’re doing it now with the “while”.

A “while” is a lot like a “for” except that instead of needing a specific list of things to loop over, “while” continues looping as long as some condition is met. In this case, that condition is that there are still arguments to look at.

Because we now are displaying results from more than one table at a time, we also print an empty line after each table, to make each set of results easier to read.

Now you can do more than one thing on the line at a time:

  • $ ./random dragons suns snakes
  • 1 fire dragon
  • 1 Yellow Sun
  • 1 coral

Or you can get more than one out of some tables but not others:

  • $ ./random 3 dragons suns 2 snakes
  • 1 albino dragon
  • 2 Rainbow Dragon
  • 3 laughing dragon
  • 1 Paisley Sun
  • 1 viper
  • 2 giant snake

One dragon or three?

Take a look at the last command example again. Do you want one random sun, or three? That is, when you leave out the number of random items for a second or third table, do you want it to default to one random item, or do you want it to default to whatever the previous number was? Either is easy, but it’s the kind of thing you’ll need to think about, and watch how you use the script later. Part of the point of writing your own scripts is to make things easier for you.

If you want to default to the previous number, move “count = 1” up above the while:

[toggle code]

  • count = 1
  • while args:
    • 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
    • table = Table(table)
    • table.choose(count)
    • print

Different tables for different locales

If you use a script like this extensively you are likely to create different sets of tables for different parts of your game world. You might have one “dragons” table for one part of the world, and another for another part; one snakes table for each part, and so on. The easiest way to manage these different locales is to make a folder for them. The script currently handles that fine.

Go ahead and make two folders in your work area, where you’ve been putting these tables. Call one “West“ and one “East”1. Into each folder, put a “gems.txt” file.

East/gems.txt
  • jasper
  • sapphire
  • chalcedony
  • emerald
  • sardonyx
  • sardius
West/gems.txt
  • chrysolyte
  • beryl
  • topaz
  • chrysoprasus
  • jacinth
  • amethyst

You can run the script to get random gems from the East or West:

  • $ ./random East/gems
  • 1 emerald
  • $ ./random 2 West/gems
  • 1 chrysolyte
  • 2 topaz

You can also do “./random 2 West/gems 3 dragons” and it will give you two gems from the West and 3 dragons from the general area. But if you get into using a script like this for all of your random tables throughout many areas, you might not remember if the West has its own dragon table or if it uses the generic dragon table.

We can add an option for locale, and then have the script use the table from the locale it if exists, or from the main area if it doesn’t.

[toggle code]

  • #!/usr/bin/python
  • import random
  • import optparse
  • import os
  • parser = optparse.OptionParser()
  • parser.add_option('-l', '--locale')
  • (options, args) = parser.parse_args()
  • #a table is a file of items that we choose randomly from
  • class Table(object):
    • #load the table and save it
    • def __init__(self, name):
      • filename = name + '.txt'
      • filepath = filename
      • if options.locale:
        • localepath = os.path.join(options.locale, filename)
        • if os.path.exists(localepath):
          • filepath = localepath
      • items = open(filepath).read()
      • self.items = items.splitlines()
    • #print some random items from the table
    • def choose(self, count):
      • for counter in range(count):
        • print counter+1, random.choice(self.items)
  • count = 1
  • while args:
    • 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
    • table = Table(table)
    • table.choose(count)
    • print

Now, you can specify which folder to look in with --locale, and the script will look in that folder first before defaulting to your overall folder.

  • $ ./random 2 gems 3 dragons --locale West
  • 1 chrysoprasus
  • 2 amethyst
  • 1 Night Dragon
  • 2 water dragon
  • 3 amethyst dragon

There’s a gems.txt in the West folder, but no dragons.txt. So, it pulls dragons from the main file.

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. And never the streams shall meet.

  1. <- Easier random tables
  2. Percent tables ->