def canPassCacatacAlley(self): sm = self.smbm return sm.wand( Bosses.bossDead(sm, 'Draygon'), sm.wor( sm.haveItem('Gravity'), sm.wand(sm.knowsGravLessLevel2(), sm.haveItem('HiJump'), sm.haveItem('SpaceJump'))))
def canPassSpongeBath(self): sm = self.smbm return sm.wand( Bosses.bossDead('Phantoon'), sm.wor( sm.wand(sm.canPassBombPassages(), sm.knowsSpongeBathBombJump()), sm.wand(sm.haveItem('HiJump'), sm.knowsSpongeBathHiJump()), sm.wor( sm.haveItem('Gravity'), sm.haveItem('SpaceJump'), sm.wand(sm.haveItem('SpeedBooster'), sm.knowsSpongeBathSpeed()), sm.canSpringBallJump())))
def escapeGraph(self): sm = self.smbm # setup smbm with item pool sm.resetItems() for boss in Bosses.bosses(): Bosses.beatBoss(boss) # Ice not usable because of hyper beam # remove energy to avoid hell runs sm.addItems([ item['Type'] for item in self.itemPool if item['Type'] != 'Ice' and item['Category'] != 'Energy' ]) path = None while path is None: (src, dst) = GraphUtils.createEscapeTransition() path = self.areaGraph.accessPath(sm, dst, 'Landing Site', self.difficultyTarget) # cleanup smbm sm.resetItems() Bosses.reset() # actually update graph self.areaGraph.addTransition(src, dst) # get timer value self.areaGraph.EscapeTimer = self.escapeTimer(path)
def getAccessibleAccessPoints(self, rootNode='Landing Site'): rootAp = self.accessPoints[rootNode] inBossChk = lambda ap: ap.Boss and ap.Name.endswith("In") allAreas = { dst.GraphArea for (src, dst) in self.InterAreaTransitions if not inBossChk(dst) and not dst.isLoop() } self.log.debug("allAreas=" + str(allAreas)) nonBossAPs = [ ap for ap in self.getAvailableAccessPoints(rootAp, None, 0) if ap.GraphArea in allAreas ] bossesAPs = [ self.accessPoints[boss + 'RoomIn'] for boss in Bosses.Golden4() ] + [self.accessPoints['Draygon Room Bottom']] return nonBossAPs + bossesAPs
def fullComebackCheck(self, container, ap, item, loc, comebackCheck): sm = container.sm tmpItems = [] # draygon special case: there are two locations, and we can # place one item, but we might need both the item and the boss # dead to get out if loc['SolveArea'] == "Draygon Boss" and Bosses.bossDead( sm, 'Draygon').bool == False: # temporary kill draygon tmpItems.append('Draygon') sm.addItems(tmpItems) ret = self.locPostAvailable( sm, loc, item.Type if item is not None else None) and not self.isSoftlockPossible( container, ap, item, loc, comebackCheck) for tmp in tmpItems: sm.removeItem(tmp) return ret
def canPassBowling(self): sm = self.smbm return sm.wand( Bosses.bossDead(sm, 'Phantoon'), sm.wor(sm.heatProof(), sm.energyReserveCountOk(1), sm.haveItem("SpaceJump"), sm.haveItem("Grapple")))
class SMBoolManager(object): items = [ 'ETank', 'Missile', 'Super', 'PowerBomb', 'Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Reserve', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack', 'Nothing', 'NoEnergy', 'MotherBrain', 'Hyper' ] + Bosses.Golden4() countItems = ['Missile', 'Super', 'PowerBomb', 'ETank', 'Reserve'] def __init__(self): self.smboolFalse = SMBool(False) Cache.reset() self.helpers = HelpersGraph(self) self.createFacadeFunctions() self.createKnowsFunctions() self.resetItems() def isEmpty(self): for item in self.items: if self.haveItem(item): return False for item in self.countItems: if self.itemCount(item) > 0: return False return True def getItems(self): # get a dict of collected items and how many (to be displayed on the solver spoiler) itemsDict = {} for item in self.items: itemsDict[item] = 1 if getattr(self, item) == True else 0 for item in self.countItems: itemsDict[item] = getattr(self, item + "Count") return itemsDict def eval(self, func, item=None): if item is not None: self.addItem(item) ret = func(self) if item is not None: self.removeItem(item) return ret def resetItems(self): # start without items for item in SMBoolManager.items: setattr(self, item, SMBool(False)) for item in SMBoolManager.countItems: setattr(self, item + 'Count', 0) Cache.reset() def addItem(self, item): # a new item is available setattr(self, item, SMBool(True, items=[item])) if self.isCountItem(item): setattr(self, item + 'Count', getattr(self, item + 'Count') + 1) Cache.reset() def addItems(self, items): if len(items) == 0: return for item in items: setattr(self, item, SMBool(True, items=[item])) if self.isCountItem(item): setattr(self, item + 'Count', getattr(self, item + 'Count') + 1) Cache.reset() def removeItem(self, item): # randomizer removed an item (or the item was added to test a post available) if self.isCountItem(item): count = getattr(self, item + 'Count') - 1 setattr(self, item + 'Count', count) if count == 0: setattr(self, item, SMBool(False)) else: setattr(self, item, SMBool(False)) Cache.reset() def createFacadeFunctions(self): for fun in dir(self.helpers): if fun != 'smbm' and fun[0:2] != '__': setattr(self, fun, getattr(self.helpers, fun)) def createKnowsFunctions(self): # for each knows we have a function knowsKnows (ex: knowsAlcatrazEscape()) which # take no parameter for knows in Knows.__dict__: if isKnows(knows): setattr(self, 'knows' + knows, lambda knows=knows: SMBool(Knows.__dict__[knows].bool, Knows.__dict__[knows]. difficulty, knows=[knows])) def isCountItem(self, item): return item in self.countItems def itemCount(self, item): # return integer return getattr(self, item + 'Count') def haveItem(self, item): return getattr(self, item) def wand(self, *args): if False in args: return self.smboolFalse else: return SMBool(True, sum([smb.difficulty for smb in args]), [know for smb in args for know in smb.knows], [item for smb in args for item in smb.items]) def wor(self, *args): if True in args: # return the smbool with the smallest difficulty among True smbools. return min(args) else: return self.smboolFalse # negates boolean part of the SMBool def wnot(self, a): return SMBool(not a.bool, a.difficulty) def itemCountOk(self, item, count, difficulty=0): if self.itemCount(item) >= count: if item in ['ETank', 'Reserve']: item = str(count) + '-' + item return SMBool(True, difficulty, items=[item]) else: return self.smboolFalse def energyReserveCountOk(self, count, difficulty=0): if self.energyReserveCount() >= count: nEtank = self.itemCount('ETank') if nEtank > count: nEtank = int(count) items = str(nEtank) + '-ETank' nReserve = self.itemCount('Reserve') if nEtank < count: nReserve = int(count) - nEtank items += ' - ' + str(nReserve) + '-Reserve' return SMBool(True, difficulty, items=[items]) else: return self.smboolFalse
def checkPool(self, forbidden=None): self.log.debug("checkPool. forbidden=" + str(forbidden) + ", self.forbiddenItems=" + str(self.forbiddenItems)) if self.graphSettings.minimizerN is None and len( self.allLocations) > len(self.locations): # invalid graph with looped areas return False ret = True if forbidden is not None: pool = self.getItemPool(forbidden) else: pool = self.getItemPool() # get restricted locs totalAvailLocs = [] comeBack = {} try: container = ItemLocContainer(self.sm, pool, self.locations) except AssertionError as e: # invalid graph altogether self.log.debug( "checkPool: AssertionError when creating ItemLocContainer: {}". format(e)) return False # restrict item pool in chozo: game should be finishable with chozo items only contPool = [] if self.restrictions.isChozo(): container.restrictItemPool(isChozoItem) missile = container.getNextItemInPool('Missile') if missile is not None: # add missile (if zeb skip not known) contPool.append(missile) contPool += [item for item in pool if item in container.itemPool] # give us everything and beat every boss to see what we can access self.disableBossChecks() self.sm.resetItems() self.sm.addItems([item.Type for item in contPool]) # will add bosses as well poolDict = container.getPoolDict() self.log.debug('pool={}'.format( sorted([(t, len(poolDict[t])) for t in poolDict]))) refAP = 'Landing Site' locs = self.services.currentLocations(self.startAP, container, post=True) self.areaGraph.useCache(True) for loc in locs: ap = loc['accessPoint'] if ap not in comeBack: # we chose Landing Site because other start APs might not have comeback transitions # possible start AP issues are handled in checkStart comeBack[ap] = self.areaGraph.canAccess( self.sm, ap, 'Landing Site', self.settings.maxDiff) if comeBack[ap]: totalAvailLocs.append(loc) self.areaGraph.useCache(False) self.lastRestricted = [ loc for loc in self.locations if loc not in totalAvailLocs ] self.log.debug("restricted=" + str([loc['Name'] for loc in self.lastRestricted])) # check if all inter-area APs can reach each other interAPs = [ ap for ap in self.areaGraph.getAccessibleAccessPoints(self.startAP) if not ap.isInternal() and not ap.isLoop() ] for startAp in interAPs: availAccessPoints = self.areaGraph.getAvailableAccessPoints( startAp, self.sm, self.settings.maxDiff) for ap in interAPs: if not ap in availAccessPoints: ret = False self.log.debug("unavail AP: " + ap.Name + ", from " + startAp.Name) # cleanup self.sm.resetItems() self.restoreBossChecks() # check if we can reach/beat all bosses if ret: for loc in self.lastRestricted: if loc['Name'] in self.bossesLocs: ret = False self.log.debug("unavail Boss: " + loc['Name']) if ret: # revive bosses self.sm.addItems([ item.Type for item in contPool if item.Category != 'Boss' ]) maxDiff = self.settings.maxDiff # see if phantoon doesn't block himself, and if we can reach draygon if she's alive ret = self.areaGraph.canAccess(self.sm, self.startAP, 'PhantoonRoomIn', maxDiff)\ and self.areaGraph.canAccess(self.sm, self.startAP, 'DraygonRoomIn', maxDiff) if ret: # see if we can beat bosses with this equipment (infinity as max diff for a "onlyBossesLeft" type check beatableBosses = sorted([ loc['Name'] for loc in self.services.currentLocations( self.startAP, container, diff=infinity) if "Boss" in loc['Class'] ]) self.log.debug("checkPool. beatableBosses=" + str(beatableBosses)) ret = beatableBosses == Bosses.Golden4() if ret: # check that we can then kill mother brain self.sm.addItems(Bosses.Golden4()) beatableMotherBrain = [ loc['Name'] for loc in self.services.currentLocations( self.startAP, container, diff=infinity) if loc['Name'] == 'Mother Brain' ] ret = len(beatableMotherBrain) > 0 self.log.debug( "checkPool. beatable Mother Brain={}".format(ret)) else: self.log.debug('checkPool. locked by Phantoon or Draygon') self.log.debug('checkPool. boss access sanity check: ' + str(ret)) if self.restrictions.isChozo(): # last check for chozo locations: don't put more restricted chozo locations than removed chozo items # (we cannot rely on removing ammo/energy in fillRestrictedLocations since it is already the bare minimum in chozo pool) # FIXME something to do there for ultra sparse, it gives us up to 3 more spots for nothing items restrictedLocs = self.restrictedLocs + [ loc for loc in self.lastRestricted if loc not in self.restrictedLocs ] nRestrictedChozo = sum(1 for loc in restrictedLocs if 'Chozo' in loc['Class']) nNothingChozo = sum( 1 for item in pool if 'Chozo' in item.Class and item.Category == 'Nothing') ret &= nRestrictedChozo <= nNothingChozo self.log.debug('checkPool. nRestrictedChozo=' + str(nRestrictedChozo) + ', nNothingChozo=' + str(nNothingChozo)) self.log.debug('checkPool. result: ' + str(ret)) return ret