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 getG4EscapeAccessPoints(n):
    return (n, [Bosses.accessPoints[boss] for boss in Bosses.Golden4()])
Esempio n. 3
0
    def checkPool(self, forbidden=None):
        self.log.debug("checkPool. forbidden=" + str(forbidden) +
                       ", self.forbiddenItems=" + str(self.forbiddenItems))
        if not self.graphSettings.isMinimizer(
        ) and not self.settings.isPlandoRando() and len(
                self.allLocations) > len(self.locations):
            # invalid graph with looped areas
            self.log.debug(
                "checkPool: not all areas are connected, but minimizer param is off / not a plando rando"
            )
            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])))
        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 Golden Four because it is always there.
                # Start APs might not have comeback transitions
                # possible start AP issues are handled in checkStart
                comeBack[ap] = self.areaGraph.canAccess(
                    self.sm, ap, 'Golden Four', 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:
                    self.log.debug(
                        "checkPool: ap {} non accessible from {}".format(
                            ap.Name, startAp.Name))
                    ret = False
        if not ret:
            self.log.debug("checkPool. inter-area APs check failed")
        # 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 loc.isBoss()
                    ])
                    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 loc.isChozo())
            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
    def computeDifficulty(self):
        # loop on the available locations depending on the collected items.
        # before getting a new item, loop on all of them and get their difficulty,
        # the next collected item is the one with the smallest difficulty,
        # if equality between major and minor, take major first.

        # remove mother brain location (there items pickup conditions on top of going to mother brain location)
        mbLoc = self.getLoc('Mother Brain')
        self.locations.remove(mbLoc)

        if self.majorsSplit == 'Major':
            self.majorLocations = [
                loc for loc in self.locations if loc.isMajor() or loc.isBoss()
            ]
            self.minorLocations = [
                loc for loc in self.locations if loc.isMinor()
            ]
        elif self.majorsSplit == 'Chozo':
            self.majorLocations = [
                loc for loc in self.locations if loc.isChozo() or loc.isBoss()
            ]
            self.minorLocations = [
                loc for loc in self.locations
                if not loc.isChozo() and not loc.isBoss()
            ]
        else:
            # Full
            self.majorLocations = self.locations[:]  # copy
            self.minorLocations = self.majorLocations

        self.visitedLocations = []
        self.collectedItems = []

        self.log.debug(
            "{}: available major: {}, available minor: {}, visited: {}".format(
                Conf.itemsPickup, len(self.majorLocations),
                len(self.minorLocations), len(self.visitedLocations)))

        isEndPossible = False
        endDifficulty = mania
        diffThreshold = self.getDiffThreshold()
        while True:
            # actual while condition
            hasEnoughMinors = self.pickup.enoughMinors(self.smbm,
                                                       self.minorLocations)
            hasEnoughMajors = self.pickup.enoughMajors(self.smbm,
                                                       self.majorLocations)
            hasEnoughItems = hasEnoughMajors and hasEnoughMinors
            canEndGame = self.canEndGame()
            (isEndPossible, endDifficulty) = (canEndGame.bool,
                                              canEndGame.difficulty)
            if isEndPossible and hasEnoughItems and endDifficulty <= diffThreshold:
                if self.checkMB(mbLoc):
                    self.log.debug("END")
                    break
                else:
                    self.log.debug("canEnd but MB loc not accessible")

            # check time limit
            if self.runtimeLimit_s > 0:
                if time.process_time() - self.startTime > self.runtimeLimit_s:
                    self.log.debug("time limit exceeded ({})".format(
                        self.runtimeLimit_s))
                    return (-1, False)

            self.log.debug("Current AP/Area: {}/{}".format(
                self.lastAP, self.lastArea))

            # compute the difficulty of all the locations
            self.computeLocationsDifficulty(self.majorLocations)
            if self.majorsSplit != 'Full':
                self.computeLocationsDifficulty(self.minorLocations,
                                                phase="minor")

            # keep only the available locations
            majorsAvailable = [
                loc for loc in self.majorLocations
                if loc.difficulty is not None and loc.difficulty.bool == True
            ]
            minorsAvailable = [
                loc for loc in self.minorLocations
                if loc.difficulty is not None and loc.difficulty.bool == True
            ]

            if self.majorsSplit == 'Full':
                locs = majorsAvailable
            else:
                locs = majorsAvailable + minorsAvailable

            self.nbAvailLocs.append(len(locs))

            # check if we're stuck
            if len(majorsAvailable) == 0 and len(minorsAvailable) == 0:
                if not isEndPossible:
                    self.log.debug("STUCK MAJORS and MINORS")
                    if self.comeBack.rewind(len(self.collectedItems)) == True:
                        continue
                    else:
                        # we're really stucked
                        self.log.debug("STUCK CAN'T REWIND")
                        break
                else:
                    self.log.debug("HARD END 2")
                    self.checkMB(mbLoc)
                    break

            # handle no comeback locations
            rewindRequired = self.comeBack.handleNoComeBack(
                locs, len(self.collectedItems))
            if rewindRequired == True:
                if self.comeBack.rewind(len(self.collectedItems)) == True:
                    continue
                else:
                    # we're really stucked
                    self.log.debug("STUCK CAN'T REWIND")
                    break

            # sort them on difficulty and proximity
            self.log.debug("getAvailableItemsList majors")
            majorsAvailable = self.getAvailableItemsList(
                majorsAvailable, diffThreshold)
            if self.majorsSplit == 'Full':
                minorsAvailable = majorsAvailable
            else:
                self.log.debug("getAvailableItemsList minors")
                minorsAvailable = self.getAvailableItemsList(
                    minorsAvailable, diffThreshold)

            # choose one to pick up
            self.nextDecision(majorsAvailable, minorsAvailable,
                              hasEnoughMinors, diffThreshold)

            self.comeBack.cleanNoComeBack(locs)

        # compute difficulty value
        (difficulty, itemsOk) = self.computeDifficultyValue()

        if self.log.getEffectiveLevel() == logging.DEBUG:
            self.log.debug("difficulty={}".format(difficulty))
            self.log.debug("itemsOk={}".format(itemsOk))
            self.log.debug(
                "{}: remaining major: {}, remaining minor: {}, visited: {}".
                format(Conf.itemsPickup, len(self.majorLocations),
                       len(self.minorLocations), len(self.visitedLocations)))

            self.log.debug("remaining majors:")
            for loc in self.majorLocations:
                self.log.debug("{} ({})".format(loc.Name, loc.itemName))

            self.log.debug("bosses: {}".format([
                (boss, Bosses.bossDead(self.smbm, boss))
                for boss in Bosses.Golden4()
            ]))

        return (difficulty, itemsOk)
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._items = {}
        self._counts = {}

        # cache related
        self.cacheKey = 0
        self.computeItemsPositions()
        Cache.reset()

        self.helpers = Logic.HelpersGraph(self)
        self.doorsManager = DoorsManager()
        self.createFacadeFunctions()
        self.createKnowsFunctions()
        self.resetItems()

    def computeItemsPositions(self):
        # compute index in cache key for each items
        self.itemsPositions = {}
        maxBitsForCountItem = 7  # 128 values with 7 bits
        for (i, item) in enumerate(self.countItems):
            pos = i * maxBitsForCountItem
            bitMask = (2 << (maxBitsForCountItem - 1)) - 1
            bitMask = bitMask << pos
            self.itemsPositions[item] = (pos, bitMask)
        for (i, item) in enumerate(self.items,
                                   (i + 1) * maxBitsForCountItem + 1):
            if item in self.countItems:
                continue
            self.itemsPositions[item] = (i, 1 << i)

    def computeNewCacheKey(self, item, value):
        # generate an unique integer for each items combinations which is use as key in the cache.
        if item in ['Nothing', 'NoEnergy']:
            return
        (pos, bitMask) = self.itemsPositions[item]
        #        print("--------------------- {} {} ----------------------------".format(item, value))
        #        print("old:  "+format(self.cacheKey, '#067b'))
        self.cacheKey = (self.cacheKey & (~bitMask)) | (value << pos)


#        print("new:  "+format(self.cacheKey, '#067b'))
#        self.printItemsInKey(self.cacheKey)

    def printItemsInKey(self, key):
        # for debug purpose
        print("key:  " + format(key, '#067b'))
        msg = ""
        for (item, (pos, bitMask)) in self.itemsPositions.items():
            value = (key & bitMask) >> pos
            if value != 0:
                msg += " {}: {}".format(item, value)
        print("items:{}".format(msg))

    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 self._items[item] == True else 0
        for item in self.countItems:
            itemsDict[item] = self._counts[item]
        return itemsDict

    def withItem(self, item, func):
        self.addItem(item)
        ret = func(self)
        self.removeItem(item)
        return ret

    def resetItems(self):
        self._items = {item: smboolFalse for item in self.items}
        self._counts = {item: 0 for item in self.countItems}

        self.cacheKey = 0
        Cache.update(self.cacheKey)

    def addItem(self, item):
        # a new item is available
        self._items[item] = SMBool(True, items=[item])
        if self.isCountItem(item):
            count = self._counts[item] + 1
            self._counts[item] = count
            self.computeNewCacheKey(item, count)
        else:
            self.computeNewCacheKey(item, 1)

        Cache.update(self.cacheKey)

    def addItems(self, items):
        if len(items) == 0:
            return
        for item in items:
            self._items[item] = SMBool(True, items=[item])
            if self.isCountItem(item):
                count = self._counts[item] + 1
                self._counts[item] = count
                self.computeNewCacheKey(item, count)
            else:
                self.computeNewCacheKey(item, 1)

        Cache.update(self.cacheKey)

    def removeItem(self, item):
        # randomizer removed an item (or the item was added to test a post available)
        if self.isCountItem(item):
            count = self._counts[item] - 1
            self._counts[item] = count
            if count == 0:
                self._items[item] = smboolFalse
            self.computeNewCacheKey(item, count)
        else:
            self._items[item] = smboolFalse
            self.computeNewCacheKey(item, 0)

        Cache.update(self.cacheKey)

    def createFacadeFunctions(self):
        for fun in dir(self.helpers):
            if fun != 'smbm' and fun[0:2] != '__':
                setattr(self, fun, getattr(self.helpers, fun))

    def traverse(self, doorName):
        return self.doorsManager.traverse(self, doorName)

    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 self._counts[item]

    def haveItem(self, item):
        return self._items[item]

    wand = staticmethod(SMBool.wand)
    wandmax = staticmethod(SMBool.wandmax)
    wor = staticmethod(SMBool.wor)
    wnot = staticmethod(SMBool.wnot)

    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 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 smboolFalse
Esempio n. 6
0
    def computeDifficulty(self):
        # loop on the available locations depending on the collected items.
        # before getting a new item, loop on all of them and get their difficulty,
        # the next collected item is the one with the smallest difficulty,
        # if equality between major and minor, take major first.

        # remove mother brain location (there items pickup conditions on top of going to mother brain location)
        mbLoc = self.getLoc('Mother Brain')
        self.locations.remove(mbLoc)

        if self.majorsSplit == 'Major':
            self.majorLocations = [
                loc for loc in self.locations if loc.isMajor() or loc.isBoss()
            ]
            self.minorLocations = [
                loc for loc in self.locations if loc.isMinor()
            ]
        elif self.majorsSplit == 'Chozo':
            self.majorLocations = [
                loc for loc in self.locations if loc.isChozo() or loc.isBoss()
            ]
            self.minorLocations = [
                loc for loc in self.locations
                if not loc.isChozo() and not loc.isBoss()
            ]
        elif self.majorsSplit == 'Scavenger':
            self.majorLocations = [
                loc for loc in self.locations
                if loc.isScavenger() or loc.isBoss()
            ]
            self.minorLocations = [
                loc for loc in self.locations
                if not loc.isScavenger() and not loc.isBoss()
            ]
        else:
            # Full
            self.majorLocations = self.locations[:]  # copy
            self.minorLocations = self.majorLocations

        self.visitedLocations = []
        self.collectedItems = []

        self.log.debug(
            "{}: available major: {}, available minor: {}, visited: {}".format(
                Conf.itemsPickup, len(self.majorLocations),
                len(self.minorLocations), len(self.visitedLocations)))

        isEndPossible = False
        endDifficulty = mania
        diffThreshold = self.getDiffThreshold()
        self.motherBrainKilled = False
        self.motherBrainCouldBeKilled = False
        while True:
            # actual while condition
            hasEnoughMinors = self.pickup.enoughMinors(self.smbm,
                                                       self.minorLocations)
            hasEnoughMajors = self.pickup.enoughMajors(self.smbm,
                                                       self.majorLocations)
            hasEnoughItems = hasEnoughMajors and hasEnoughMinors
            canEndGame = self.canEndGame()
            (isEndPossible, endDifficulty) = (canEndGame.bool,
                                              canEndGame.difficulty)
            if isEndPossible and hasEnoughItems:
                if not self.objectives.tourianRequired:
                    if self.checkEscape():
                        self.log.debug(
                            "checkMB: disabled, can escape to gunship, END")
                        break
                    else:
                        self.log.debug(
                            "checkMB: disabled, can't escape to gunship")
                else:
                    if endDifficulty <= diffThreshold:
                        if self.checkMB(mbLoc):
                            self.log.debug(
                                "checkMB: all end game checks are ok, END")
                            break
                        else:
                            self.log.debug(
                                "checkMB: canEnd but MB loc not accessible")
                    else:
                        if not self.motherBrainCouldBeKilled:
                            self.motherBrainCouldBeKilled = self.checkMB(
                                mbLoc, justCheck=True)
                        self.log.debug(
                            "checkMB: end checks ok except MB difficulty, MB could be killed: {}"
                            .format(self.motherBrainCouldBeKilled))

            # check time limit
            if self.runtimeLimit_s > 0:
                if time.process_time() - self.startTime > self.runtimeLimit_s:
                    self.log.debug("time limit exceeded ({})".format(
                        self.runtimeLimit_s))
                    return (-1, False)

            self.log.debug("Current AP/Area: {}/{}".format(
                self.lastAP, self.lastArea))

            # compute the difficulty of all the locations
            self.computeLocationsDifficulty(self.majorLocations)
            if self.majorsSplit != 'Full':
                self.computeLocationsDifficulty(self.minorLocations,
                                                phase="minor")

            # keep only the available locations
            majorsAvailable = [
                loc for loc in self.majorLocations
                if loc.difficulty is not None and loc.difficulty.bool == True
            ]
            minorsAvailable = [
                loc for loc in self.minorLocations
                if loc.difficulty is not None and loc.difficulty.bool == True
            ]

            self.nbAvailLocs.append(
                len(self.getAllLocs(majorsAvailable, minorsAvailable)))

            # remove next scavenger locs before checking if we're stuck
            if self.majorsSplit == 'Scavenger':
                majorsAvailable = self.filterScavengerLocs(majorsAvailable)

            # check if we're stuck
            if len(majorsAvailable) == 0 and len(minorsAvailable) == 0:
                if not isEndPossible:
                    self.log.debug("STUCK MAJORS and MINORS")
                    if self.comeBack.rewind(len(self.collectedItems)) == True:
                        continue
                    else:
                        # we're really stucked
                        self.log.debug("STUCK CAN'T REWIND")
                        break
                else:
                    self.log.debug("HARD END 2")
                    if self.checkMB(mbLoc):
                        self.log.debug("all end game checks are ok, END")
                        break
                    else:
                        self.log.debug(
                            "We're stucked somewhere and can't reach mother brain"
                        )
                        # check if we were able to access MB and kill it.
                        # we do it before rollbacks to avoid endless rollbacks.
                        if self.motherBrainCouldBeKilled:
                            self.log.debug(
                                "we're stucked but we could have killed MB before"
                            )
                            # add MB loc for the spoiler log, remove its path as it's not the correct one
                            # from when the loc was accessible
                            mbLoc.path = [getAccessPoint('Golden Four')]
                            self.visitedLocations.append(mbLoc)
                            self.motherBrainKilled = True
                            break
                        else:
                            # we're really stucked, try to rollback
                            if self.comeBack.rewind(len(
                                    self.collectedItems)) == True:
                                continue
                            else:
                                self.log.debug(
                                    "We could end but we're STUCK CAN'T REWIND"
                                )
                                return (-1, False)

            # handle no comeback locations
            rewindRequired = self.comeBack.handleNoComeBack(
                self.getAllLocs(majorsAvailable, minorsAvailable),
                len(self.collectedItems))
            if rewindRequired == True:
                if self.comeBack.rewind(len(self.collectedItems)) == True:
                    continue
                else:
                    # we're really stucked
                    self.log.debug("STUCK CAN'T REWIND")
                    break

            # sort them on difficulty and proximity
            self.log.debug("getAvailableItemsList majors")
            majorsAvailable = self.getAvailableItemsList(
                majorsAvailable, diffThreshold)
            if self.majorsSplit == 'Full':
                minorsAvailable = majorsAvailable
            else:
                self.log.debug("getAvailableItemsList minors")
                minorsAvailable = self.getAvailableItemsList(
                    minorsAvailable, diffThreshold)

            # choose one to pick up
            self.nextDecision(majorsAvailable, minorsAvailable,
                              hasEnoughMinors, diffThreshold)

            self.comeBack.cleanNoComeBack(
                self.getAllLocs(self.majorLocations, self.minorLocations))

        # compute difficulty value
        (difficulty, itemsOk) = self.computeDifficultyValue()

        if self.log.getEffectiveLevel() == logging.DEBUG:
            self.log.debug("difficulty={}".format(difficulty))
            self.log.debug("itemsOk={}".format(itemsOk))
            self.log.debug(
                "{}: remaining major: {}, remaining minor: {}, visited: {}".
                format(Conf.itemsPickup, len(self.majorLocations),
                       len(self.minorLocations), len(self.visitedLocations)))

            self.log.debug("remaining majors:")
            for loc in self.majorLocations:
                self.log.debug("{} ({})".format(loc.Name, loc.itemName))

            self.log.debug("bosses: {}".format([
                (boss, Bosses.bossDead(self.smbm, boss))
                for boss in Bosses.Golden4()
            ]))

        return (difficulty, itemsOk)
Esempio n. 7
0
    def checkPool(self, forbidden=None):
        self.log.debug("checkPool. forbidden=" + str(forbidden) + ", self.forbiddenItems=" + str(self.forbiddenItems))
        if not self.graphSettings.isMinimizer() and not self.settings.isPlandoRando() and len(self.allLocations) > len(self.locations):
            # invalid graph with looped areas
            self.log.debug("checkPool: not all areas are connected, but minimizer param is off / not a plando rando")
            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
        self.log.debug('pool={}'.format(getItemListStr(container.itemPool)))
        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 Golden Four because it is always there.
                # Start APs might not have comeback transitions
                # possible start AP issues are handled in checkStart
                comeBack[ap] = self.areaGraph.canAccess(self.sm, ap, 'Golden Four', 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 objectives are compatible with accessible APs
        startAP = self.areaGraph.accessPoints[self.startAP]
        availAPs = [ap.Name for ap in self.areaGraph.getAvailableAccessPoints(startAP, self.sm, self.settings.maxDiff)]
        self.log.debug("availAPs="+str(availAPs))
        for goal in Objectives.activeGoals:
            n, aps = goal.escapeAccessPoints
            if len(aps) == 0:
                continue
            escAPs = [ap for ap in aps if ap in availAPs]
            self.log.debug("escAPs="+str(escAPs))
            if len(escAPs) < n:
                self.log.debug("checkPool. goal '"+goal.name+"' impossible to complete due to area layout")
                ret = False
                continue
            for ap in escAPs:
                if not self.areaGraph.canAccess(self.sm, ap, "Golden Four", self.settings.maxDiff):
                    self.log.debug("checkPool. goal '"+goal.name+"' impossible to complete due to area layout")
                    ret = False
                    break
        # check if all inter-area APs can reach each other
        if ret:
            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:
                        self.log.debug("checkPool: ap {} non accessible from {}".format(ap.Name, startAp.Name))
                        ret = False
            if not ret:
                self.log.debug("checkPool. inter-area APs check failed")
        # cleanup
        self.sm.resetItems()
        self.restoreBossChecks()
        # check if we can reach/beat all bosses
        if ret:
            # always add G4 to mandatory bosses, even if not required by objectives
            mandatoryBosses = set(Objectives.getMandatoryBosses() + Bosses.Golden4())

            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.BossItemType for loc in self.services.currentLocations(self.startAP, container, diff=infinity) if loc.isBoss()])
                    self.log.debug("checkPool. beatableBosses="+str(beatableBosses))
                    self.log.debug("checkPool. mandatoryBosses: {}".format(mandatoryBosses))
                    ret = mandatoryBosses.issubset(set(beatableBosses)) and Objectives.checkLimitObjectives(beatableBosses)
                    if ret:
                        # check that we can then kill mother brain
                        self.sm.addItems(Bosses.Golden4() + Bosses.miniBosses())
                        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() or self.restrictions.isScavenger():
            # in chozo or scavenger, we cannot put other items than NoEnergy in the restricted locations,
            # we would be forced to put majors in there, which can make seed generation fail:
            # don't put more restricted major locations than removed major items
            # FIXME something to do there for chozo/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]
            nRestrictedMajor = sum(1 for loc in restrictedLocs if self.restrictions.isLocMajor(loc))
            nNothingMajor = sum(1 for item in pool if self.restrictions.isItemMajor(item) and item.Category == 'Nothing')
            ret &= nRestrictedMajor <= nNothingMajor
            self.log.debug('checkPool. nRestrictedMajor='+str(nRestrictedMajor)+', nNothingMajor='+str(nNothingMajor))
        self.log.debug('checkPool. result: '+str(ret))
        return ret