def fillRestrictedLocations(self): def getPred(itemType, loc): return lambda item: (itemType is None or item.Type == itemType) and self.restrictions.canPlaceAtLocation(item, loc, self.container) locs = self.restrictedLocs self.log.debug("fillRestrictedLocations. locs="+getLocListStr(locs)) for loc in locs: itemLocation = ItemLocation(None, loc) if loc.BossItemType is not None: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred(loc.BossItemType, loc)) elif self.container.hasItemInPool(getPred('Nothing', loc)): itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Nothing', loc)) elif self.container.hasItemInPool(getPred('NoEnergy', loc)): itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('NoEnergy', loc)) elif self.container.countItems(getPred('Missile', loc)) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Missile', loc)) elif self.container.countItems(getPred('Super', loc)) > 2: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Super', loc)) elif self.container.countItems(getPred('PowerBomb', loc)) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('PowerBomb', loc)) elif self.container.countItems(getPred('Reserve', loc)) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Reserve', loc)) elif self.container.countItems(getPred('ETank', loc)) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('ETank', loc)) else: raise RuntimeError("Cannot fill restricted locations") self.log.debug("Fill: {}/{} at {}".format(itemLocation.Item.Type, itemLocation.Item.Class, itemLocation.Location.Name)) self.container.collect(itemLocation, False)
def collectPair(self, pairItemLocDict): # choose a pair of items which create progression keys = list(pairItemLocDict.keys()) key = random.choice(keys) # collect them availableLocs = pairItemLocDict[key] self.collect(ItemLocation(key[0], availableLocs[0][0])) self.collect(ItemLocation(key[1], availableLocs[1][0]))
def fill(locs, getPred): self.log.debug("fillRestrictedLocations. locs=" + getLocListStr(locs)) for loc in locs: loc.restricted = True itemLocation = ItemLocation(None, loc) if self.container.hasItemInPool(getPred('Nothing')): itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Nothing')) elif self.container.hasItemInPool(getPred('NoEnergy')): itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('NoEnergy')) elif self.container.countItems(getPred('Missile')) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Missile')) elif self.container.countItems(getPred('Super')) > 2: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Super')) elif self.container.countItems(getPred('PowerBomb')) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('PowerBomb')) elif self.container.countItems(getPred('Reserve')) > 1: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('Reserve')) elif self.container.countItems(getPred('ETank')) > 3: itemLocation.Item = self.container.getNextItemInPoolMatching( getPred('ETank')) else: raise RuntimeError("Cannot fill restricted locations") self.log.debug("Fill: {} at {}".format( itemLocation.Item.Type, itemLocation.Location.Name)) self.container.collect(itemLocation, False)
def processEarlyMorph(self, ap, container, comebackCheck, itemLocDict, curLocs): morph = container.getNextItemInPool('Morph') if morph is not None: self.log.debug("processEarlyMorph. morph not placed yet") morphLocItem = next( (item for item in itemLocDict if item.Type == morph.Type), None) if morphLocItem is not None: morphLocs = itemLocDict[morphLocItem] itemLocDict.clear() itemLocDict[morphLocItem] = morphLocs elif len(curLocs) >= 2: self.log.debug( "processEarlyMorph. early morph placement check") # we have to place morph early, it's still not placed, and not detected as placeable # let's see if we can place it anyway in the context of a combo morphLocs = self.getPlacementLocs(ap, container, comebackCheck, morph, curLocs) if len(morphLocs) > 0: # copy our context to do some destructive checks containerCpy = copy.copy(container) # choose a morph item location in that context morphItemLoc = ItemLocation( morph, random.choice(containerCpy.extractLocs(morphLocs))) # acquire morph in new context and see if we can still open new locs newAP = self.collect(ap, containerCpy, morphItemLoc) (ild, poss) = self.getPossiblePlacements( newAP, containerCpy, comebackCheck) if poss: # it's possible, only offer morph as possibility itemLocDict.clear() itemLocDict[morph] = morphLocs
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 step(self): # here a step is not an item collection but a whole fill attempt date = time.process_time() while not self.container.isPoolEmpty() and date <= self.endDate: item = random.choice(self.container.itemPool) locs = self.getLocations(item) if not locs: self.log.debug( "FillerRandom: constraint collision during step {} for item {}" .format(self.nSteps, item.Type)) self.resetHelpingContainer() date = time.process_time() continue loc = random.choice(locs) itemLoc = ItemLocation(item, loc) self.container.collect(itemLoc, pickup=False) date = time.process_time() if date > self.endDate: return False # pool is exhausted, use mini solver to see if it is beatable if self.isBeatable(): sys.stdout.write('o') sys.stdout.flush() else: if self.diffSteps > 0 and self.settings.maxDiff < infinity: if self.nSteps < self.diffSteps: couldBeBeatable = self.isBeatable(maxDiff=infinity) if couldBeBeatable: difficulty = max([ il.Location.difficulty.difficulty for il in self.container.itemLocations ]) if self.beatableBackup is None or difficulty < self.beatableBackup[ 1]: self.beatableBackup = ( self.container.itemLocations, difficulty) elif self.beatableBackup is not None: self.container.itemLocations = self.beatableBackup[0] difficulty = self.beatableBackup[1] self.errorMsg += "Could not find a solution compatible with max difficulty. Estimated seed difficulty: " + diffValue2txt( difficulty) sys.stdout.write('O') sys.stdout.flush() return True else: return False # reset container to force a retry self.resetHelpingContainer() if (self.nSteps + 1) % 100 == 0: sys.stdout.write('x') sys.stdout.flush() # help speedrun filler self.getHelp() return True
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 collectAlreadyPlacedItemLocations(self, container): if not self.isPlandoRando(): return for locName,itemType in self.plandoSettings["locsItems"].items(): if not any(loc.Name == locName for loc in container.unusedLocations): continue item = container.getNextItemInPool(itemType) assert item is not None, "Invalid plando item pool" location = container.getLocs(lambda loc: loc.Name == locName)[0] itemLoc = ItemLocation(item, location) container.collect(itemLoc, pickup=False)
def step(self): item = random.choice(self.container.itemPool) locs = [ loc for loc in self.container.unusedLocations if self.restrictions.canPlaceAtLocation(item, loc, self.container) ] loc = random.choice(locs) itemLoc = ItemLocation(item, loc) self.container.collect(itemLoc, pickup=False) sys.stdout.write('.') sys.stdout.flush() return True
def findStartupProgItemPair(self, ap, container): self.log.debug("findStartupProgItemPair") (itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck) assert not isProg items = list(itemLocDict.keys()) random.shuffle(items) for item in items: cont = copy.copy(container) loc = random.choice(itemLocDict[item]) itemLoc1 = ItemLocation(item, loc) self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1)) newAP = self.collect(ap, cont, itemLoc1) if self.cache is not None: self.cache.reset() (ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck) if isProg: item2 = random.choice(list(ild.keys())) itemLoc2 = ItemLocation(item2, random.choice(ild[item2])) self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2)) return (itemLoc1, itemLoc2) return None
def postProcessItemLocs(self, itemLocs, hide): # hide some items like in dessy's if hide == True: for itemLoc in itemLocs: item = itemLoc.Item loc = itemLoc.Location if (item.Category != "Nothing" and loc.CanHidden == True and loc.Visibility == 'Visible'): if bool(random.getrandbits(1)) == True: loc.Visibility = 'Hidden' # put nothing in unfilled locations filledLocNames = [il.Location.Name for il in itemLocs] unfilledLocs = [ loc for loc in Logic.locations if loc.Name not in filledLocNames ] nothing = ItemManager.getItem('Nothing') for loc in unfilledLocs: loc.restricted = True itemLocs.append(ItemLocation(nothing, loc, False))
def savePlando(self, lock, escapeTimer): # store filled locations addresses in the ROM for next creating session from rando.Items import ItemManager locsItems = {} itemLocs = [] for loc in self.visitedLocations: locsItems[loc.Name] = loc.itemName for loc in self.locations: if loc.Name in locsItems: itemLocs.append( ItemLocation(ItemManager.getItem(loc.itemName), loc)) else: # put nothing items in unused locations itemLocs.append( ItemLocation(ItemManager.getItem("Nothing"), loc)) # patch the ROM if lock == True: import random magic = random.randint(1, 0xffff) else: magic = None romPatcher = RomPatcher(magic=magic, plando=True) patches = [ 'credits_varia.ips', 'tracking.ips', "Escape_Animals_Disable" ] if DoorsManager.isRandom(): patches += RomPatcher.IPSPatches['DoorsColors'] patches.append("Enable_Backup_Saves") if magic != None: patches.insert(0, 'race_mode.ips') patches.append('race_mode_credits.ips') romPatcher.addIPSPatches(patches) plms = [] if self.areaRando == True or self.bossRando == True or self.escapeRando == True: doors = GraphUtils.getDoorConnections( AccessGraph(Logic.accessPoints, self.fillGraph()), self.areaRando, self.bossRando, self.escapeRando, False) romPatcher.writeDoorConnections(doors) if magic == None: doorsPtrs = GraphUtils.getAps2DoorsPtrs() romPatcher.writePlandoTransitions( self.curGraphTransitions, doorsPtrs, len(vanillaBossesTransitions) + len(vanillaTransitions)) if self.escapeRando == True and escapeTimer != None: # convert from '03:00' to number of seconds escapeTimer = int(escapeTimer[0:2]) * 60 + int( escapeTimer[3:5]) romPatcher.applyEscapeAttributes( { 'Timer': escapeTimer, 'Animals': None }, plms) # write plm table & random doors romPatcher.writePlmTable(plms, self.areaRando, self.bossRando, self.startAP) romPatcher.writeItemsLocs(itemLocs) romPatcher.writeItemsNumber() romPatcher.writeSpoiler(itemLocs) # plando is considered Full romPatcher.writeSplitLocs(itemLocs, "Full") romPatcher.writeMajorsSplit("Full") class FakeRandoSettings: def __init__(self): self.qty = {'energy': 'plando'} self.progSpeed = 'plando' self.progDiff = 'plando' self.restrictions = {'Suits': False, 'Morph': 'plando'} self.superFun = {} randoSettings = FakeRandoSettings() romPatcher.writeRandoSettings(randoSettings, itemLocs) if magic != None: romPatcher.writeMagic() else: romPatcher.writePlandoAddresses(self.visitedLocations) romPatcher.commitIPS() romPatcher.end() data = romPatcher.romFile.data preset = os.path.splitext(os.path.basename(self.presetFileName))[0] seedCode = 'FX' if self.bossRando == True: seedCode = 'B' + seedCode if DoorsManager.isRandom(): seedCode = 'D' + seedCode if self.areaRando == True: seedCode = 'A' + seedCode from time import gmtime, strftime fileName = 'VARIA_Plandomizer_{}{}_{}.sfc'.format( seedCode, strftime("%Y%m%d%H%M%S", gmtime()), preset) data["fileName"] = fileName # error msg in json to be displayed by the web site data["errorMsg"] = "" with open(self.outputFileName, 'w') as jsonFile: json.dump(data, jsonFile)
def getStartupProgItemsPairs(self, ap, container): self.log.debug("getStartupProgItemsPairs: kickstart") (itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck) # save container saveEmptyContainer = ContainerSoftBackup(container) # key is (item1, item2) pairItemLocDict = {} # keep only unique items in itemLocDict uniqItemLocDict = {} for item, locs in itemLocDict.items(): if item.Type in ['NoEnergy', 'Nothing']: continue if item.Type not in [it.Type for it in uniqItemLocDict.keys()]: uniqItemLocDict[item] = locs if not uniqItemLocDict: return None if self.cache: self.cache.reset() curLocsBefore = self.currentLocations(ap, container) if not curLocsBefore: return None self.log.debug("search for progression with a second item") for item1, locs1 in uniqItemLocDict.items(): # collect first item in first available location matching restrictions if self.cache: self.cache.reset() firstItemPlaced = False for loc in curLocsBefore: if self.restrictions.canPlaceAtLocation(item1, loc, container): self.log.debug("getStartupProgItemsPairs. firstItemPlaced") container.collect(ItemLocation(item1, loc)) firstItemPlaced = True break if not firstItemPlaced: saveEmptyContainer.restore(container) continue saveAfterFirst = ContainerSoftBackup(container) curLocsAfterFirst = self.currentLocations(ap, container) if not curLocsAfterFirst: saveEmptyContainer.restore(container) continue for item2, locs2 in uniqItemLocDict.items(): if item1.Type == item2.Type: continue if (item1, item2) in pairItemLocDict.keys() or ( item2, item1) in pairItemLocDict.keys(): continue # collect second item in first available location if self.cache: self.cache.reset() secondItemPlaced = False for loc in curLocsAfterFirst: if self.restrictions.canPlaceAtLocation( item2, loc, container): container.collect(ItemLocation(item2, loc)) secondItemPlaced = True break if not secondItemPlaced: saveAfterFirst.restore(container) continue curLocsAfterSecond = self.currentLocations(ap, container) if not curLocsAfterSecond: saveAfterFirst.restore(container) continue pairItemLocDict[(item1, item2)] = [ curLocsBefore, curLocsAfterFirst, curLocsAfterSecond ] saveAfterFirst.restore(container) saveEmptyContainer.restore(container) # check if a pair was found if len(pairItemLocDict) == 0: self.log.debug("no pair was found") return None else: if self.log.getEffectiveLevel() == logging.DEBUG: self.log.debug("pairItemLocDict:") for key, locs in pairItemLocDict.items(): self.log.debug( "{}->{}: {}".format(key[0].Type, key[1].Type, [l['Name'] for l in locs[2]])) return pairItemLocDict