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
 |