214 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			214 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | import utils.log, random | ||
|  | from utils.utils import getRangeDict, chooseFromRange | ||
|  | from rando.ItemLocContainer import ItemLocation | ||
|  | 
 | ||
|  | # helper object to choose item/loc | ||
|  | class Choice(object): | ||
|  |     def __init__(self, restrictions): | ||
|  |         self.restrictions = restrictions | ||
|  |         self.settings = restrictions.settings | ||
|  |         self.log = utils.log.get("Choice") | ||
|  | 
 | ||
|  |     # args are return from RandoServices.getPossiblePlacements | ||
|  |     # return itemLoc dict, or None if no possible choice | ||
|  |     def chooseItemLoc(self, itemLocDict, isProg): | ||
|  |         return None | ||
|  | 
 | ||
|  |     def getItemList(self, itemLocDict): | ||
|  |         return sorted([item for item in itemLocDict.keys()], key=lambda item: item.Type) | ||
|  | 
 | ||
|  |     def getLocList(self, itemLocDict, item): | ||
|  |         return sorted(itemLocDict[item], key=lambda loc: loc.Name) | ||
|  | 
 | ||
|  | # simple random choice, that chooses an item first, then a locatio to put it in | ||
|  | class ItemThenLocChoice(Choice): | ||
|  |     def __init__(self, restrictions): | ||
|  |         super(ItemThenLocChoice, self).__init__(restrictions) | ||
|  | 
 | ||
|  |     def chooseItemLoc(self, itemLocDict, isProg): | ||
|  |         itemList = self.getItemList(itemLocDict) | ||
|  |         item = self.chooseItem(itemList, isProg) | ||
|  |         if item is None: | ||
|  |             return None | ||
|  |         locList = self.getLocList(itemLocDict, item) | ||
|  |         loc = self.chooseLocation(locList, item, isProg) | ||
|  |         if loc is None: | ||
|  |             return None | ||
|  |         return ItemLocation(item, loc) | ||
|  | 
 | ||
|  |     def chooseItem(self, itemList, isProg): | ||
|  |         if len(itemList) == 0: | ||
|  |             return None | ||
|  |         if isProg: | ||
|  |             return self.chooseItemProg(itemList) | ||
|  |         else: | ||
|  |             return self.chooseItemRandom(itemList) | ||
|  | 
 | ||
|  |     def chooseItemProg(self, itemList): | ||
|  |         return self.chooseItemRandom(itemList) | ||
|  | 
 | ||
|  |     def chooseItemRandom(self, itemList): | ||
|  |         return random.choice(itemList) | ||
|  | 
 | ||
|  |     def chooseLocation(self, locList, item, isProg): | ||
|  |         if len(locList) == 0: | ||
|  |             return None | ||
|  |         if isProg: | ||
|  |             return self.chooseLocationProg(locList, item) | ||
|  |         else: | ||
|  |             return self.chooseLocationRandom(locList) | ||
|  | 
 | ||
|  |     def chooseLocationProg(self, locList, item): | ||
|  |         return self.chooseLocationRandom(locList) | ||
|  | 
 | ||
|  |     def chooseLocationRandom(self, locList): | ||
|  |         return random.choice(locList) | ||
|  | 
 | ||
|  | # Choice specialization for prog speed based filler | ||
|  | class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): | ||
|  |     def __init__(self, restrictions, progSpeedParams, distanceProp, services): | ||
|  |         super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions) | ||
|  |         self.progSpeedParams = progSpeedParams | ||
|  |         self.distanceProp = distanceProp | ||
|  |         self.services = services | ||
|  |         self.chooseItemFuncs = { | ||
|  |             'Random' : self.chooseItemRandom, | ||
|  |             'MinProgression' : self.chooseItemMinProgression, | ||
|  |             'MaxProgression' : self.chooseItemMaxProgression | ||
|  |         } | ||
|  |         self.chooseLocFuncs = { | ||
|  |             'Random' : self.chooseLocationRandom, | ||
|  |             'MinDiff' : self.chooseLocationMinDiff, | ||
|  |             'MaxDiff' : self.chooseLocationMaxDiff | ||
|  |         } | ||
|  | 
 | ||
|  |     def currentLocations(self, item=None): | ||
|  |         return self.services.currentLocations(self.ap, self.container, item=item) | ||
|  | 
 | ||
|  |     def processLateDoors(self, itemLocDict, ap, container): | ||
|  |         doorBeams = self.restrictions.mandatoryBeams | ||
|  |         def canOpenExtendedDoors(item): | ||
|  |             return item.Category == 'Ammo' or item.Type in doorBeams | ||
|  |         # exclude door items from itemLocDict | ||
|  |         noDoorsLocDict = {item:locList for item,locList in itemLocDict.items() if not canOpenExtendedDoors(item) or container.sm.haveItem(item.Type)} | ||
|  |         if len(noDoorsLocDict) > 0: | ||
|  |             self.log.debug('processLateDoors. no doors') | ||
|  |             itemLocDict.clear() | ||
|  |             itemLocDict.update(noDoorsLocDict) | ||
|  | 
 | ||
|  |     def chooseItemLoc(self, itemLocDict, isProg, progressionItemLocs, ap, container): | ||
|  |         # if late morph, redo the late morph check if morph is the | ||
|  |         # only possibility since we can rollback | ||
|  |         canRollback = len(container.currentItems) > 0 | ||
|  |         if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1: | ||
|  |             item, locList = list(itemLocDict.items())[0] | ||
|  |             if item.Type == 'Morph': | ||
|  |                 morphLocs = self.restrictions.lateMorphCheck(container, locList) | ||
|  |                 if morphLocs is not None: | ||
|  |                     itemLocDict[item] = morphLocs | ||
|  |                 else: | ||
|  |                     return None | ||
|  |         # if a boss is available, choose it right away | ||
|  |         for item,locs in itemLocDict.items(): | ||
|  |             if item.Category == 'Boss': | ||
|  |                 assert len(locs) == 1 and locs[0].Name == item.Name | ||
|  |                 return ItemLocation(item, locs[0]) | ||
|  |         # late doors check for random door colors | ||
|  |         if self.restrictions.isLateDoors() and random.random() < self.lateDoorsProb: | ||
|  |             self.processLateDoors(itemLocDict, ap, container) | ||
|  |         self.progressionItemLocs = progressionItemLocs | ||
|  |         self.ap = ap | ||
|  |         self.container = container | ||
|  |         return super(ItemThenLocChoiceProgSpeed, self).chooseItemLoc(itemLocDict, isProg) | ||
|  | 
 | ||
|  |     def determineParameters(self, progSpeed=None, progDiff=None): | ||
|  |         self.chooseLocRanges = getRangeDict(self.getChooseLocs(progDiff)) | ||
|  |         self.chooseItemRanges = getRangeDict(self.getChooseItems(progSpeed)) | ||
|  |         self.spreadProb = self.progSpeedParams.getSpreadFactor(progSpeed) | ||
|  |         self.lateDoorsProb = self.progSpeedParams.getLateDoorsProb(progSpeed) | ||
|  | 
 | ||
|  |     def getChooseLocs(self, progDiff=None): | ||
|  |         if progDiff is None: | ||
|  |             progDiff = self.settings.progDiff | ||
|  |         return self.progSpeedParams.getChooseLocDict(progDiff) | ||
|  | 
 | ||
|  |     def getChooseItems(self, progSpeed): | ||
|  |         if progSpeed is None: | ||
|  |             progSpeed = self.settings.progSpeed | ||
|  |         return self.progSpeedParams.getChooseItemDict(progSpeed) | ||
|  | 
 | ||
|  |     def chooseItemProg(self, itemList): | ||
|  |         ret = self.getChooseFunc(self.chooseItemRanges, self.chooseItemFuncs)(itemList) | ||
|  |         self.log.debug('chooseItemProg. ret='+ret.Type) | ||
|  |         return ret | ||
|  | 
 | ||
|  |     def chooseLocationProg(self, locs, item): | ||
|  |         locs = self.getLocsSpreadProgression(locs) | ||
|  |         random.shuffle(locs) | ||
|  |         ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs) | ||
|  |         self.log.debug('chooseLocationProg. ret='+ret.Name) | ||
|  |         return ret | ||
|  | 
 | ||
|  |     # get choose function from a weighted dict | ||
|  |     def getChooseFunc(self, rangeDict, funcDict): | ||
|  |         v = chooseFromRange(rangeDict) | ||
|  | 
 | ||
|  |         return funcDict[v] | ||
|  | 
 | ||
|  |     def chooseItemMinProgression(self, items): | ||
|  |         minNewLocs = 1000 | ||
|  |         ret = None | ||
|  | 
 | ||
|  |         for item in items: | ||
|  |             newLocs = len(self.currentLocations(item)) | ||
|  |             if newLocs < minNewLocs: | ||
|  |                 minNewLocs = newLocs | ||
|  |                 ret = item | ||
|  |         return ret | ||
|  | 
 | ||
|  |     def chooseItemMaxProgression(self, items): | ||
|  |         maxNewLocs = 0 | ||
|  |         ret = None | ||
|  | 
 | ||
|  |         for item in items: | ||
|  |             newLocs = len(self.currentLocations(item)) | ||
|  |             if newLocs > maxNewLocs: | ||
|  |                 maxNewLocs = newLocs | ||
|  |                 ret = item | ||
|  |         return ret | ||
|  | 
 | ||
|  | 
 | ||
|  |     def chooseLocationMaxDiff(self, availableLocations): | ||
|  |         self.log.debug("MAX") | ||
|  |         self.log.debug("chooseLocationMaxDiff: {}".format([(l.Name, l.difficulty) for l in availableLocations])) | ||
|  |         return max(availableLocations, key=lambda loc:loc.difficulty.difficulty) | ||
|  | 
 | ||
|  |     def chooseLocationMinDiff(self, availableLocations): | ||
|  |         self.log.debug("MIN") | ||
|  |         self.log.debug("chooseLocationMinDiff: {}".format([(l.Name, l.difficulty) for l in availableLocations])) | ||
|  |         return min(availableLocations, key=lambda loc:loc.difficulty.difficulty) | ||
|  | 
 | ||
|  |     def areaDistance(self, loc, otherLocs): | ||
|  |         areas = [getattr(l, self.distanceProp) for l in otherLocs] | ||
|  |         cnt = areas.count(getattr(loc, self.distanceProp)) | ||
|  |         d = None | ||
|  |         if cnt == 0: | ||
|  |             d = 2 | ||
|  |         else: | ||
|  |             d = 1.0/cnt | ||
|  |         return d | ||
|  | 
 | ||
|  |     def getLocsSpreadProgression(self, availableLocations): | ||
|  |         split = self.restrictions.split | ||
|  |         cond = lambda item: ((split == 'Full' and item.Class == 'Major') or split == item.Class) and item.Category != "Energy" | ||
|  |         progLocs = [il.Location for il in self.progressionItemLocs if cond(il.Item)] | ||
|  |         distances = [self.areaDistance(loc, progLocs) for loc in availableLocations] | ||
|  |         maxDist = max(distances) | ||
|  |         locs = [] | ||
|  |         for i in range(len(availableLocations)): | ||
|  |             loc = availableLocations[i] | ||
|  |             d = distances[i] | ||
|  |             if d == maxDist or random.random() >= self.spreadProb: | ||
|  |                 locs.append(loc) | ||
|  |         return locs |