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
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)
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 = []
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)
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
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))
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))
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)
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]))
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)