Beispiel #1
0
 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'))))
Beispiel #2
0
 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)
Beispiel #4
0
 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
Beispiel #6
0
 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")))
Beispiel #7
0
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