Tuesday, 17 October 2017

Treasure Generator for Dungeons & Dragons

One of my hobbies has been Dungeons and Dragons. Although I don't play much these days, it has had a second lease of life as a subject to write computer programs for. Here is one such, when I was wondering how to generate a treasure hoard, given an approximate value and whether or not the treasure was mostly low-value copper coins, or high value gems and magic items.

#!/usr/bin/python3.5
import random
print ("Treasure hoard generating program")
print ("=================================")

print ("Please enter approximate value of treasure hoard: ")
total_value = input("? ")
total_value = int(total_value) # approximate total value. It doesn't work out precisely.

print ("On a scale of 1-10 how skewed is it towards high value items?")
skew = input("1= mostly low value coins, 10 = mostly magic items: ")
skew = int(skew) -1
# for ratio tuples [0] is copper, [1] is silver, [2] is electrum, [3] is gold
# [4] is platinum, [5] is gems, [6] is jewelry, [7] is magic items
skew_ratio1 = (0.75, 0.95, 0.98, 0, 0, 1, 0, 0)
skew_ratio2 = (0.50, 0.85, 0.90, 0.95, 0, 0.98, 1, 0)
skew_ratio3 = (0.30, 0.60, 0.70, 0.80, 0.85, 0.90, 0.95, 1)
skew_ratio4 = (0.25, 0.50, 0.65, 0.80, 0.85, 0.90, 0.95, 1)
skew_ratio5 = (0.15, 0.30, 0.45, 0.60, 0.75, 0.83, 0.91, 1)
skew_ratio6 = (0.10, 0.20, 0.40, 0.60, 0.75, 0.90, 0.95, 1)
skew_ratio7 = (0.07, 0.14, 0.20, 0.50, 0.65, 0.80, 0.90, 1)
skew_ratio8 = (0.05, 0.10, 0.15, 0.35, 0.50, 0.65, 0.80, 1)
skew_ratio9 = (0.03, 0.07, 0.12, 0.30, 0.45, 0.60, 0.85, 1)
skew_ratio10 = (0.01, 0.02, 0.10, 0.20, 0.30, 0.50, 0.75, 1)
skew_ratio_tup = (skew_ratio1, skew_ratio2, skew_ratio3, skew_ratio4, skew_ratio5, skew_ratio6, skew_ratio7, skew_ratio8, skew_ratio9, skew_ratio10)

selected_skew_ratio = skew_ratio_tup[skew] # should select the right set of ratios
spent_value = 0
copper_value = 0
silver_value = 0
electrum_value = 0
gold_value = 0
platinum_value = 0
gem_value = 0
jewelry_value = 0
magic_item_value = 0

chunk_number = 0
while spent_value < total_value:
    chunk_value = total_value * random.random() * 0.1
    chunk_choice = random.random()
    category_num = 0
    chunk_done = False
    for category in selected_skew_ratio:
        if category > chunk_choice and chunk_done == False:
            spent_value = spent_value + chunk_value
            if category_num == 0:
                copper_value = (copper_value + chunk_value)
            elif category_num == 1:
                silver_value = (silver_value + chunk_value)
            elif category_num == 2:
                electrum_value = (electrum_value + chunk_value)
            elif category_num == 3:
                gold_value = (gold_value + chunk_value)
            elif category_num == 4:
                platinum_value = (platinum_value + chunk_value)
            elif category_num == 5:
                gem_value = (gem_value + chunk_value)
            elif category_num == 6:
                jewelry_value = (jewelry_value + chunk_value)
            else:
                magic_item_value = (magic_item_value + chunk_value)
            #print (category_num, chunk_value)
            chunk_done = True
        else:
            category_num = category_num +1
    chunk_number = chunk_number +1
print ("Coins")
print ("=====")
copper_coins = format(int(copper_value * 100), ',d')
print ("Copper value = ", copper_value, "\n\tCopper Coins = ", copper_coins)
silver_coins = format(int(silver_value * 10), ',d')
print ("Silver value = ", silver_value, "\n\tSilver Coins = ", silver_coins)
electrum_coins = format(int(electrum_value * 5), ',d')
print ("Electrum value = ", electrum_value, "\n\tElectrum Coins = ", electrum_coins)
print ("Gold value = ", gold_value, "\n\tGold coins = ", format(int(gold_value), ',d'))
platinum_coins = format(int(platinum_value / 5), ',d')
print ("Platinum value = ", platinum_value, "\n\tPlatinum coins = ", platinum_coins)
print ("Gem value = ", gem_value)
print ("Jewelry value = ", jewelry_value)
print ("Magic Item value =", magic_item_value)
print ("Total assigned value = ", spent_value)
print ('\n')

gemstash = [] # what the treasure hoard contains - starts empty
total_gemvalue = 0
if gem_value < 10 and gem_value > 0:
    gem_string = ["1 small pretty stone worth ", int(gem_value)]
    total_gemvalue = total_gemvalue + int(gem_value)
else:
    gem_string = ""
    handle = open('gemfile.txt', 'r')
    gemlist = [] # table for storing data about gems
    for line in handle:
        linelist = line.split(', ')
        gemlist.append(linelist)
    remaining_gemvalue = gem_value
    size_tuple = ((0.2, "very small "), (0.5, "small "), (0.5, "small "), (1, "medium "), (1, "medium "), (1, "medium "), (2, "large "), (5, "huge "))
    quality_tuple = ((0.2, "poor "), (0.5, "flawed "), (0.5, "flawed "), (1, "normal "), (1, "normal "), (1, "normal "), (2, "flawless "), (5, "perfect "))
    while remaining_gemvalue >= 10:
        potential_gem = [random.choice(quality_tuple), random.choice(size_tuple), random.choice(gemlist)]
        potential_value = float(potential_gem[0][0]) * float(potential_gem[1][0]) * float(potential_gem[2][1])
        if potential_value <= remaining_gemvalue:
            gem_descrip = potential_gem[0][1] + potential_gem[1][1] + potential_gem[2][0]
            gem_final = [int(potential_value), gem_descrip]
            gemstash.append(gem_final)
            remaining_gemvalue = remaining_gemvalue - potential_value
            total_gemvalue = total_gemvalue + potential_value
    handle.close()
gemstash.append(gem_string)
print ("Gems")
print ("====")
for gem in gemstash:
    print (gem)
print ("Gem value is ", total_gemvalue)
print ('\n')

jewelrystash = []
jewelrylist = []
total_jewelryvalue = 0
remaining_jewelryvalue = jewelry_value
handle = open('jewelryfile.txt', 'r')
for line in handle:
    linelist = line.split(', ')
    linelist[1] = int(linelist[1])
    jewelrylist.append(linelist)
jewelry_tuple = (("plain ", 1), ("engraved ", 2), ("ornate ", 5), ("bejeweled ", 10))
metal_tuple = (("copper ", 1), ("silver ", 10), ("electrum ", 50), ("gold ", 100), ("platinum ", 500), ("mithril ", 2000))
while remaining_jewelryvalue >= 10:
    potential_jewel = [random.choice(jewelry_tuple), random.choice(metal_tuple), random.choice(jewelrylist)]
    potential_jewel_description = potential_jewel[0][0] + potential_jewel[1][0] + potential_jewel[2][0]
    potential_jewel_value = potential_jewel[0][1] * potential_jewel[1][1] * int(potential_jewel[2][1])
    if remaining_jewelryvalue > potential_jewel_value:
        jewelrystash.append((potential_jewel_description, potential_jewel_value))
        remaining_jewelryvalue = remaining_jewelryvalue - potential_jewel_value
        total_jewelryvalue = total_jewelryvalue + potential_jewel_value

print ("Jewellery")
print ("=========")
for jewel in jewelrystash:
    print (jewel)
print ("Jewelry value is", total_jewelryvalue)
print ("\n")
handle.close()

itemstash = []
itemlist = [] # magic items and values are from DMG 3.0
total_itemvalue = 0
remaining_itemvalue = magic_item_value
handle = open('magicitemfile.txt', 'r')
for line in handle:
    linelist = line.split(', ')
    linelist[1] = int(linelist[1])
    itemlist.append(linelist)
while remaining_itemvalue >= 200:
    potential_item = random.choice(itemlist)
    if remaining_itemvalue >= potential_item[1]:
        itemstash.append(potential_item)
        remaining_itemvalue = remaining_itemvalue - potential_item[1]
        total_itemvalue = total_itemvalue + potential_item[1]
print ("Magic Items")
print ("===========")
for magicitem in itemstash:
    print (magicitem)
print ("Magic Item value is ", total_itemvalue)

total_treasurevalue = (int(copper_value)) + (int(silver_value)) + (int(electrum_value)) + int(gold_value) + int(platinum_value) + total_gemvalue + total_jewelryvalue + total_itemvalue
print ("total treasure value is", total_treasurevalue)


First of all, this is a big program. 
Secondly it uses a number of data files, namely magicitemfile.txtjewelryfile.txt and gemfile.txtThese contain large amounts of data that did not seem suitable to have in the main code. 
Thirdly this is not entirely accurate in total value - it gives amounts about 10% either side of initial required value. 

It works by splitting the treasure up into randomly-sized chunks between 0% and 10% of the required value. 
That chunk is then randomly determined to be a particular treasure type (copper coins, silver coins, gems, jewellery etc). Rather than equal chances for each type, the chances are determined by looking up the skew_ratio which then gives the chances for each type. 
If the chunk is made of coins, then the number of coins is determined based on the value of the chunk. 
If the chunk is made of gems, jewellery or magic items, then random examples of those are generate. If their values are equal to or less than that of the chunk then the item is added and the value of the item deducted from the chunk. If there is enough value left over in the chunk, another example is generated until the remaining value of the chunk falls below a threshold.   
And the end result? Here is a typical run, generating a 100,000gp-value treasure trove

 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test08a_treasure.py
Treasure hoard generating program
=================================
Please enter approximate value of treasure hoard:
?
100000
On a scale of 1-10 how skewed is it towards high value items?
1= mostly low value coins, 10 = mostly magic items:
5
Coins
=====
Copper value =  14977.991665483956
 Copper Coins =  1,497,799
Silver value =  15397.31065665539
 Silver Coins =  153,973
Electrum value =  18959.482408553344
 Electrum Coins =  94,797
Gold value =  2479.269646407558
 Gold coins =  2,479
Platinum value =  24353.349234787212
 Platinum coins =  4,870
Gem value =  1626.2385405454738
Jewelry value =  8493.713069086232
Magic Item value = 14708.117885848227
Total assigned value =  100995.4731073674


Gems
====
[30, 'poor small Aquamarine']
[100, 'poor huge Iolite']
[100, 'normal medium Amber']
[100, 'flawed very small Emerald']
[50, 'normal small Tourmaline']
[1000, 'flawless huge Carnelian']
[20, 'normal very small Peridot']
[30, 'normal medium Rose Quartz']
[7, 'flawed small Citrine']
[60, 'normal very small Garnet']
[120, 'flawless very small Pearl']

Gem value is  1617.5

Jewellery
=========
('ornate electrum vase', 7500)
('engraved silver fork', 100)
('plain copper spoon', 5)
('engraved copper torc', 100)
('ornate copper spurs', 50)
('plain copper crown', 100)
('plain silver spurs', 100)
('plain silver spurs', 100)
('plain silver necklace', 150)
('plain copper knife', 5)
('bejeweled copper pin', 50)
('plain copper dagger', 50)
('bejeweled copper fork', 50)
('plain copper chain', 20)
('ornate copper earring', 50)
('engraved copper nosering', 20)
('engraved copper headband', 40)
Jewelry value is 8490


Magic Items
===========
['Wand of Magic Missile', 750, 'DMG 3.0\n']
['Longsword +2', 8315, 'DMG 3.0\n']
['Scroll of 5th level cleric spell', 1125, 'DMG 3.0\n']
['Bolts +1 (50)', 2350, 'DMG 3.0\n']
['Scroll of 5th level wizard spell', 1125, 'DMG 3.0\n']
['Potion of Heroism', 900, 'DMG 3.0\n']
Magic Item value is  14565
total treasure value is 100837.5

>>>


The presentation could do with some cleaning up, but the idea is sound. And just in case you do play Dungeons & Dragons, the magicitemfile.txt derives its information from 3rd Edition Dungeon Master's Guide. If you prefer a different edition, feel free to generate your own magicitemfile.txt

No comments:

Post a Comment