def test_zone_properties_default(self): testZoneKey = ZoneDefinitionFields.EMPTY_SPACE z = Zone(testZoneKey) z.suffix = "Alpha" z.monstersKilled = 5 z.maxMonsters = 10 z.lvl = 4 pZPk = dbHelp.create_test_zone_obj(ZoneDefinitionFields.HOME).get_pk() z.previousZoneReferencePK = pZPk z.nextZoneReferenceList = [ dbHelp.create_test_zone_dict(ZoneDefinitionFields.ASTEROID_FIELD), dbHelp.create_test_zone_dict(ZoneDefinitionFields.GAS), dbHelp.create_test_zone_dict(ZoneDefinitionFields.NEBULA) ] self.assertEqual(z.definitionKey, testZoneKey) self.assertEqual( z.get_fullName(), ZoneDefinition.get_name_for_key(testZoneKey) + " Alpha") self.assertEqual(z.suffix, "Alpha") self.assertEqual(z.monstersKilled, 5) self.assertEqual(z.maxMonsters, 10) self.assertEqual(z.lvl, 4) self.assertEqual(z.get_description(), ZoneDefinition.get_description_for_key(testZoneKey)) self.assertEqual(z.previousZoneReferencePK, pZPk) self.assertListEqual(z.nextZoneReferenceList, [ dbHelp.create_test_zone_dict(ZoneDefinitionFields.ASTEROID_FIELD), dbHelp.create_test_zone_dict(ZoneDefinitionFields.GAS), dbHelp.create_test_zone_dict(ZoneDefinitionFields.NEBULA) ])
def construct_next_zone_choice(cls, heroLvl, vistiedZones, matchHeroLvl=False): """ generates a zone with unique name and randomlvl args: heroLvl: this should be a positive integer greater than 1 visitedZones: this should be a dict. the dict is used to keep tract of which name suffix combinations have popped up already. matchHeroLvl: Set this to true if first level.if this is true than the zone difficulty level will perfectly match the hero's level rather than approximate it. returns: a model of type zone also adds to the value for a key in the visitedZones dict """ import GeneralUtilities as gu selectedZoneKey = Zone.get_random_zone_definitionKey(heroLvl) definition = ZoneDefinition(selectedZoneKey) zone = { ZoneDBFields.DEFINITION_KEY: selectedZoneKey, ZoneDBFields.LVL: heroLvl, ZoneDBFields.MAX_MONSTERS: random.randint(5, 15), ZoneDBFields.NAME: definition.get_name(), ZoneDBFields.DESCRIPTION: definition.get_description() } if selectedZoneKey in vistiedZones: #if we've visited it before zone[ZoneDBFields.SUFFIX] = Zone.generate_full_zone_name_suffix( vistiedZones[selectedZoneKey]) zone[ZoneDBFields.FULL_NAME] = \ "{0} {1}".format(zone[ZoneDBFields.NAME],zone[ZoneDBFields.SUFFIX]).rstrip() vistiedZones[selectedZoneKey] += 1 else: zone[ZoneDBFields.FULL_NAME] = zone[ZoneDBFields.NAME] vistiedZones[selectedZoneKey] = 1 if not matchHeroLvl: zone[ZoneDBFields.LVL] = gu.calculate_lvl(heroLvl, 10) return zone
def test_zone_properties_from_id(self): hPk = dbHelp.setup_test_hero_using_default_values() z = dbHelp.create_test_zone_obj() testZoneKey = ZoneDefinitionFields.EMPTY_SPACE z.save_changes(hPk) zPk = z.get_pk() z2 = z.construct_model_from_pk(zPk) self.assertEqual(z.definitionKey, testZoneKey) self.assertEqual(z.get_fullName(), ZoneDefinition.get_name_for_key(testZoneKey) + " Alpha") self.assertEqual(z.suffix,"Alpha") self.assertEqual(z.monstersKilled, 2) self.assertEqual(z.maxMonsters,15) self.assertEqual(z.lvl, 3) self.assertEqual(z.get_description(),ZoneDefinition.get_description_for_key(testZoneKey)) oldCount = tu.get_record_count_from_table(ZoneDBFields.COLLECTION_NAME) z.save_changes(hPk) newCount = tu.get_record_count_from_table(ZoneDBFields.COLLECTION_NAME) self.assertEqual(oldCount , newCount)
def test_zone_properties_from_id(self): hPk = dbHelp.setup_test_hero_using_default_values() z = dbHelp.create_test_zone_obj() testZoneKey = ZoneDefinitionFields.EMPTY_SPACE z.save_changes(hPk) zPk = z.get_pk() z2 = z.construct_model_from_pk(zPk) self.assertEqual(z.definitionKey, testZoneKey) self.assertEqual( z.get_fullName(), ZoneDefinition.get_name_for_key(testZoneKey) + " Alpha") self.assertEqual(z.suffix, "Alpha") self.assertEqual(z.monstersKilled, 2) self.assertEqual(z.maxMonsters, 15) self.assertEqual(z.lvl, 3) self.assertEqual(z.get_description(), ZoneDefinition.get_description_for_key(testZoneKey)) oldCount = tu.get_record_count_from_table(ZoneDBFields.COLLECTION_NAME) z.save_changes(hPk) newCount = tu.get_record_count_from_table(ZoneDBFields.COLLECTION_NAME) self.assertEqual(oldCount, newCount)
def construct_next_zone_choice(cls,heroLvl,vistiedZones,matchHeroLvl = False): """ generates a zone with unique name and randomlvl args: heroLvl: this should be a positive integer greater than 1 visitedZones: this should be a dict. the dict is used to keep tract of which name suffix combinations have popped up already. matchHeroLvl: Set this to true if first level.if this is true than the zone difficulty level will perfectly match the hero's level rather than approximate it. returns: a model of type zone also adds to the value for a key in the visitedZones dict """ import GeneralUtilities as gu selectedZoneKey = Zone.get_random_zone_definitionKey(heroLvl) definition = ZoneDefinition(selectedZoneKey) zone = {ZoneDBFields.DEFINITION_KEY:selectedZoneKey,ZoneDBFields.LVL: heroLvl, ZoneDBFields.MAX_MONSTERS: random.randint(5,15),ZoneDBFields.NAME: definition.get_name(), ZoneDBFields.DESCRIPTION: definition.get_description()} if selectedZoneKey in vistiedZones: #if we've visited it before zone[ZoneDBFields.SUFFIX] = Zone.generate_full_zone_name_suffix(vistiedZones[selectedZoneKey]) zone[ZoneDBFields.FULL_NAME] = \ "{0} {1}".format(zone[ZoneDBFields.NAME],zone[ZoneDBFields.SUFFIX]).rstrip() vistiedZones[selectedZoneKey] += 1 else: zone[ZoneDBFields.FULL_NAME] = zone[ZoneDBFields.NAME] vistiedZones[selectedZoneKey] = 1 if not matchHeroLvl: zone[ZoneDBFields.LVL] = gu.calculate_lvl(heroLvl,10) return zone
def test_zone_properties_default(self): testZoneKey = ZoneDefinitionFields.EMPTY_SPACE z = Zone(testZoneKey) z.suffix = "Alpha" z.monstersKilled = 5 z.maxMonsters = 10 z.lvl = 4 pZPk = dbHelp.create_test_zone_obj(ZoneDefinitionFields.HOME).get_pk() z.previousZoneReferencePK = pZPk z.nextZoneReferenceList = [dbHelp.create_test_zone_dict(ZoneDefinitionFields.ASTEROID_FIELD), dbHelp.create_test_zone_dict(ZoneDefinitionFields.GAS), dbHelp.create_test_zone_dict(ZoneDefinitionFields.NEBULA)] self.assertEqual(z.definitionKey, testZoneKey) self.assertEqual(z.get_fullName(), ZoneDefinition.get_name_for_key(testZoneKey) + " Alpha") self.assertEqual(z.suffix,"Alpha") self.assertEqual(z.monstersKilled, 5) self.assertEqual(z.maxMonsters,10) self.assertEqual(z.lvl, 4) self.assertEqual(z.get_description(),ZoneDefinition.get_description_for_key(testZoneKey)) self.assertEqual(z.previousZoneReferencePK,pZPk) self.assertListEqual(z.nextZoneReferenceList,[dbHelp.create_test_zone_dict(ZoneDefinitionFields.ASTEROID_FIELD), dbHelp.create_test_zone_dict(ZoneDefinitionFields.GAS), dbHelp.create_test_zone_dict(ZoneDefinitionFields.NEBULA)])
def test_build_first_time_checkin_messages(self): from ZoneDefinitions import ZoneDefinition from AllDBFields import ZoneDefinitionFields from AllDBFields import ZoneDBFields pkStruct = dbHelp.create_test_user_using_default_values() hero = Hero.construct_model_from_pk(pkStruct['heroPk']) m = StartUpRoutine.build_first_time_checkin_messages(hero) s0 = m['storyNotice'] self.assertEqual(len(s0),self.EXPECTED_CHAR_COUNT_PLACEHOLDER) self.assertNotEqual(s0.find("USS Placeholder"),-1) s1 = m['zoneNotice'] zdef = ZoneDefinition(ZoneDefinitionFields.HOME) self.assertEqual(s1,zdef.get_description()) zPs = m['zonePrompt'] self.assertEqual(len(zPs),3) zDef = ZoneDefinition(ZoneDefinitionFields.EMPTY_SPACE) zp0 = zPs[0] self.assertEqual(zp0[ZoneDBFields.FULL_NAME],zDef.get_name()) self.assertEqual(zp0[ZoneDBFields.DESCRIPTION],zDef.get_description()) zDef = ZoneDefinition(ZoneDefinitionFields.GAS) zp0 = zPs[1] self.assertEqual(zp0[ZoneDBFields.FULL_NAME],zDef.get_name()) self.assertEqual(zp0[ZoneDBFields.DESCRIPTION],zDef.get_description()) zDef = ZoneDefinition(ZoneDefinitionFields.NEBULA) zp0 = zPs[2] self.assertEqual(zp0[ZoneDBFields.FULL_NAME],zDef.get_name()) self.assertEqual(zp0[ZoneDBFields.DESCRIPTION],zDef.get_description())
def get_zoneName(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_name()
def get_description(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_description()
class Zone(StoryModels): """ This is a wrapper for the zone data from the database. This is different from the other models in that Zone is used as a part of the hero model. """ def __init__(self, definitionKey): return super().__init__(definitionKey) @classmethod def construct_model_from_pk(cls, pk): """ args: id: uses the id to load this model from the database. return: an instance of the model on which this is called """ collection = DatabaseLayer.get_table( cls.get_dbFields().COLLECTION_NAME) obj = cls(None) obj.dict = collection.find_one({cls.get_dbFields().PK_KEY: pk}) return obj def save_changes(self, heroId): """ args: heroId: this needs to be an pymongo objectId. It is used as an owner relationship to a hero model """ from AllDBFields import HeroDbFields ownerCollection = DatabaseLayer.get_table( self.get_dbFields().OWNER_COLLECTION) if self.get_pk(): if self._changes: ownerCollection.update_one( {self.get_dbFields().PK_KEY: heroId}, {'$set': self._changes}) else: collection = DatabaseLayer.get_table( self.get_dbFields().COLLECTION_NAME) pk = collection.insert_one(self.dict).inserted_id self.dict[self.get_dbFields().PK_KEY] = pk nestedZone = {HeroDbFields.ZONE: self.dict} ownerCollection.update_one({self.get_dbFields().PK_KEY: heroId}, {'$set': nestedZone}) self._changes = {} @classmethod def get_dbFields(cls): return ZoneDBFields def get_zoneName(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_name() def get_fullName(self): return "{0} {1}".format(self.get_zoneName(), self.suffix).rstrip() @property def suffix(self): if self.get_dbFields().SUFFIX in self.dict: return self.dict[self.get_dbFields().SUFFIX] else: return "" @suffix.setter def suffix(self, value): self.set_common_story_property(self.get_dbFields().SUFFIX, value) @property def monstersKilled(self): if self.dict[self.get_dbFields().MONSTERS_KILLED]: return self.dict[self.get_dbFields().MONSTERS_KILLED] else: return 0 @monstersKilled.setter def monstersKilled(self, value): self.set_common_story_property(self.get_dbFields().MONSTERS_KILLED, value) @property def maxMonsters(self): return self.dict[self.get_dbFields().MAX_MONSTERS] @maxMonsters.setter def maxMonsters(self, value): self.set_common_story_property(self.get_dbFields().MAX_MONSTERS, value) @property def lvl(self): return self.dict[self.get_dbFields().LVL] @lvl.setter def lvl(self, value): self.set_common_story_property(self.get_dbFields().LVL, value) def get_description(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_description() @property def previousZoneReferencePK(self): if self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK in self.dict: return self.dict[self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK] return None @previousZoneReferencePK.setter def previousZoneReferencePK(self, value): self.set_common_story_property( self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK, value) @property def nextZoneReferenceList(self): return self.dict[self.get_dbFields().NEXT_ZONE_REFERENCE_LIST] @nextZoneReferenceList.setter def nextZoneReferenceList(self, value): self.set_common_story_property( self.get_dbFields().NEXT_ZONE_REFERENCE_LIST, value) @property def alias(self): raise NotImplementedError() return self.dict[self.get_dbFields().ALIAS] @alias.setter def alias(self, value): raise NotImplementedError() self.dict[self.get_dbFields().ALIAS] = value self._changes[self.get_dbFields().ALIAS] = value @property def definitionKey(self): return self.dict[self.get_dbFields().DEFINITION_KEY] @definitionKey.setter def definitionKey(self, value): self.set_common_story_property(self.get_dbFields().DEFINITION_KEY, value) @classmethod def get_home_zone(cls): """ this probably only needs to be called when a new hero is being created for a user args: heroId: this needs to be an pymongo objectId. It is used as an owner relationship to a hero model returns: a model of type zone with starting details """ from AllDBFields import ZoneDefinitionFields zone = Zone(ZoneDefinitionFields.HOME) zone.maxMonsters = 0 zone.skillLvl = 0 return zone @classmethod def construct_next_zone_choice(cls, heroLvl, vistiedZones, matchHeroLvl=False): """ generates a zone with unique name and randomlvl args: heroLvl: this should be a positive integer greater than 1 visitedZones: this should be a dict. the dict is used to keep tract of which name suffix combinations have popped up already. matchHeroLvl: Set this to true if first level.if this is true than the zone difficulty level will perfectly match the hero's level rather than approximate it. returns: a model of type zone also adds to the value for a key in the visitedZones dict """ import GeneralUtilities as gu selectedZoneKey = Zone.get_random_zone_definitionKey(heroLvl) definition = ZoneDefinition(selectedZoneKey) zone = { ZoneDBFields.DEFINITION_KEY: selectedZoneKey, ZoneDBFields.LVL: heroLvl, ZoneDBFields.MAX_MONSTERS: random.randint(5, 15), ZoneDBFields.NAME: definition.get_name(), ZoneDBFields.DESCRIPTION: definition.get_description() } if selectedZoneKey in vistiedZones: #if we've visited it before zone[ZoneDBFields.SUFFIX] = Zone.generate_full_zone_name_suffix( vistiedZones[selectedZoneKey]) zone[ZoneDBFields.FULL_NAME] = \ "{0} {1}".format(zone[ZoneDBFields.NAME],zone[ZoneDBFields.SUFFIX]).rstrip() vistiedZones[selectedZoneKey] += 1 else: zone[ZoneDBFields.FULL_NAME] = zone[ZoneDBFields.NAME] vistiedZones[selectedZoneKey] = 1 if not matchHeroLvl: zone[ZoneDBFields.LVL] = gu.calculate_lvl(heroLvl, 10) return zone @classmethod def get_random_zone_definitionKey(cls, heroLvl): """ selects a random dictionary key to be used with ZoneDefinitions args: heroLvl: this should be a positive integer greater than 1 returns: a string which is a dict key """ zoneGroupKeys = Zone.get_unlocked_zone_groupKeys(heroLvl) selectedZoneGroupKey = random.choice(zoneGroupKeys) zoneList = list(AllZones[selectedZoneGroupKey].keys()) return random.choice(zoneList) @classmethod def generate_full_zone_name_suffix(cls, visitCount): """ each time we visit a particular zone type, we don't want it to have the same exact name as last time. To do this, we will add a suffix to the name. This generates a suffic based on the number of times that zone has been hit. args: visitCount: the number of times the hero character has visited a zone returns: a suffix which will be a string. We will take this string and append it to stuff. """ if visitCount < 1: return "" symbols = Zone.get_symbols() hugeVisitCountResult = Zone.special_action_for_extremely_huge_visitCounts( visitCount, symbols) numericSuffix = hugeVisitCountResult['numericSuffix'] visitCount = hugeVisitCountResult['visitCount'] adjustedVisitCount = Zone.skip_powers_of_base_in_number( visitCount, len(symbols)) suffix = Zone.get_symbol_suffix(adjustedVisitCount, symbols) if numericSuffix > 0: suffix += str(numericSuffix) return suffix.strip() @classmethod def special_action_for_extremely_huge_visitCounts(cls, visitCount, symbols): """ this gets a special suffix for extremely huge vist counts, i.e, higher than 10100. Also shrinks the number play nicely with the normal suffix generating process args: visitCount: the number of times the hero character has visited a zone symbols: the list of symbols. We're changing the first element to something magic return: a dict with the numericSuffix value and the updated visitCount """ numericSuffix = 0 if visitCount > (len(symbols) - 1) * len(symbols): symbols[0] = "?4815162342" numericSuffix = Zone.get_numeric_suffix(visitCount, len(symbols)) visitCount = Zone.adjust_visitCount_for_extremely_huge_counts( visitCount, len(symbols)) return {'numericSuffix': numericSuffix, 'visitCount': visitCount} @classmethod def get_symbol_suffix(cls, visitCount, symbols): """ converts a number to a suffix. Think of it as converting a number to a base 100 system of sorts args: visitCount: the number of times the hero character has visited a zone symbols: the list of symbols. return: a string to be zone suffix """ suffix = "" while visitCount > 0: r = visitCount % len(symbols) visitCount //= len(symbols) suffix = (symbols[r] + " " + suffix) return suffix @classmethod def adjust_visitCount_for_extremely_huge_counts(cls, visitCount, symbolsLen): """ args: visitCount: the number of times the hero character has visited a zone symbolsLen: the count of all the available symbols to be made into a suffix """ return visitCount % ((symbolsLen - 1) * symbolsLen) @classmethod def get_numeric_suffix(cls, visitCount, symbolsLen): """ args: visitCount: the number of times the hero character has visited a zone symbolsLen: the count of all the available symbols to be made into a suffix """ #the -1 on the first array length is to account for the single symbol range of items return visitCount // ( (symbolsLen - 1) * symbolsLen) + 1 #+1 because the 1 suffix would be redundant @classmethod def get_symbols(cls): """ if you add any items to symbols, please adjust the unit test to account for that """ symbols = [ "", "Alpha", "Beta", "Cain", "Delta", #4 "Epsilon", "Foxtrot", "September", "October", #8 "November", "Kilo", "Juliett", "Romeo", "Silver", "Deckard", #14 "Sierra", "Tango", "Zeta", "Theta", "July", "Ludwig", "Tyrell", #21 "Lambda", "Mu", "London", "Victor", "Quintin", "Gold", #27 "Whiskey", "Xray", "Zulu", "Pi", "Rho", "Antilles", "Blanca", #34 "Sigma", "Tau", "India", "Hector", "Quebec", "Waltz", "Sapphire", #41 "Tokyo", "Ramesses", "Washington", "Darius", "Emerald", "Midgard", #47 "Futura", "Charlotte", "Flanders", "Berlin", "Onion", "Ruby", #53 "David", "Pizza", "Lazlo", "Kong", "Jerico", "Diamond", #59 "Black", "White", "Olaf", "Biggs", "Wedge", "Tyrannus", #65 "Richter", "Medusa", "Swan", "Gemini", "Noir", "Xerxes", #71 "TNT", "Plutonia", "Cerberus", "Tiberius", #75 "Arcturus", "Prime", "Tarsonis", "Babylon", "Sparta", #80 "Atlanta", "Yutani", "Python", "Ridley", "Midway", #85 "Bismark", "Dextera", "Dominus", "Jejunum", #89 "Superior", "Distal", "Eurebus", "Indigo", #93 "Xs", "Rex", "Titan", "Zen", "Apex", "Omega", "Zed" ] #100 return symbols @classmethod def skip_powers_of_base_in_number(cls, num, base): """ Numbers naturally want to follow this pattern: 0,A,B,C,...,Y,Z,A0,AA,AB,AC,...,AY,AZ,B0,BA,BB,BC But I want zone suffix naming system to follow this pattern: 0,A,B,C,...,Y,Z,AA,AB,AC,...,AY,AZ,BA,BB,BC,... This function adjust numbers to fit the wanted pattern, i.e. without the proverbial mulitples of 10 The accuracy of this function becomes unreliable after base^2 args: num: this is the number that we're offsetting. base: an integer. multiples of this number will be skipped returns: a number that's been offset for the base occurances skipped over """ if base < 1 or not float.is_integer(float(base)): raise ValueError("Base needs to be a positive non-zero integer") if not float.is_integer(float(num)): raise ValueError( "num needs to be an integer and not a floating number") isNegative = False if num < 0: num *= -1 isNegative = True adjusterNum = num + (num // base) return num + (adjusterNum // base) @classmethod def get_unlocked_zone_groupKeys(cls, heroLvl): """" gets the list of availible zones groups that can be selected depeding on the hero's level args: heroLvl: this should be an interger returns: a list of dict keys to the AllZones dict. """ if heroLvl < 1: return [] availableZonesGroups = [] availableZonesGroups.append("lvl1Zones") if heroLvl >= 5: availableZonesGroups.append("lvl5Zones") if heroLvl >= 10: availableZonesGroups.append("lvl10Zones") if heroLvl >= 15: availableZonesGroups.append("lvl15Zones") if heroLvl >= 20: availableZonesGroups.append("lvl20Zones") if heroLvl >= 25: availableZonesGroups.append("lvl25Zones") if heroLvl >= 30: availableZonesGroups.append("lvl30Zones") return availableZonesGroups
class Zone(StoryModels): """ This is a wrapper for the zone data from the database. This is different from the other models in that Zone is used as a part of the hero model. """ def __init__(self, definitionKey): return super().__init__(definitionKey) @classmethod def construct_model_from_pk(cls,pk): """ args: id: uses the id to load this model from the database. return: an instance of the model on which this is called """ collection = DatabaseLayer.get_table(cls.get_dbFields().COLLECTION_NAME) obj = cls(None) obj.dict = collection.find_one({cls.get_dbFields().PK_KEY:pk}) return obj def save_changes(self,heroId): """ args: heroId: this needs to be an pymongo objectId. It is used as an owner relationship to a hero model """ from AllDBFields import HeroDbFields ownerCollection = DatabaseLayer.get_table(self.get_dbFields().OWNER_COLLECTION) if self.get_pk(): if self._changes: ownerCollection.update_one({self.get_dbFields().PK_KEY:heroId},{'$set':self._changes}) else: collection = DatabaseLayer.get_table(self.get_dbFields().COLLECTION_NAME) pk = collection.insert_one(self.dict).inserted_id self.dict[self.get_dbFields().PK_KEY] = pk nestedZone = {HeroDbFields.ZONE:self.dict} ownerCollection.update_one({self.get_dbFields().PK_KEY:heroId},{'$set':nestedZone}) self._changes = {} @classmethod def get_dbFields(cls): return ZoneDBFields def get_zoneName(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_name() def get_fullName(self): return "{0} {1}".format(self.get_zoneName(),self.suffix).rstrip() @property def suffix(self): if self.get_dbFields().SUFFIX in self.dict: return self.dict[self.get_dbFields().SUFFIX] else: return "" @suffix.setter def suffix(self,value): self.set_common_story_property(self.get_dbFields().SUFFIX,value) @property def monstersKilled(self): if self.dict[self.get_dbFields().MONSTERS_KILLED]: return self.dict[self.get_dbFields().MONSTERS_KILLED] else: return 0 @monstersKilled.setter def monstersKilled(self,value): self.set_common_story_property(self.get_dbFields().MONSTERS_KILLED,value) @property def maxMonsters(self): return self.dict[self.get_dbFields().MAX_MONSTERS] @maxMonsters.setter def maxMonsters(self,value): self.set_common_story_property(self.get_dbFields().MAX_MONSTERS,value) @property def lvl(self): return self.dict[self.get_dbFields().LVL] @lvl.setter def lvl(self,value): self.set_common_story_property(self.get_dbFields().LVL,value) def get_description(self): if not self._definition: self._definition = ZoneDefinition(self.definitionKey) return self._definition.get_description() @property def previousZoneReferencePK(self): if self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK in self.dict: return self.dict[self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK] return None @previousZoneReferencePK.setter def previousZoneReferencePK(self,value): self.set_common_story_property(self.get_dbFields().PREVIOUS_ZONE_REFERENCE_PK,value) @property def nextZoneReferenceList(self): return self.dict[self.get_dbFields().NEXT_ZONE_REFERENCE_LIST] @nextZoneReferenceList.setter def nextZoneReferenceList(self,value): self.set_common_story_property(self.get_dbFields().NEXT_ZONE_REFERENCE_LIST,value) @property def alias(self): raise NotImplementedError() return self.dict[self.get_dbFields().ALIAS] @alias.setter def alias(self,value): raise NotImplementedError() self.dict[self.get_dbFields().ALIAS] = value self._changes[self.get_dbFields().ALIAS] = value @property def definitionKey(self): return self.dict[self.get_dbFields().DEFINITION_KEY] @definitionKey.setter def definitionKey(self,value): self.set_common_story_property(self.get_dbFields().DEFINITION_KEY,value) @classmethod def get_home_zone(cls): """ this probably only needs to be called when a new hero is being created for a user args: heroId: this needs to be an pymongo objectId. It is used as an owner relationship to a hero model returns: a model of type zone with starting details """ from AllDBFields import ZoneDefinitionFields zone = Zone(ZoneDefinitionFields.HOME) zone.maxMonsters = 0 zone.skillLvl = 0 return zone @classmethod def construct_next_zone_choice(cls,heroLvl,vistiedZones,matchHeroLvl = False): """ generates a zone with unique name and randomlvl args: heroLvl: this should be a positive integer greater than 1 visitedZones: this should be a dict. the dict is used to keep tract of which name suffix combinations have popped up already. matchHeroLvl: Set this to true if first level.if this is true than the zone difficulty level will perfectly match the hero's level rather than approximate it. returns: a model of type zone also adds to the value for a key in the visitedZones dict """ import GeneralUtilities as gu selectedZoneKey = Zone.get_random_zone_definitionKey(heroLvl) definition = ZoneDefinition(selectedZoneKey) zone = {ZoneDBFields.DEFINITION_KEY:selectedZoneKey,ZoneDBFields.LVL: heroLvl, ZoneDBFields.MAX_MONSTERS: random.randint(5,15),ZoneDBFields.NAME: definition.get_name(), ZoneDBFields.DESCRIPTION: definition.get_description()} if selectedZoneKey in vistiedZones: #if we've visited it before zone[ZoneDBFields.SUFFIX] = Zone.generate_full_zone_name_suffix(vistiedZones[selectedZoneKey]) zone[ZoneDBFields.FULL_NAME] = \ "{0} {1}".format(zone[ZoneDBFields.NAME],zone[ZoneDBFields.SUFFIX]).rstrip() vistiedZones[selectedZoneKey] += 1 else: zone[ZoneDBFields.FULL_NAME] = zone[ZoneDBFields.NAME] vistiedZones[selectedZoneKey] = 1 if not matchHeroLvl: zone[ZoneDBFields.LVL] = gu.calculate_lvl(heroLvl,10) return zone @classmethod def get_random_zone_definitionKey(cls,heroLvl): """ selects a random dictionary key to be used with ZoneDefinitions args: heroLvl: this should be a positive integer greater than 1 returns: a string which is a dict key """ zoneGroupKeys = Zone.get_unlocked_zone_groupKeys(heroLvl) selectedZoneGroupKey = random.choice(zoneGroupKeys) zoneList = list(AllZones[selectedZoneGroupKey].keys()) return random.choice(zoneList) @classmethod def generate_full_zone_name_suffix(cls,visitCount): """ each time we visit a particular zone type, we don't want it to have the same exact name as last time. To do this, we will add a suffix to the name. This generates a suffic based on the number of times that zone has been hit. args: visitCount: the number of times the hero character has visited a zone returns: a suffix which will be a string. We will take this string and append it to stuff. """ if visitCount < 1: return "" symbols = Zone.get_symbols() hugeVisitCountResult = Zone.special_action_for_extremely_huge_visitCounts(visitCount,symbols) numericSuffix = hugeVisitCountResult['numericSuffix'] visitCount = hugeVisitCountResult['visitCount'] adjustedVisitCount = Zone.skip_powers_of_base_in_number(visitCount,len(symbols)) suffix = Zone.get_symbol_suffix(adjustedVisitCount,symbols) if numericSuffix > 0: suffix += str(numericSuffix) return suffix.strip() @classmethod def special_action_for_extremely_huge_visitCounts(cls,visitCount,symbols): """ this gets a special suffix for extremely huge vist counts, i.e, higher than 10100. Also shrinks the number play nicely with the normal suffix generating process args: visitCount: the number of times the hero character has visited a zone symbols: the list of symbols. We're changing the first element to something magic return: a dict with the numericSuffix value and the updated visitCount """ numericSuffix = 0 if visitCount > (len(symbols)-1) * len(symbols): symbols[0] = "?4815162342" numericSuffix = Zone.get_numeric_suffix(visitCount,len(symbols)) visitCount = Zone.adjust_visitCount_for_extremely_huge_counts(visitCount,len(symbols)) return {'numericSuffix':numericSuffix,'visitCount':visitCount} @classmethod def get_symbol_suffix(cls,visitCount,symbols): """ converts a number to a suffix. Think of it as converting a number to a base 100 system of sorts args: visitCount: the number of times the hero character has visited a zone symbols: the list of symbols. return: a string to be zone suffix """ suffix = "" while visitCount > 0: r = visitCount % len(symbols) visitCount //= len(symbols) suffix = (symbols[r] + " " + suffix) return suffix @classmethod def adjust_visitCount_for_extremely_huge_counts(cls,visitCount,symbolsLen): """ args: visitCount: the number of times the hero character has visited a zone symbolsLen: the count of all the available symbols to be made into a suffix """ return visitCount % ((symbolsLen-1) * symbolsLen) @classmethod def get_numeric_suffix(cls,visitCount,symbolsLen): """ args: visitCount: the number of times the hero character has visited a zone symbolsLen: the count of all the available symbols to be made into a suffix """ #the -1 on the first array length is to account for the single symbol range of items return visitCount // ((symbolsLen-1) * symbolsLen) + 1 #+1 because the 1 suffix would be redundant @classmethod def get_symbols(cls): """ if you add any items to symbols, please adjust the unit test to account for that """ symbols =["","Alpha", "Beta","Cain","Delta", #4 "Epsilon","Foxtrot","September","October", #8 "November","Kilo","Juliett","Romeo","Silver","Deckard", #14 "Sierra","Tango","Zeta","Theta","July","Ludwig","Tyrell", #21 "Lambda","Mu","London","Victor","Quintin","Gold", #27 "Whiskey","Xray","Zulu","Pi","Rho","Antilles","Blanca", #34 "Sigma","Tau","India","Hector","Quebec","Waltz","Sapphire", #41 "Tokyo","Ramesses","Washington","Darius","Emerald","Midgard", #47 "Futura","Charlotte","Flanders","Berlin","Onion","Ruby", #53 "David","Pizza","Lazlo","Kong","Jerico","Diamond", #59 "Black","White","Olaf","Biggs","Wedge","Tyrannus", #65 "Richter","Medusa","Swan","Gemini","Noir","Xerxes",#71 "TNT","Plutonia","Cerberus","Tiberius", #75 "Arcturus","Prime","Tarsonis","Babylon","Sparta",#80 "Atlanta","Yutani","Python","Ridley","Midway", #85 "Bismark","Dextera","Dominus","Jejunum", #89 "Superior","Distal","Eurebus","Indigo", #93 "Xs","Rex","Titan","Zen","Apex","Omega","Zed"] #100 return symbols @classmethod def skip_powers_of_base_in_number(cls,num,base): """ Numbers naturally want to follow this pattern: 0,A,B,C,...,Y,Z,A0,AA,AB,AC,...,AY,AZ,B0,BA,BB,BC But I want zone suffix naming system to follow this pattern: 0,A,B,C,...,Y,Z,AA,AB,AC,...,AY,AZ,BA,BB,BC,... This function adjust numbers to fit the wanted pattern, i.e. without the proverbial mulitples of 10 The accuracy of this function becomes unreliable after base^2 args: num: this is the number that we're offsetting. base: an integer. multiples of this number will be skipped returns: a number that's been offset for the base occurances skipped over """ if base < 1 or not float.is_integer(float(base)): raise ValueError("Base needs to be a positive non-zero integer") if not float.is_integer(float(num)): raise ValueError("num needs to be an integer and not a floating number") isNegative = False if num < 0: num *= -1 isNegative = True adjusterNum = num + (num // base) return num + (adjusterNum // base) @classmethod def get_unlocked_zone_groupKeys(cls,heroLvl): """" gets the list of availible zones groups that can be selected depeding on the hero's level args: heroLvl: this should be an interger returns: a list of dict keys to the AllZones dict. """ if heroLvl < 1: return [] availableZonesGroups = [] availableZonesGroups.append("lvl1Zones") if heroLvl >= 5: availableZonesGroups.append("lvl5Zones") if heroLvl >= 10: availableZonesGroups.append("lvl10Zones") if heroLvl >= 15: availableZonesGroups.append("lvl15Zones") if heroLvl >= 20: availableZonesGroups.append("lvl20Zones") if heroLvl >= 25: availableZonesGroups.append("lvl25Zones") if heroLvl >= 30: availableZonesGroups.append("lvl30Zones") return availableZonesGroups