def __init__(self, startAP, areaGraph, restrictions):
     self.startAP = startAP
     self.areaGraph = areaGraph
     self.restrictions = restrictions
     self.settings = restrictions.settings
     self.smbm = SMBoolManager()
     self.log = utils.log.get('MiniSolver')
 def checkLimitObjectives(beatableBosses):
     # check that there's enough bosses/minibosses for limit objectives
     from logic.smboolmanager import SMBoolManager
     smbm = SMBoolManager()
     smbm.addItems(beatableBosses)
     for goal in Objectives.activeGoals:
         if not goal.isLimit():
             continue
         if not goal.canClearGoal(smbm):
             return False
     return True
Example #3
0
 def __init__(self, graphSettings, locations, services):
     self.sm = SMBoolManager()
     self.settings = services.settings
     self.graphSettings = graphSettings
     self.startAP = graphSettings.startAP
     self.superFun = self.settings.getSuperFun()
     self.container = None
     self.services = services
     self.restrictions = services.restrictions
     self.areaGraph = services.areaGraph
     self.allLocations = locations
     self.locations = self.areaGraph.getAccessibleLocations(
         locations, self.startAP)
     #        print("nLocs Setup: "+str(len(self.locations)))
     self.itemManager = self.settings.getItemManager(
         self.sm, len(self.locations))
     self.forbiddenItems = []
     self.restrictedLocs = []
     self.lastRestricted = []
     self.bossesLocs = sorted(
         ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain'])
     self.suits = ['Varia', 'Gravity']
     # organized by priority
     self.movementItems = [
         'SpaceJump', 'HiJump', 'SpeedBooster', 'Bomb', 'Grapple',
         'SpringBall'
     ]
     # organized by priority
     self.combatItems = ['ScrewAttack', 'Plasma', 'Wave', 'Spazer']
     # OMG
     self.bossChecks = {
         'Kraid': self.sm.enoughStuffsKraid,
         'Phantoon': self.sm.enoughStuffsPhantoon,
         'Draygon': self.sm.enoughStuffsDraygon,
         'Ridley': self.sm.enoughStuffsRidley,
         'Mother Brain': self.sm.enoughStuffsMotherbrain
     }
     self.okay = lambda: SMBool(True, 0)
     exclude = self.settings.getExcludeItems(self.locations)
     # we have to use item manager only once, otherwise pool will change
     self.itemManager.createItemPool(exclude)
     self.basePool = self.itemManager.getItemPool()[:]
     self.log = utils.log.get('RandoSetup')
     if len(locations) != len(self.locations):
         self.log.debug("inaccessible locations :" + getLocListStr(
             [loc for loc in locations if loc not in self.locations]))
    def __init__(self,
                 majorsSplit,
                 startLocation,
                 areaGraph,
                 locations,
                 vcr=None):
        self.interactive = False
        self.checkDuplicateMajor = False
        self.vcr = vcr
        # for compatibility with some common methods of the interactive solver
        self.mode = 'standard'

        self.log = utils.log.get('Solver')

        # default conf
        self.setConf(easy, 'all', [], False)

        self.firstLogFile = None

        self.extStatsFilename = None
        self.extStatsStep = None

        self.type = 'rando'
        self.output = Out.factory(self.type, self)
        self.outputFileName = None

        self.locations = locations

        self.smbm = SMBoolManager()

        # preset already loaded by rando
        self.presetFileName = None

        self.pickup = Pickup(Conf.itemsPickup)

        self.comeBack = ComeBack(self)

        # load ROM info, patches are already loaded by the rando. get the graph from the rando too
        self.majorsSplit = majorsSplit
        self.startLocation = startLocation
        self.startArea = getAccessPoint(startLocation).Start['solveArea']
        self.areaGraph = areaGraph

        self.objectives = Objectives()

        # store at each step how many locations are available
        self.nbAvailLocs = []

        # limit to a few seconds to avoid cases with a lot of rewinds which could last for minutes
        self.runtimeLimit_s = 5
        self.startTime = time.process_time()
    def initialize(self, mode, rom, presetFileName, magic, debug, fill,
                   startAP, trackerRace):
        # load rom and preset, return first state
        self.debug = debug
        self.mode = mode
        if self.mode != "seedless":
            self.seed = os.path.basename(os.path.splitext(rom)[0]) + '.sfc'
        else:
            self.seed = "seedless"

        self.smbm = SMBoolManager()

        self.presetFileName = presetFileName
        self.loadPreset(self.presetFileName)

        self.loadRom(rom, interactive=True, magic=magic, startAP=startAP)
        # in plando/tracker always consider that we're doing full
        self.majorsSplit = 'Full'

        # hide doors
        if self.doorsRando and mode == 'standard':
            DoorsManager.initTracker()

        self.clearItems()

        # in debug mode don't load plando locs/transitions
        if self.mode == 'plando' and self.debug == False:
            if fill == True:
                # load the source seed transitions and items/locations
                self.curGraphTransitions = self.bossTransitions + self.areaTransitions + self.escapeTransition
                self.areaGraph = AccessGraph(accessPoints,
                                             self.curGraphTransitions)
                self.fillPlandoLocs()
            else:
                if self.areaRando == True or self.bossRando == True:
                    plandoTrans = self.loadPlandoTransitions()
                    if len(plandoTrans) > 0:
                        self.curGraphTransitions = plandoTrans
                    self.areaGraph = AccessGraph(accessPoints,
                                                 self.curGraphTransitions)

                self.loadPlandoLocs()

        # compute new available locations
        self.computeLocationsDifficulty(self.majorLocations)

        if trackerRace == True:
            self.mode = 'seedless'

        self.dumpState()
 def __copy__(self):
     locs = copy.copy(self.unusedLocations)
     # we don't copy restriction state on purpose: it depends on
     # outside context we don't want to bring to the copy
     ret = ItemLocContainer(SMBoolManager(),
                            self.itemPoolBackup[:] if self.itemPoolBackup != None else self.itemPool[:],
                            locs)
     ret.currentItems = self.currentItems[:]
     ret.unrestrictedItems = copy.copy(self.unrestrictedItems)
     ret.itemLocations = [ ItemLocation(
         il.Item,
         copy.copy(il.Location)
     ) for il in self.itemLocations ]
     ret.sm.addItems([item.Type for item in ret.currentItems])
     return ret
    def tryRemainingLocs(self):
        # use preset which knows every techniques to test the remaining locs to
        # find which technique could allow to continue the seed
        locations = self.majorLocations if self.majorsSplit == 'Full' else self.majorLocations + self.minorLocations

        # instanciate a new smbool manager to reset the cache
        self.smbm = SMBoolManager()
        presetFileName = os.path.expanduser('~/RandomMetroidSolver/standard_presets/solution.json')
        presetLoader = PresetLoader.factory(presetFileName)
        presetLoader.load()
        self.smbm.createKnowsFunctions()

        self.areaGraph.getAvailableLocations(locations, self.smbm, infinity, self.lastAP)

        return [loc for loc in locations if loc.difficulty.bool == True]
    def __init__(self, rom, presetFileName, difficultyTarget, pickupStrategy, itemsForbidden=[], type='console',
                 firstItemsLog=None, extStatsFilename=None, extStatsStep=None, displayGeneratedPath=False,
                 outputFileName=None, magic=None, checkDuplicateMajor=False, vcr=False, plot=None):
        self.interactive = False
        self.checkDuplicateMajor = checkDuplicateMajor
        if vcr == True:
            from utils.vcr import VCR
            self.vcr = VCR(rom, 'solver')
        else:
            self.vcr = None
        # for compatibility with some common methods of the interactive solver
        self.mode = 'standard'

        self.log = utils.log.get('Solver')

        self.setConf(difficultyTarget, pickupStrategy, itemsForbidden, displayGeneratedPath)

        self.firstLogFile = None
        if firstItemsLog is not None:
            self.firstLogFile = open(firstItemsLog, 'w')
            self.firstLogFile.write('Item;Location;Area\n')

        self.extStatsFilename = extStatsFilename
        self.extStatsStep = extStatsStep
        self.plot = plot

        # can be called from command line (console) or from web site (web)
        self.type = type
        self.output = Out.factory(self.type, self)
        self.outputFileName = outputFileName

        self.locations = graphLocations

        self.smbm = SMBoolManager()

        self.presetFileName = presetFileName
        self.loadPreset(self.presetFileName)

        self.loadRom(rom, magic=magic)

        self.pickup = Pickup(Conf.itemsPickup)

        self.comeBack = ComeBack(self)
Example #9
0
    def __init__(self, majorsSplit, startAP, areaGraph, locations):
        self.interactive = False
        self.checkDuplicateMajor = False
        self.vcr = None
        # for compatibility with some common methods of the interactive solver
        self.mode = 'standard'

        self.log = utils.log.get('Solver')

        # default conf
        self.setConf(easy, 'all', [], False)

        self.firstLogFile = None

        self.extStatsFilename = None
        self.extStatsStep = None
        self.plot = None

        self.type = 'rando'
        self.output = Out.factory(self.type, self)
        self.outputFileName = None

        self.locations = locations

        self.smbm = SMBoolManager()

        # preset already loaded by rando
        self.presetFileName = None

        self.pickup = Pickup(Conf.itemsPickup)

        self.comeBack = ComeBack(self)

        # load ROM info, patches are already loaded by the rando. get the graph from the rando too
        self.majorsSplit = majorsSplit
        self.startAP = startAP
        self.startArea = getAccessPoint(startAP).Start['solveArea']
        self.areaGraph = areaGraph

        # store at each step how many locations are available
        self.nbAvailLocs = []
Example #10
0
    def computeHellruns(self, hellRuns):
        sm = SMBoolManager()
        for hellRun in ['Ice', 'MainUpperNorfair']:
            hellRuns[hellRun] = {}

            for (actualHellRun,
                 params) in Settings.hellRunsTable[hellRun].items():
                hellRuns[hellRun][actualHellRun] = {}
                for (key,
                     difficulties) in Settings.hellRunPresets[hellRun].items():
                    if key == 'Solution':
                        continue
                    Settings.hellRuns[hellRun] = difficulties
                    hellRuns[hellRun][actualHellRun][key] = {
                        easy: -1,
                        medium: -1,
                        hard: -1,
                        harder: -1,
                        hardcore: -1,
                        mania: -1
                    }
                    if difficulties == None:
                        continue

                    sm.resetItems()
                    for etank in range(19):
                        ret = sm.canHellRun(**params)

                        if ret.bool == True:
                            nEtank = 0
                            for item in ret.items:
                                if item.find('ETank') != -1:
                                    nEtank = int(item[0:item.find('-ETank')])
                                    break
                            hellRuns[hellRun][actualHellRun][key][
                                ret.difficulty] = nEtank

                        sm.addItem('ETank')

        hellRun = 'LowerNorfair'
        hellRuns[hellRun] = {}
        hellRuns[hellRun]["NoScrew"] = self.computeLNHellRun(sm, False)
        hellRuns[hellRun]["Screw"] = self.computeLNHellRun(sm, True)
Example #11
0
    def computeHardRooms(self, hardRooms):
        # add gravity patch (as we add it by default in the randomizer)
        RomPatches.ActivePatches.append(RomPatches.NoGravityEnvProtection)

        sm = SMBoolManager()

        # xray
        xray = {}
        xray['Suitless'] = self.computeXray(sm, False)
        xray['Varia'] = self.computeXray(sm, True)
        hardRooms['X-Ray'] = xray

        # gauntlet
        gauntlet = {}
        gauntlet['SuitlessBomb'] = self.computeGauntlet(sm, 'Bomb', False)
        gauntlet['SuitlessPowerBomb'] = self.computeGauntlet(
            sm, 'PowerBomb', False)
        gauntlet['VariaBomb'] = self.computeGauntlet(sm, 'Bomb', True)
        gauntlet['VariaPowerBomb'] = self.computeGauntlet(
            sm, 'PowerBomb', True)
        hardRooms['Gauntlet'] = gauntlet

        return hardRooms
Example #12
0
    def loadRom(self, rom, interactive=False, magic=None, startLocation=None):
        self.scavengerOrder = []
        self.plandoScavengerOrder = []
        # startLocation param is only use for seedless
        if rom == None:
            # TODO::add a --logic parameter for seedless
            Logic.factory('vanilla')
            self.romFileName = 'seedless'
            self.majorsSplit = 'Full'
            self.masterMajorsSplit = 'Full'
            self.areaRando = True
            self.bossRando = True
            self.escapeRando = False
            self.escapeTimer = "03:00"
            self.startLocation = startLocation
            RomPatches.setDefaultPatches(startLocation)
            self.startArea = getAccessPoint(startLocation).Start['solveArea']
            # in seedless load all the vanilla transitions
            self.areaTransitions = vanillaTransitions[:]
            self.bossTransitions = vanillaBossesTransitions[:]
            self.escapeTransition = [vanillaEscapeTransitions[0]]
            # in seedless we allow mixing of area and boss transitions
            self.hasMixedTransitions = True
            self.curGraphTransitions = self.bossTransitions + self.areaTransitions + self.escapeTransition
            self.locations = Logic.locations
            for loc in self.locations:
                loc.itemName = 'Nothing'
            # set doors related to default patches
            DoorsManager.setDoorsColor()
            self.doorsRando = False
            self.hasNothing = False
            self.objectives.setVanilla()
            self.tourian = 'Vanilla'
        else:
            self.romFileName = rom
            self.romLoader = RomLoader.factory(rom, magic)
            Logic.factory(self.romLoader.readLogic())
            self.locations = Logic.locations
            (self.majorsSplit,
             self.masterMajorsSplit) = self.romLoader.assignItems(
                 self.locations)
            (self.startLocation, self.startArea,
             startPatches) = self.romLoader.getStartAP()
            if not GraphUtils.isStandardStart(
                    self.startLocation) and self.majorsSplit != 'Full':
                # update major/chozo locs in non standard start
                self.romLoader.updateSplitLocs(self.majorsSplit,
                                               self.locations)
            (self.areaRando, self.bossRando, self.escapeRando, hasObjectives,
             self.tourian) = self.romLoader.loadPatches()
            RomPatches.ActivePatches += startPatches
            self.escapeTimer = self.romLoader.getEscapeTimer()
            self.doorsRando = self.romLoader.loadDoorsColor()
            self.hasNothing = self.checkLocsForNothing()
            if self.majorsSplit == 'Scavenger':
                self.scavengerOrder = self.romLoader.loadScavengerOrder(
                    self.locations)
            if hasObjectives:
                self.romLoader.loadObjectives(self.objectives)
            else:
                if self.majorsSplit == "Scavenger":
                    self.objectives.setScavengerHunt()
                    self.objectives.tourianRequired = not self.romLoader.hasPatch(
                        'Escape_Trigger')
                else:
                    self.objectives.setVanilla()
            self.majorUpgrades = self.romLoader.loadMajorUpgrades()
            self.splitLocsByArea = self.romLoader.getSplitLocsByArea(
                self.locations)
            self.objectives.setSolverMode(self)

            if interactive == False:
                print(
                    "ROM {} majors: {} area: {} boss: {} escape: {} patches: {} activePatches: {}"
                    .format(rom, self.majorsSplit, self.areaRando,
                            self.bossRando, self.escapeRando,
                            sorted(self.romLoader.getPatches()),
                            sorted(RomPatches.ActivePatches)))
            else:
                print(
                    "majors: {} area: {} boss: {} escape: {} activepatches: {}"
                    .format(self.majorsSplit, self.areaRando,
                            self.bossRando, self.escapeRando,
                            sorted(RomPatches.ActivePatches)))

            (self.areaTransitions, self.bossTransitions, self.escapeTransition,
             self.hasMixedTransitions) = self.romLoader.getTransitions(
                 self.tourian)
            if interactive == True and self.debug == False:
                # in interactive area mode we build the graph as we play along
                if self.areaRando == True and self.bossRando == True:
                    self.curGraphTransitions = []
                elif self.areaRando == True:
                    self.curGraphTransitions = self.bossTransitions[:]
                elif self.bossRando == True:
                    self.curGraphTransitions = self.areaTransitions[:]
                else:
                    self.curGraphTransitions = self.bossTransitions + self.areaTransitions
                if self.escapeRando == False:
                    self.curGraphTransitions += self.escapeTransition
            else:
                self.curGraphTransitions = self.bossTransitions + self.areaTransitions + self.escapeTransition

        self.smbm = SMBoolManager()
        self.buildGraph()

        # store at each step how many locations are available
        self.nbAvailLocs = []

        if self.log.getEffectiveLevel() == logging.DEBUG:
            self.log.debug("Display items at locations:")
            for loc in self.locations:
                self.log.debug('{:>50}: {:>16}'.format(loc.Name, loc.itemName))
Example #13
0
    def iterate(self, stateJson, scope, action, params):
        self.debug = params["debug"]
        self.smbm = SMBoolManager()

        state = SolverState()
        state.fromJson(stateJson)
        state.toSolver(self)

        self.loadPreset(self.presetFileName)

        # add already collected items to smbm
        self.smbm.addItems(self.collectedItems)

        if scope == 'item':
            if action == 'clear':
                self.clearItems(True)
            else:
                if action == 'add':
                    if self.mode in ['plando', 'seedless', 'race', 'debug']:
                        if params['loc'] != None:
                            if self.mode == 'plando':
                                self.setItemAt(params['loc'], params['item'],
                                               params['hide'])
                            else:
                                itemName = params.get('item', 'Nothing')
                                if itemName is None:
                                    itemName = 'Nothing'
                                self.setItemAt(params['loc'], itemName, False)
                        else:
                            self.increaseItem(params['item'])
                    else:
                        # pickup item at locName
                        self.pickItemAt(params['loc'])
                elif action == 'remove':
                    if 'loc' in params:
                        self.removeItemAt(params['loc'])
                    elif 'count' in params:
                        # remove last collected item
                        self.cancelLastItems(params['count'])
                    else:
                        self.decreaseItem(params['item'])
                elif action == 'replace':
                    self.replaceItemAt(params['loc'], params['item'],
                                       params['hide'])
                elif action == 'toggle':
                    self.toggleItem(params['item'])
        elif scope == 'area':
            if action == 'clear':
                self.clearTransitions()
            else:
                if action == 'add':
                    startPoint = params['startPoint']
                    endPoint = params['endPoint']
                    self.addTransition(self.transWeb2Internal[startPoint],
                                       self.transWeb2Internal[endPoint])
                elif action == 'remove':
                    if 'startPoint' in params:
                        self.cancelTransition(
                            self.transWeb2Internal[params['startPoint']])
                    else:
                        # remove last transition
                        self.cancelLastTransition()
        elif scope == 'door':
            if action == 'replace':
                doorName = params['doorName']
                newColor = params['newColor']
                DoorsManager.setColor(doorName, newColor)
            elif action == 'toggle':
                doorName = params['doorName']
                DoorsManager.switchVisibility(doorName)

        self.areaGraph = AccessGraph(Logic.accessPoints,
                                     self.curGraphTransitions)

        if scope == 'common':
            if action == 'save':
                return self.savePlando(params['lock'], params['escapeTimer'])
            elif action == 'randomize':
                self.randoPlando(params)

        # if last loc added was a sequence break, recompute its difficulty,
        # as it may be available with the newly placed item.
        if len(self.visitedLocations) > 0:
            lastVisited = self.visitedLocations[-1]
            if lastVisited.difficulty.difficulty == -1:
                self.visitedLocations.remove(lastVisited)
                self.majorLocations.append(lastVisited)
            else:
                lastVisited = None
        else:
            lastVisited = None

        # compute new available locations
        self.clearLocs(self.majorLocations)
        self.computeLocationsDifficulty(self.majorLocations)

        # put back last visited location
        if lastVisited != None:
            self.majorLocations.remove(lastVisited)
            self.visitedLocations.append(lastVisited)
            if lastVisited.difficulty == False:
                # if the loc is still sequence break, put it back as sequence break
                lastVisited.difficulty = SMBool(True, -1)

        # return them
        self.dumpState()
 def transferCollected(self, dest):
     dest.currentItems = self.currentItems[:]
     dest.sm = SMBoolManager()
     dest.sm.addItems([item.Type for item in dest.currentItems])
     dest.itemLocations = copy.copy(self.itemLocations)
     dest.unrestrictedItems = copy.copy(self.unrestrictedItems)
class MiniSolver(object):
    def __init__(self, startAP, areaGraph, restrictions):
        self.startAP = startAP
        self.areaGraph = areaGraph
        self.restrictions = restrictions
        self.settings = restrictions.settings
        self.smbm = SMBoolManager()
        self.log = utils.log.get('MiniSolver')

    # if True, does not mean it is actually beatable, unless you're sure of it from another source of information
    # if False, it is certain it is not beatable
    def isBeatable(self, itemLocations, maxDiff=None):
        if maxDiff is None:
            maxDiff = self.settings.maxDiff
        minDiff = self.settings.minDiff
        locations = []
        for il in itemLocations:
            loc = il.Location
            if loc.restricted:
                continue
            loc.itemName = il.Item.Type
            loc.difficulty = None
            locations.append(loc)
        self.smbm.resetItems()
        ap = self.startAP
        onlyBossesLeft = -1
        hasOneLocAboveMinDiff = False
        while True:
            if not locations:
                return hasOneLocAboveMinDiff
            # only two loops to collect all remaining locations in only bosses left mode
            if onlyBossesLeft >= 0:
                onlyBossesLeft += 1
                if onlyBossesLeft > 2:
                    return False
            self.areaGraph.getAvailableLocations(locations, self.smbm, maxDiff,
                                                 ap)
            post = [
                loc for loc in locations
                if loc.PostAvailable and loc.difficulty.bool == True
            ]
            for loc in post:
                self.smbm.addItem(loc.itemName)
                postAvailable = loc.PostAvailable(self.smbm)
                self.smbm.removeItem(loc.itemName)
                loc.difficulty = self.smbm.wand(loc.difficulty, postAvailable)
            toCollect = [
                loc for loc in locations if loc.difficulty.bool == True
                and loc.difficulty.difficulty <= maxDiff
            ]
            if not toCollect:
                # mini onlyBossesLeft
                if maxDiff < infinity:
                    maxDiff = infinity
                    onlyBossesLeft = 0
                    continue
                return False
            if not hasOneLocAboveMinDiff:
                hasOneLocAboveMinDiff = any(
                    loc.difficulty.difficulty >= minDiff for loc in locations)
            self.smbm.addItems([loc.itemName for loc in toCollect])
            for loc in toCollect:
                locations.remove(loc)
    def loadRom(self, rom, interactive=False, magic=None, startAP=None):
        # startAP param is only use for seedless
        if rom == None:
            # TODO::add a --logic parameter for seedless
            Logic.factory('varia')
            self.romFileName = 'seedless'
            self.majorsSplit = 'Full'
            self.areaRando = True
            self.bossRando = True
            self.escapeRando = False
            self.escapeTimer = "03:00"
            self.startAP = startAP
            RomPatches.setDefaultPatches(startAP)
            self.startArea = getAccessPoint(startAP).Start['solveArea']
            # in seedless load all the vanilla transitions
            self.areaTransitions = vanillaTransitions[:]
            self.bossTransitions = vanillaBossesTransitions[:]
            self.escapeTransition = [vanillaEscapeTransitions[0]]
            # in seedless we allow mixing of area and boss transitions
            self.hasMixedTransitions = True
            self.curGraphTransitions = self.bossTransitions + self.areaTransitions + self.escapeTransition
            self.locations = Logic.locations
            for loc in self.locations:
                loc.itemName = 'Nothing'
            # set doors related to default patches
            DoorsManager.setDoorsColor()
            self.doorsRando = False
        else:
            self.romFileName = rom
            self.romLoader = RomLoader.factory(rom, magic)
            Logic.factory(self.romLoader.readLogic())
            self.romLoader.readNothingId()
            self.locations = Logic.locations
            self.majorsSplit = self.romLoader.assignItems(self.locations)
            (self.startAP, self.startArea,
             startPatches) = self.romLoader.getStartAP()
            (self.areaRando, self.bossRando,
             self.escapeRando) = self.romLoader.loadPatches()
            RomPatches.ActivePatches += startPatches
            self.escapeTimer = self.romLoader.getEscapeTimer()
            self.doorsRando = self.romLoader.loadDoorsColor()

            if interactive == False:
                print(
                    "ROM {} majors: {} area: {} boss: {} escape: {} patches: {} activePatches: {}"
                    .format(rom, self.majorsSplit, self.areaRando,
                            self.bossRando, self.escapeRando,
                            sorted(self.romLoader.getPatches()),
                            sorted(RomPatches.ActivePatches)))
            else:
                print(
                    "majors: {} area: {} boss: {} escape: {} activepatches: {}"
                    .format(self.majorsSplit, self.areaRando,
                            self.bossRando, self.escapeRando,
                            sorted(RomPatches.ActivePatches)))

            (self.areaTransitions, self.bossTransitions, self.escapeTransition,
             self.hasMixedTransitions) = self.romLoader.getTransitions()
            if interactive == True and self.debug == False:
                # in interactive area mode we build the graph as we play along
                if self.areaRando == True and self.bossRando == True:
                    self.curGraphTransitions = []
                elif self.areaRando == True:
                    self.curGraphTransitions = self.bossTransitions[:]
                elif self.bossRando == True:
                    self.curGraphTransitions = self.areaTransitions[:]
                else:
                    self.curGraphTransitions = self.bossTransitions + self.areaTransitions
                if self.escapeRando == False:
                    self.curGraphTransitions += self.escapeTransition
            else:
                self.curGraphTransitions = self.bossTransitions + self.areaTransitions + self.escapeTransition

        self.smbm = SMBoolManager()
        self.areaGraph = AccessGraph(Logic.accessPoints,
                                     self.curGraphTransitions)

        # store at each step how many locations are available
        self.nbAvailLocs = []

        if self.log.getEffectiveLevel() == logging.DEBUG:
            self.log.debug("Display items at locations:")
            for loc in self.locations:
                self.log.debug('{:>50}: {:>16}'.format(loc.Name, loc.itemName))
Example #17
0
from utils.utils import randGaussBounds
from rando.Items import ItemManager
from logic.smboolmanager import SMBoolManager
import random
import utils.log

fun = [
    'HiJump', 'SpeedBooster', 'Plasma', 'ScrewAttack', 'Wave', 'Spazer',
    'SpringBall'
]

if __name__ == "__main__":
    #    log.init(True) # debug mode
    utils.log.init(False)
    logger = utils.log.get('ItemsTest')
    sm = SMBoolManager()
    with open("itemStats.csv", "w") as csvOut:
        csvOut.write(
            "nLocs;energyQty;minorQty;nFun;strictMinors;MissProb;SuperProb;PowerProb;split;nItems;nTanks;nTanksTotal;nMinors;nMissiles;nSupers;nPowers;MissAccuracy;SuperAccuracy;PowerAccuracy;AmmoAccuracy\n"
        )
        for i in range(10000):
            logger.debug('SEED ' + str(i))
            if (i + 1) % 100 == 0:
                print(i + 1)
            isVanilla = random.random() < 0.5
            strictMinors = bool(random.getrandbits(1))
            minQty = 100
            energyQty = 'vanilla'
            forbidden = []
            if not isVanilla:
                minQty = random.randint(1, 99)
Example #18
0
class RandoSetup(object):
    def __init__(self, graphSettings, locations, services):
        self.sm = SMBoolManager()
        self.settings = services.settings
        self.graphSettings = graphSettings
        self.startAP = graphSettings.startAP
        self.superFun = self.settings.getSuperFun()
        self.container = None
        self.services = services
        self.restrictions = services.restrictions
        self.areaGraph = services.areaGraph
        self.allLocations = locations
        self.locations = self.areaGraph.getAccessibleLocations(
            locations, self.startAP)
        #        print("nLocs Setup: "+str(len(self.locations)))
        self.itemManager = self.settings.getItemManager(
            self.sm, len(self.locations))
        self.forbiddenItems = []
        self.restrictedLocs = []
        self.lastRestricted = []
        self.bossesLocs = sorted(
            ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain'])
        self.suits = ['Varia', 'Gravity']
        # organized by priority
        self.movementItems = [
            'SpaceJump', 'HiJump', 'SpeedBooster', 'Bomb', 'Grapple',
            'SpringBall'
        ]
        # organized by priority
        self.combatItems = ['ScrewAttack', 'Plasma', 'Wave', 'Spazer']
        # OMG
        self.bossChecks = {
            'Kraid': self.sm.enoughStuffsKraid,
            'Phantoon': self.sm.enoughStuffsPhantoon,
            'Draygon': self.sm.enoughStuffsDraygon,
            'Ridley': self.sm.enoughStuffsRidley,
            'Mother Brain': self.sm.enoughStuffsMotherbrain
        }
        self.okay = lambda: SMBool(True, 0)
        exclude = self.settings.getExcludeItems(self.locations)
        # we have to use item manager only once, otherwise pool will change
        self.itemManager.createItemPool(exclude)
        self.basePool = self.itemManager.getItemPool()[:]
        self.log = utils.log.get('RandoSetup')
        if len(locations) != len(self.locations):
            self.log.debug("inaccessible locations :" + getLocListStr(
                [loc for loc in locations if loc not in self.locations]))

    # processes everything and returns an ItemLocContainer, or None if failed (invalid init conditions/settings)
    def createItemLocContainer(self):
        self.getForbidden()
        self.log.debug("LAST CHECKPOOL")
        if not self.checkPool():
            self.log.debug("createItemLocContainer: checkPool fail")
            return None
        self.checkDoorBeams()
        self.container = ItemLocContainer(self.sm, self.getItemPool(),
                                          self.locations)
        if self.restrictions.isLateMorph():
            self.restrictions.lateMorphInit(self.startAP, self.container,
                                            self.services)
            isStdStart = GraphUtils.isStandardStart(self.startAP)
            # ensure we have an area layout that can put morph outside start area
            # TODO::allow for custom start which doesn't require morph early
            if self.graphSettings.areaRando and isStdStart and not self.restrictions.suitsRestrictions and self.restrictions.lateMorphForbiddenArea is None:
                self.container = None
                self.log.debug("createItemLocContainer: checkLateMorph fail")
                return None
        # checkStart needs the container
        if not self.checkStart():
            self.container = None
            self.log.debug("createItemLocContainer: checkStart fail")
            return None
        # add placement restriction helpers for random fill
        if self.settings.progSpeed == 'speedrun':
            restrictionDict = self.getSpeedrunRestrictionsDict()
            self.restrictions.addPlacementRestrictions(restrictionDict)
        self.settings.collectAlreadyPlacedItemLocations(self.container)
        self.fillRestrictedLocations()
        self.settings.updateSuperFun(self.superFun)
        return self.container

    def getSpeedrunRestrictionsDict(self):
        itemTypes = {
            item.Type
            for item in self.container.itemPool
            if item.Category not in Restrictions.NoCheckCat
        }
        allAreas = {loc.GraphArea for loc in self.locations}
        items = [
            self.container.getNextItemInPool(itemType)
            for itemType in itemTypes
        ]
        restrictionDict = {}
        for area in allAreas:
            restrictionDict[area] = {}
            for itemType in itemTypes:
                restrictionDict[area][itemType] = set()
        for item in items:
            itemType = item.Type
            poss = self.services.possibleLocations(item, self.startAP,
                                                   self.container)
            for loc in poss:
                restrictionDict[loc.GraphArea][itemType].add(loc.Name)
        if self.restrictions.isEarlyMorph() and GraphUtils.isStandardStart(
                self.startAP):
            morphLocs = ['Morphing Ball']
            if self.restrictions.split in ['Full', 'Major']:
                dboost = self.sm.knowsCeilingDBoost()
                if dboost.bool == True and dboost.difficulty <= self.settings.maxDiff:
                    morphLocs.append('Energy Tank, Brinstar Ceiling')
            for area, locDict in restrictionDict.items():
                if area == 'Crateria':
                    locDict['Morph'] = set(morphLocs)
                else:
                    locDict['Morph'] = set()
        return restrictionDict

    # fill up unreachable locations with "junk" to maximize the chance of the ROM
    # to be finishable
    def fillRestrictedLocations(self):
        majorRestrictedLocs = [
            loc for loc in self.restrictedLocs
            if self.restrictions.isLocMajor(loc)
        ]
        otherRestrictedLocs = [
            loc for loc in self.restrictedLocs
            if loc not in majorRestrictedLocs
        ]

        def getItemPredicateMajor(itemType):
            return lambda item: item.Type == itemType and self.restrictions.isItemMajor(
                item)

        def getItemPredicateMinor(itemType):
            return lambda item: item.Type == itemType and self.restrictions.isItemMinor(
                item)

        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)

        fill(majorRestrictedLocs, getItemPredicateMajor)
        fill(otherRestrictedLocs, getItemPredicateMinor)

    def getItemPool(self, forbidden=[]):
        self.itemManager.setItemPool(
            self.basePool[:])  # reuse base pool to have constant base item set
        return self.itemManager.removeForbiddenItems(self.forbiddenItems +
                                                     forbidden)

    # if needed, do a simplified "pre-randomization" of a few items to check start AP/area layout validity
    def checkStart(self):
        ap = getAccessPoint(self.startAP)
        if not self.graphSettings.areaRando or ap.Start is None or \
           (('needsPreRando' not in ap.Start or not ap.Start['needsPreRando']) and\
            ('areaMode' not in ap.Start or not ap.Start['areaMode'])):
            return True
        self.log.debug("********* PRE RANDO START")
        container = copy.copy(self.container)
        filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions,
                             container)
        condition = filler.createStepCountCondition(4)
        (isStuck, itemLocations, progItems) = filler.generateItems(condition)
        self.log.debug("********* PRE RANDO END")
        return not isStuck and len(
            self.services.currentLocations(filler.ap, filler.container)) > 0

    # in door color rando, determine mandatory beams
    def checkDoorBeams(self):
        if self.restrictions.isLateDoors():
            doorBeams = ['Wave', 'Ice', 'Spazer', 'Plasma']
            self.restrictions.mandatoryBeams = [
                beam for beam in doorBeams
                if not self.checkPool(forbidden=[beam])
            ]
            self.log.debug("checkDoorBeams. mandatoryBeams=" +
                           str(self.restrictions.mandatoryBeams))

    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 disableBossChecks(self):
        self.sm.enoughStuffsKraid = self.okay
        self.sm.enoughStuffsPhantoon = self.okay
        self.sm.enoughStuffsDraygon = self.okay
        self.sm.enoughStuffsRidley = self.okay

        def mbCheck():
            (possible, energyDiff) = self.sm.mbEtankCheck()
            if possible == True:
                return self.okay()
            return smboolFalse

        self.sm.enoughStuffsMotherbrain = mbCheck

    def restoreBossChecks(self):
        self.sm.enoughStuffsKraid = self.bossChecks['Kraid']
        self.sm.enoughStuffsPhantoon = self.bossChecks['Phantoon']
        self.sm.enoughStuffsDraygon = self.bossChecks['Draygon']
        self.sm.enoughStuffsRidley = self.bossChecks['Ridley']
        self.sm.enoughStuffsMotherbrain = self.bossChecks['Mother Brain']

    def addRestricted(self):
        self.checkPool()
        for r in self.lastRestricted:
            if r not in self.restrictedLocs:
                self.restrictedLocs.append(r)

    def getForbiddenItemsFromList(self, itemList):
        self.log.debug('getForbiddenItemsFromList: ' + str(itemList))
        remove = []
        n = randGaussBounds(len(itemList))
        for i in range(n):
            idx = random.randint(0, len(itemList) - 1)
            item = itemList.pop(idx)
            if item is not None:
                remove.append(item)
        return remove

    def addForbidden(self, removable):
        forb = None
        # it can take several tries if some item combination removal
        # forbids access to more stuff than each individually
        tries = 0
        while forb is None and tries < 100:
            forb = self.getForbiddenItemsFromList(removable[:])
            self.log.debug("addForbidden. forb=" + str(forb))
            if self.checkPool(forb) == False:
                forb = None
            tries += 1
        if forb is None:
            # we couldn't find a combination, just pick an item
            firstItem = next(
                (itemType for itemType in removable if itemType is not None),
                None)
            if firstItem is not None:
                forb = [firstItem]
            else:
                forb = []
        self.forbiddenItems += forb
        self.checkPool()
        self.addRestricted()
        return len(forb)

    def getForbiddenSuits(self):
        self.log.debug("getForbiddenSuits BEGIN. forbidden=" +
                       str(self.forbiddenItems) + ",ap=" + self.startAP)
        removableSuits = [
            suit for suit in self.suits if self.checkPool([suit])
        ]
        if 'Varia' in removableSuits and self.startAP in [
                'Bubble Mountain', 'Firefleas Top'
        ]:
            # Varia has to be first item there, and checkPool can't detect it
            removableSuits.remove('Varia')
        self.log.debug("getForbiddenSuits removable=" + str(removableSuits))
        if len(removableSuits) > 0:
            # remove at least one
            if self.addForbidden(removableSuits) == 0:
                self.forbiddenItems.append(removableSuits.pop())
                self.checkPool()
                self.addRestricted()
        else:
            self.superFun.remove('Suits')
            self.errorMsgs.append("Super Fun : Could not remove any suit")
        self.log.debug("getForbiddenSuits END. forbidden=" +
                       str(self.forbiddenItems))

    def getForbiddenMovement(self):
        self.log.debug("getForbiddenMovement BEGIN. forbidden=" +
                       str(self.forbiddenItems))
        removableMovement = [
            mvt for mvt in self.movementItems if self.checkPool([mvt])
        ]
        self.log.debug("getForbiddenMovement removable=" +
                       str(removableMovement))
        if len(removableMovement) > 0:
            # remove at least the most important
            self.forbiddenItems.append(removableMovement.pop(0))
            self.addForbidden(removableMovement + [None])
        else:
            self.superFun.remove('Movement')
            self.errorMsgs.append(
                'Super Fun : Could not remove any movement item')
        self.log.debug("getForbiddenMovement END. forbidden=" +
                       str(self.forbiddenItems))

    def getForbiddenCombat(self):
        self.log.debug("getForbiddenCombat BEGIN. forbidden=" +
                       str(self.forbiddenItems))
        removableCombat = [
            cbt for cbt in self.combatItems if self.checkPool([cbt])
        ]
        self.log.debug("getForbiddenCombat removable=" + str(removableCombat))
        if len(removableCombat) > 0:
            fake = [
            ]  # placeholders to avoid tricking the gaussian into removing too much stuff
            if len(removableCombat) > 0:
                # remove at least one if possible (will be screw or plasma)
                self.forbiddenItems.append(removableCombat.pop(0))
                fake.append(None)
            # if plasma is still available, remove it as well if we can
            if len(removableCombat
                   ) > 0 and removableCombat[0] == 'Plasma' and self.checkPool(
                       [removableCombat[0]]):
                self.forbiddenItems.append(removableCombat.pop(0))
                fake.append(None)
            self.addForbidden(removableCombat + fake)
        else:
            self.superFun.remove('Combat')
            self.errorMsgs.append(
                'Super Fun : Could not remove any combat item')
        self.log.debug("getForbiddenCombat END. forbidden=" +
                       str(self.forbiddenItems))

    def getForbidden(self):
        self.forbiddenItems = []
        self.restrictedLocs = []
        self.errorMsgs = []
        if 'Suits' in self.superFun:  # impact on movement item
            self.getForbiddenSuits()
        if 'Movement' in self.superFun:
            self.getForbiddenMovement()
        if 'Combat' in self.superFun:
            self.getForbiddenCombat()
        # if no super fun, check that there's no restricted locations (for ultra sparse)
        if len(self.superFun) == 0:
            self.addRestricted()
        self.log.debug("forbiddenItems: {}".format(self.forbiddenItems))
        self.log.debug("restrictedLocs: {}".format(
            [loc.Name for loc in self.restrictedLocs]))
Example #19
0
 def transferCollected(self, dest):
     dest.currentItems = self.currentItems[:]
     dest.sm = SMBoolManager(self.sm.player, self.sm.maxDiff, self.sm.onlyBossLeft)
     dest.sm.addItems([item.Type for item in dest.currentItems])
     dest.itemLocations = copy.copy(self.itemLocations)
     dest.unrestrictedItems = copy.copy(self.unrestrictedItems)