#!/usr/bin/python -tt import randhack_saved, sys from random import choice LEEWAY = 1.25 TEST_SIZE = 1000000 # Role/Race/Alignment table # {{{ # | dwarf | elf | gnome | human | orc | # a Archeologist | L | | N | LN | | # b Barbarian | | | | NC | C | # c Cave(wo)man | L | | N | LN | | # h Healer | | | N | N | | # k Knight | | | | L | | # m Monk | | | | L | | # p Priest(ess) | | C | | LNC | | # r Rogue | | | | C | C | # R Ranger | | C | N | NC | C | # s Samurai | | | | L | | # t Tourist | | | | N | | # v Valkyrie | L | | | LN | | # w Wizard | | C | N | NC | C | # role, race, gender, alignment # }}} # dwarves are always lawful # elves and orcs are always chaotic # gnomes are always neutral # humans can have any alignment # # Rogues are always chaotic # Knights, Monks, and Samurai are always lawful # Healers and Tourists are always neutral # Archeologists, Cave(wo)men, and Valkyries are never chaotic # Barbarians, Rangers and Wizards are never lawful # Prest(esse)s may have any alignment # # The above eliminate many combinations. Also: # Dwarves may not be Knights, Monks, Priest(esse)s or Samurai # Elves may not be Barbarians or Rogues # Gnomes may not be Barbarians, Priest(esse)s, Tourists or Valkyries # Orcs may not be Priest(esse)s, roles = { # {{{ 'a': 'Archeologist', 'b': 'Barbarian', 'c': 'Cave(wo)man', 'h': 'Healer', 'k': 'Knight', 'm': 'Monk', 'p': 'Priest(ess)', 'r': 'Rogue', 'R': 'Ranger', 's': 'Samurai', 't': 'Tourist', 'v': 'Valkyrie', 'w': 'Wizard', } # }}} gender_roles = { 'c': { 'f': 'Cavewoman', 'm': 'Caveman', }, 'p': { 'f': 'Priestess', 'm': 'Priest', }, } races = { # {{{ 'd': 'dwarf', 'e': 'elf', 'g': 'gnome', 'h': 'human', 'o': 'orc', } # }}} races_adj = { # {{{ 'd': 'dwarven', 'e': 'elven', 'g': 'gnomish', 'h': 'human', 'o': 'orcish', } # }}} genders = { # {{{ 'f': 'female', 'm': 'male', } # }}} alignments = { # {{{ 'l': 'lawful', 'n': 'neutral', 'c': 'chaotic', } # }}} combinations = { # {{{ 'a': { 'd': 'l', 'g': 'n', 'h': 'ln' , }, 'b': { 'h': 'nc' , 'o': 'c' }, 'c': { 'd': 'l', 'g': 'n', 'h': 'ln' , }, 'h': { 'g': 'n', 'h': 'n' , }, 'k': { 'h': 'l' , }, 'm': { 'h': 'l' , }, 'p': { 'e': 'c', 'h': 'lnc', }, 'r': { 'h': 'c' , 'o': 'c' }, 'R': { 'e': 'c', 'g': 'n', 'h': 'nc' , 'o': 'c' }, 's': { 'h': 'l' , }, 't': { 'h': 'n' , }, 'v': { 'd': 'l', 'h': 'ln' , }, 'w': { 'e': 'c', 'g': 'n', 'h': 'nc' , 'o': 'c' }, } # }}} def initializeCounts(): if randhack_saved.SAVED_COUNTS is not None: return randhack_saved.SAVED_COUNTS counts = {} for role in roles.keys(): counts[role] = 0 for race in combinations[role].keys(): counts[role + race] = 0 for gender in genders.keys(): counts[role + race + gender] = 0 for alignment in combinations[role][race]: counts[role + race + gender + alignment] = 0 return counts def saveCounts(counts): fn = randhack_saved.__file__ if fn.endswith('.pyc'): fn = fn[:-1] fh = file(fn, 'w') fh.write('#!/usr/bin/python -tt\n') fh.write('SAVED_COUNTS = %s\n' % repr(counts)) def randomize(cur, counts, force): forceme = force[len(cur)] possible = [ key for key in counts.keys() if len(key) == len(cur) + 1 and key.startswith(cur) and (key.endswith(forceme) or forceme == '.') ] if len(possible) < 1: print "No possible choices! (force = '%s')" % force sys.exit(1) target = min([ counts[key] for key in possible ]) * LEEWAY result = choice([ result for result in possible if counts[result] <= target ]) print "Choosing '%s' (used %d times which below the target of %.02f)" % (result, counts[result], target) counts[result] += 1 return result def formatLong(combo): role, race, gender, alignment = combo if role in gender_roles: return ' '.join((alignments[alignment], races_adj[race], gender_roles[role][gender])) else: return ' '.join((alignments[alignment], genders[gender], races_adj[race], roles[role])) def report(counts): def sumSubset(combos, name, pos, lookup): counts = {} for thing, num in [ (c[-1], n) for c, n in combos if len(c) == pos and n > 0 ]: if thing not in counts: counts[thing] = 0 counts[thing] += num print ("\n== %s " % name) + '=' * (60 - len(name)) scounts = counts.items() scounts.sort(lambda a, b: cmp(a[0], b[0])) for thing, num in scounts: print '%5d - %s' % (num, lookup[thing]) combos = counts.items() combos.sort(lambda a, b: cmp(a[0], b[0])) print "\n== Totals ======================================================"; for combo, num in [ (c, n) for c, n in combos if len(c) == 4 and n > 0 ]: print '%5d - %s (%s)' % (num, formatLong(combo), combo) sumSubset(combos, 'Genders', 3, genders) sumSubset(combos, 'Alignments', 4, alignments) sumSubset(combos, 'Races', 2, races) sumSubset(combos, 'Roles', 1, roles) def main(): try: action = sys.argv[1] except: action = None counts = initializeCounts() if action == 'report': # print a report of stuff so far report(counts) elif action == 'reset': print "Restting counts!" counts = None elif action == 'test': tot = 0.0 print "Testing with %d iterations..." % TEST_SIZE mint = 10000 maxt = 0 for x in xrange(TEST_SIZE): temp = dict([ (key, 0) for key in roles.keys() ]) count = 0 while min(temp.values()) == 0: count += 1 temp[choice(roles.keys())] += 1 if count < mint: mint = count if count > maxt: maxt = count tot += count print "%.2f average attemps were requried to cover all roles randomly." % (tot/TEST_SIZE) print "(min: %d, max: %d)" % (mint, maxt) else: if action is not None and action.startswith('force='): force = action[-4:] else: force = '....' action = None passes = [ randomize('', counts, force) ] for x in range(3): passes.append(randomize(passes[-1], counts, force)) # combo = passes[-1] role, race, gender, alignment = combo print "Enter: %s" % combo print "You are a %s!" % formatLong(combo) if action == None: foo = 'a' while foo[0] not in 'yn': sys.stdout.write("Save? [y/n] ") foo = sys.stdin.readline().lower() if foo[0] == 'y': saveCounts(counts) if __name__ == '__main__': main()