import random import sys Strategies = {'average' : 0.5, 'impressive' : 1.0, 'extreme' : 1.5, 'maximum' : 2.0 } class Race: """ Represents an abstract 'race' in Exalted, such as a type of Exalted. This is a mechanical definition, where the attribute limits, charm limits and such differ. """ def __init__(self, name): self.name = name self.attributeRange = range(1,10 + 1) self.abilityRange = range(1,10 + 1) self.specialtyRange = range(0,3 + 1) self.augmentationRange = range(-5,10 + 1) self.essenceRange = range(2,10 + 1) self.charmRange = (0) def getAbilityRange(self): ''' An array containing each possible ability rating for this race. This is usually one to ten, but may vary for mortals or fey ''' return self.abilityRange def getAgmentationRange(self): ''' An array containing each possible ability rating for "augmentation". This is a generic accounting dice from non-magical, non-trait sources. This might be situational penalties or bonuses, stunts, dice from equipment and so on. ''' return self.augmentationRange def getAttributeRange(self): ''' An array containing each possible attribute rating for this race. This is usually one to ten, but may vary for mortals or attribute-based exalts ''' return self.attributeRange def getEssenceRange(self): ''' An array containing each possible essence rating for this race. This is usually one to ten, but may vary for mortals or other races. It usually doesn't play a part in the calculations here, other than to act as limits for certain traits. ''' return self.essenceRange def getCharmRange(self,character): ''' An array containing each possible adjustment from charms for a particular instance of this race. This is usually overridden for each race. By default, it returns zero. ''' cap = self.getCharmCap(character) return range(0,cap+1) def getCharmCap(self,character): ''' The charm cap for this race. By default, it returns zero. ''' return 0 def getSpecialtyRange(self): ''' The array of possible dice from specialties. This is almost always zero to three. ''' return self.specialtyRange class Abyssal(Race): def __init__(self): Race.__init__(self, "abyssal") def getCharmCap(self,instance): attribute = instance.attribute ability = instance.ability return attribute + ability class Alchemical(Race): def __init__(self): Race.__init__(self, "alchemical") self.attributeRange = range(1,12 + 1) def getCharmCap(self,character): attribute = character.attribute return attribute class Lunar(Race): def __init__(self): Race.__init__(self, "lunar") self.attributeRange = range(1,15 + 1) def getCharmCap(self,character): attribute = character.attribute return attribute class Infernal(Race): def __init__(self): Race.__init__(self, "infernal") def getCharmCap(self,character): attribute = character.attribute ability = character.ability return attribute + ability class Sidereal(Race): def __init__(self): Race.__init__(self, "sidereal") def getCharmCap(self,character): essence = character.essence return essence class Solar(Race): def __init__(self): Race.__init__(self, "solar") def getCharmCap(self,character): attribute = character.attribute ability = character.ability return attribute + ability class Terrestrial(Race): def __init__(self): Race.__init__(self, "terrestrial") self.essenceRange = range(1,7 + 1) def getCharmCap(self,character): ability = character.ability specialty = character.specialty return ability + specialty class Character: def __init__(self, race, essence, attribute, ability, specialty): self.race = race self.essence = essence self.attribute = attribute self.ability = ability self.specialty = specialty self.instances = [] def __repr__(self): return "%s %d %d %d %d" % (self.race.name, self.essence, self.attribute, self.ability, self.specialty) def addInstance(self, augmentation, charm, strategy, stunt = 0, virtue = 0): instance = Instance(self, augmentation, charm, strategy, stunt, virtue) self.instances.append(instance) return instance def getCharmRange(self): cap = self.race.getCharmCap(self) return (0,cap) # for charms, only look at all or nothing class Instance: def __init__(self, character, augmentation, charm, strategy, stunt, virtue): self.character = character self.augmentation = augmentation self.charm = charm self.strategy = strategy self.stunt = stunt self.virtue = virtue self.result = max(0,int(Strategies[strategy] * self.getRolledPool())) def __repr__(self): return "%s %d %d %s %d = %d " % (self.character, self.augmentation, self.charm, self.strategy, self.getRolledPool(), self.result) def getTraitPool(self): attribute = self.character.attribute ability = self.character.ability specialty = self.character.specialty return attribute + ability + specialty def getBasePool(self): traits = self.getTraitPool() return traits + self.augmentation + self.stunt + self.virtue def getRolledPool(self): base = self.getBasePool() return base + self.charm def getResult(self): return self.result def getEssence(self): return self.character.essence def getStunt(self): return self.stunt def getVirtueChannel(self): return self.virtue class ExaltedSimulatorBase: def __init__(self): random.seed() def rollPool(self,count,sort = False): results = [] for i in range(1, count): die = self.getRoll() results.append(die) if (sort): results.sort() return results def countDamage(self,results,success=7,doubleSuccess=10): return self.countSuccesses(results,success,11) def countSuccesses(self,results,success=7,doubleSuccess=10): successes = 0 for die in results: if die >= success: successes += 1 if die >= doubleSuccess: successes += 1 return successes def getRoll(self): return random.randint(1, 10) class ExaltedSuccessSimulator: def __init__(self): self.races = (Solar(), Lunar()) self.instances = [] self.byRace = {} self.byEssence = {} self.byTraitPool = {} self.byBasePool = {} self.byRolledPool = {} self.byStrategy = {} self.byResult = {} def buildLookupMaps(self): for instance in self.instances: traitPool = instance.getTraitPool() basePool = instance.getBasePool() rolledPool = instance.getRolledPool() def createInstances(self): for race in self.races: raceInstances = [] for essence in race.getEssenceRange(): essenceInstances = [] for attribute in race.getAttributeRange(): if attribute <= 5 or attribute <= essence: for ability in race.getAbilityRange(): if ability <= 5 or ability <= essence: for specialty in race.getSpecialtyRange(): character = Character(race, essence, attribute, ability, specialty) for augmentation in race.getAgmentationRange(): for charm in character.getCharmRange(): for strategy in Strategies.keys(): instance = character.addInstance(augmentation, charm, strategy) essenceInstances.append(instance) self.addToMap(self.byEssence,essence,instance) self.addToMap(self.byTraitPool,instance.getTraitPool(),instance) self.addToMap(self.byBasePool,instance.getBasePool(),instance) self.addToMap(self.byRolledPool,instance.getRolledPool(),instance) self.addToMap(self.byStrategy,strategy,instance) self.addToMap(self.byResult,instance.getResult(),instance) print "Instances of race %s, essence %d: %d" % (race.name, essence, len(essenceInstances)) raceInstances.extend(essenceInstances) self.byRace[race.name] = raceInstances print "Instances of race %s: %d" % (race.name,len(raceInstances)) print "Current results: %s" % (self.byResult.keys()) self.instances.extend(raceInstances) print "Total instances: %d" % (len(self.instances)) def addToMap(self,map,index,instance): try: map[index].append(instance) except: map[index] = [instance] def run(self): self.createInstances() #self.buildLookupMaps() class StrategyResult: def __init__(self, simulation, name): self.results = {} for count in range (0,simulation.maxSuccesses): self.results[count] = 0 self.name = name self.simulation = simulation def getOccuranceOfSuccesses(self,suxCount): return self.results[suxCount] def addResult(self,suxCount): self.results[suxCount] += 1; class CanonicalStrategyResult(StrategyResult): def __init__(self,simulation,charInstance): StrategyResult.__init__(self,simulation,"Canon") self.charInstance = charInstance self.poolSize = self.charInstance.getBasePool() def applyStrategy(self,dice): roll = dice[:self.poolSize] sux = self.simulation.baseSim.countSuccesses(roll) self.addResult(sux) class CanonicalMortalStrategyResult(CanonicalStrategyResult): def __init__(self,simulation,charInstance): CanonicalStrategyResult.__init__(self,simulation,charInstance) self.name = "Canon Mortal" def applyStrategy(self,dice): roll = dice[:self.poolSize] sux = self.simulation.baseSim.countSuccesses(roll,doubleSuccess=11) self.addResult(sux) class RollAndKeepStrategyResult(StrategyResult): def __init__(self,simulation,charInstance): StrategyResult.__init__(self,simulation,"Roll & Keep") self.charInstance = charInstance self.poolSize = charInstance.character.attribute + charInstance.character.ability + charInstance.character.specialty self.baseKeep = 1 + self.charInstance.getEssence() + self.charInstance.getVirtueChannel() self.keep = self.calculateKeep() def calculateKeep(self): keep = self.baseKeep if keep > self.poolSize: keep = self.poolSize return keep def getDice(self,dice): roll = dice[:self.poolSize] roll.sort(reverse=True) keep = roll[:self.keep] return keep def applyStrategy(self,dice): result = self.getDice(dice) sux = self.simulation.baseSim.countSuccesses(result) self.addResult(sux) class RollAndKeepMortalStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Mortal" def applyStrategy(self,dice): result = self.getDice(dice) sux = self.simulation.baseSim.countSuccesses(result,doubleSuccess=11) self.addResult(sux) class RollAndKeepSolarStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Solar Exc." self.addedSux = charInstance.character.ability def applyStrategy(self,dice): result = self.getDice(dice) sux = self.simulation.baseSim.countSuccesses(result) self.addResult(sux + self.addedSux) class RollAndKeepLunarStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Lunar Exc." self.convertedSux = charInstance.character.attribute if self.convertedSux > self.poolSize: self.convertedSux = self.poolSize self.poolSize = 0 else: self.poolSize -= self.convertedSux self.keep = self.calculateKeep() def applyStrategy(self,dice): result = self.getDice(dice) sux = self.simulation.baseSim.countSuccesses(result) self.addResult(sux + self.convertedSux) class RollAndKeepSiderealStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Sidereal Exc." self.baseKeep += 1 self.keep = self.calculateKeep() def applyStrategy(self,dice): result = self.getDice(dice) sux = self.simulation.baseSim.countSuccesses(result,doubleSuccess=8) self.addResult(sux) class RollAndKeepTerrestrialStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Terrestrial Exc." addedDice = charInstance.character.ability + charInstance.character.specialty self.poolSize += addedDice self.keep = self.calculateKeep() class RollAndKeepAlchemicalStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Alchemical Exc." self.poolSize += 2 self.baseKeep += 2 self.keep = self.calculateKeep() class RollAndKeepSpiritStrategyResult(RollAndKeepStrategyResult): def __init__(self,simulation,charInstance): RollAndKeepStrategyResult.__init__(self,simulation,charInstance) self.name = "R&K Spirit Exc." addedDice = charInstance.character.ability self.poolSize += addedDice self.keep = self.calculateKeep() class RollAndKeepCharacterSim: def __init__(self, simulator, essence, attribute, ability, specialty, stunt, virtue): self.baseSim = simulator self.character = Character(Solar(), essence, attribute, ability, specialty) self.charInstance = self.character.addInstance(0, 0, 'average', stunt, virtue) self.result = [] self.maxSuccesses = (attribute + ability + specialty + stunt + virtue + 1) * 3 def createResultInstances(self): self.result.append(CanonicalStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepStrategyResult(self,self.charInstance)) if self.charInstance.getEssence() == 1: self.result.append(CanonicalMortalStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepMortalStrategyResult(self,self.charInstance)) else: self.result.append(RollAndKeepSolarStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepLunarStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepSiderealStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepTerrestrialStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepAlchemicalStrategyResult(self,self.charInstance)) self.result.append(RollAndKeepSpiritStrategyResult(self,self.charInstance)) def run(self): self.createResultInstances() millions = 1 repetitions = millions * 1000000 commonPoolSize = 30 commondice = [] while repetitions > 0: commondice = self.baseSim.rollPool(commonPoolSize) for resultSet in self.result: resultSet.applyStrategy(commondice) repetitions -= 1 if repetitions % 100000 == 0: sys.stderr.write('%d\n' % (repetitions)) self.output(millions) def output(self,millions): sys.stdout.write('%d million rolls, Essense=%d, Att+Abl+Sp=%d, Stunt=%d, Virtue=%d\n' % (millions, self.charInstance.getEssence(), self.charInstance.getTraitPool(), self.charInstance.getStunt(), self.charInstance.getVirtueChannel())) header = "Count" for resultSet in self.result: header += "\t" + resultSet.name sys.stdout.write(header) sys.stdout.write("\n") for count in range (0,self.maxSuccesses): line = '%d' % (count) for resultSet in self.result: line += "\t%d" % resultSet.getOccuranceOfSuccesses(count) sys.stdout.write(line) sys.stdout.write("\n") sys.stdout.write("\n\n") sys.stdout.flush() class RollAndKeepSimulator(ExaltedSimulatorBase): def __init__(self): ExaltedSimulatorBase.__init__(self) self.simulations = [] for essence in [3]: for traits in (1,3,5): specialtyRange = [0] if essence > 1: specialtyRange.append(3) for specialty in specialtyRange: for stunt in (0,2): virtueRange = [0,3] if essence > 1: virtueRange.append(5) for virtue in virtueRange: sim = RollAndKeepCharacterSim(self,essence,traits,traits,specialty,stunt,virtue) self.simulations.append(sim) def run(self): for sim in self.simulations: sim.run() #profile.run('main()') simulator = RollAndKeepSimulator() simulator.run()