diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 5d53270d..bc8dcd61 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -124,7 +124,7 @@ class SMWorld(World): Logic.factory('vanilla') dummy_rom_file = Utils.user_path(SMSettings.RomFile.copy_to) # actual rom set in generate_output - self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player) + self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player, self.multiworld.seed, self.random) self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) # keeps Nothing items local so no player will ever pickup Nothing @@ -314,11 +314,11 @@ class SMWorld(World): raise KeyError(f"Item {name} for {self.player_name} is invalid.") def get_filler_item_name(self) -> str: - if self.multiworld.random.randint(0, 100) < self.options.minor_qty.value: + if self.random.randint(0, 100) < self.options.minor_qty.value: power_bombs = self.options.power_bomb_qty.value missiles = self.options.missile_qty.value super_missiles = self.options.super_qty.value - roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles) + roll = self.random.randint(1, power_bombs + missiles + super_missiles) if roll <= power_bombs: return "Power Bomb" elif roll <= power_bombs + missiles: @@ -340,8 +340,8 @@ class SMWorld(World): else: nonChozoLoc.append(loc) - self.multiworld.random.shuffle(nonChozoLoc) - self.multiworld.random.shuffle(chozoLoc) + self.random.shuffle(nonChozoLoc) + self.random.shuffle(chozoLoc) missingCount = len(self.NothingPool) - len(nonChozoLoc) locations = nonChozoLoc if (missingCount > 0): diff --git a/worlds/sm/variaRandomizer/graph/graph_utils.py b/worlds/sm/variaRandomizer/graph/graph_utils.py index 36253343..b2b889fd 100644 --- a/worlds/sm/variaRandomizer/graph/graph_utils.py +++ b/worlds/sm/variaRandomizer/graph/graph_utils.py @@ -1,5 +1,4 @@ import copy -import random from ..logic.logic import Logic from ..utils.parameters import Knows from ..graph.location import locationsDict @@ -136,7 +135,8 @@ class GraphUtils: refused[apName] = cause return ret, refused - def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs): + @staticmethod + def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs, random): locs = locationsDict preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)] possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs] @@ -160,7 +160,8 @@ class GraphUtils: ap = getAccessPoint(startApName) return ap.Start['patches'] if 'patches' in ap.Start else [] - def createBossesTransitions(): + @staticmethod + def createBossesTransitions(random): transitions = vanillaBossesTransitions def isVanilla(): for t in vanillaBossesTransitions: @@ -180,13 +181,15 @@ class GraphUtils: transitions.append((src,dst)) return transitions - def createAreaTransitions(lightAreaRando=False): + @staticmethod + def createAreaTransitions(lightAreaRando=False, *, random): if lightAreaRando: - return GraphUtils.createLightAreaTransitions() + return GraphUtils.createLightAreaTransitions(random=random) else: - return GraphUtils.createRegularAreaTransitions() + return GraphUtils.createRegularAreaTransitions(random=random) - def createRegularAreaTransitions(apList=None, apPred=None): + @staticmethod + def createRegularAreaTransitions(apList=None, apPred=None, *, random): if apList is None: apList = Logic.accessPoints if apPred is None: @@ -239,7 +242,8 @@ class GraphUtils: transitions.append((ap.Name, ap.Name)) # crateria can be forced in corner cases - def createMinimizerTransitions(startApName, locLimit, forcedAreas=None): + @staticmethod + def createMinimizerTransitions(startApName, locLimit, forcedAreas=None, *, random): if forcedAreas is None: forcedAreas = [] if startApName == 'Ceres': @@ -316,7 +320,8 @@ class GraphUtils: GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas)) return transitions - def createLightAreaTransitions(): + @staticmethod + def createLightAreaTransitions(random): # group APs by area aps = {} totalCount = 0 @@ -407,7 +412,8 @@ class GraphUtils: return rooms - def escapeAnimalsTransitions(graph, possibleTargets, firstEscape): + @staticmethod + def escapeAnimalsTransitions(graph, possibleTargets, firstEscape, random): n = len(possibleTargets) assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets) GraphUtils.log.debug("escapeAnimalsTransitions. possibleTargets="+str(possibleTargets)+", firstEscape="+str(firstEscape)) diff --git a/worlds/sm/variaRandomizer/rando/Choice.py b/worlds/sm/variaRandomizer/rando/Choice.py index b4f4166f..72af3d80 100644 --- a/worlds/sm/variaRandomizer/rando/Choice.py +++ b/worlds/sm/variaRandomizer/rando/Choice.py @@ -1,4 +1,3 @@ -import random from ..utils import log from ..utils.utils import getRangeDict, chooseFromRange from ..rando.ItemLocContainer import ItemLocation @@ -23,8 +22,9 @@ class Choice(object): # simple random choice, that chooses an item first, then a locatio to put it in class ItemThenLocChoice(Choice): - def __init__(self, restrictions): + def __init__(self, restrictions, random): super(ItemThenLocChoice, self).__init__(restrictions) + self.random = random def chooseItemLoc(self, itemLocDict, isProg): itemList = self.getItemList(itemLocDict) @@ -49,7 +49,7 @@ class ItemThenLocChoice(Choice): return self.chooseItemRandom(itemList) def chooseItemRandom(self, itemList): - return random.choice(itemList) + return self.random.choice(itemList) def chooseLocation(self, locList, item, isProg): if len(locList) == 0: @@ -63,12 +63,12 @@ class ItemThenLocChoice(Choice): return self.chooseLocationRandom(locList) def chooseLocationRandom(self, locList): - return random.choice(locList) + return self.random.choice(locList) # Choice specialization for prog speed based filler class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): - def __init__(self, restrictions, progSpeedParams, distanceProp, services): - super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions) + def __init__(self, restrictions, progSpeedParams, distanceProp, services, random): + super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions, random) self.progSpeedParams = progSpeedParams self.distanceProp = distanceProp self.services = services @@ -104,7 +104,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1: item, locList = list(itemLocDict.items())[0] if item.Type == 'Morph': - morphLocs = self.restrictions.lateMorphCheck(container, locList) + morphLocs = self.restrictions.lateMorphCheck(container, locList, self.random) if morphLocs is not None: itemLocDict[item] = morphLocs else: @@ -115,7 +115,7 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): assert len(locs) == 1 and locs[0].Name == item.Name return ItemLocation(item, locs[0]) # late doors check for random door colors - if self.restrictions.isLateDoors() and random.random() < self.lateDoorsProb: + if self.restrictions.isLateDoors() and self.random.random() < self.lateDoorsProb: self.processLateDoors(itemLocDict, ap, container) self.progressionItemLocs = progressionItemLocs self.ap = ap @@ -145,14 +145,14 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): def chooseLocationProg(self, locs, item): locs = self.getLocsSpreadProgression(locs) - random.shuffle(locs) + self.random.shuffle(locs) ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs) self.log.debug('chooseLocationProg. ret='+ret.Name) return ret # get choose function from a weighted dict def getChooseFunc(self, rangeDict, funcDict): - v = chooseFromRange(rangeDict) + v = chooseFromRange(rangeDict, self.random) return funcDict[v] @@ -209,6 +209,6 @@ class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): for i in range(len(availableLocations)): loc = availableLocations[i] d = distances[i] - if d == maxDist or random.random() >= self.spreadProb: + if d == maxDist or self.random.random() >= self.spreadProb: locs.append(loc) return locs diff --git a/worlds/sm/variaRandomizer/rando/Filler.py b/worlds/sm/variaRandomizer/rando/Filler.py index 00caa7e6..3fab9d39 100644 --- a/worlds/sm/variaRandomizer/rando/Filler.py +++ b/worlds/sm/variaRandomizer/rando/Filler.py @@ -1,5 +1,5 @@ -import copy, time, random +import copy, time from ..utils import log from ..logic.cache import RequestCache from ..rando.RandoServices import RandoServices @@ -15,11 +15,11 @@ from ..graph.graph_utils import GraphUtils # item pool is not empty). # entry point is generateItems class Filler(object): - def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): + def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random): self.startAP = startAP self.cache = RequestCache() self.graph = graph - self.services = RandoServices(graph, restrictions, self.cache) + self.services = RandoServices(graph, restrictions, self.cache, random=random) self.restrictions = restrictions self.settings = restrictions.settings self.endDate = endDate @@ -108,9 +108,9 @@ class Filler(object): # very simple front fill algorithm with no rollback and no "softlock checks" (== dessy algorithm) class FrontFiller(Filler): - def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): - super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate) - self.choice = ItemThenLocChoice(restrictions) + def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random): + super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate, random=random) + self.choice = ItemThenLocChoice(restrictions, random) self.stdStart = GraphUtils.isStandardStart(self.startAP) def isEarlyGame(self): diff --git a/worlds/sm/variaRandomizer/rando/GraphBuilder.py b/worlds/sm/variaRandomizer/rando/GraphBuilder.py index 88b539e7..7fb8802d 100644 --- a/worlds/sm/variaRandomizer/rando/GraphBuilder.py +++ b/worlds/sm/variaRandomizer/rando/GraphBuilder.py @@ -1,5 +1,5 @@ -import random, copy +import copy from ..utils import log from ..graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets, graphAreas, getAccessPoint from ..logic.logic import Logic @@ -11,13 +11,14 @@ from collections import defaultdict # creates graph and handles randomized escape class GraphBuilder(object): - def __init__(self, graphSettings): + def __init__(self, graphSettings, random): self.graphSettings = graphSettings self.areaRando = graphSettings.areaRando self.bossRando = graphSettings.bossRando self.escapeRando = graphSettings.escapeRando self.minimizerN = graphSettings.minimizerN self.log = log.get('GraphBuilder') + self.random = random # builds everything but escape transitions def createGraph(self, maxDiff): @@ -48,18 +49,18 @@ class GraphBuilder(object): objForced = forcedAreas.intersection(escAreas) escAreasList = sorted(list(escAreas)) while len(objForced) < n and len(escAreasList) > 0: - objForced.add(escAreasList.pop(random.randint(0, len(escAreasList)-1))) + objForced.add(escAreasList.pop(self.random.randint(0, len(escAreasList)-1))) forcedAreas = forcedAreas.union(objForced) - transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas))) + transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas)), random=self.random) else: if not self.bossRando: transitions += vanillaBossesTransitions else: - transitions += GraphUtils.createBossesTransitions() + transitions += GraphUtils.createBossesTransitions(self.random) if not self.areaRando: transitions += vanillaTransitions else: - transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando) + transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando, random=self.random) ret = AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile) Objectives.objDict[self.graphSettings.player].setGraph(ret, maxDiff) return ret @@ -100,7 +101,7 @@ class GraphBuilder(object): self.escapeTimer(graph, paths, self.areaRando or escapeTrigger is not None) self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer'])) # animals - GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst) + GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst, self.random) return True def _getTargets(self, sm, graph, maxDiff): @@ -110,7 +111,7 @@ class GraphBuilder(object): if len(possibleTargets) == 0: self.log.debug("Can't randomize escape, fallback to vanilla") possibleTargets.append('Climb Bottom Left') - random.shuffle(possibleTargets) + self.random.shuffle(possibleTargets) return possibleTargets def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff): diff --git a/worlds/sm/variaRandomizer/rando/Items.py b/worlds/sm/variaRandomizer/rando/Items.py index ec58b478..612bc36b 100644 --- a/worlds/sm/variaRandomizer/rando/Items.py +++ b/worlds/sm/variaRandomizer/rando/Items.py @@ -1,6 +1,6 @@ from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange from ..utils import log -import logging, copy, random +import logging, copy class Item: __slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' ) @@ -335,7 +335,7 @@ class ItemManager: itemCode = item.Code + modifier return itemCode - def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff): + def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff, random): self.qty = qty self.sm = sm self.majorsSplit = majorsSplit @@ -344,6 +344,7 @@ class ItemManager: self.maxDiff = maxDiff self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major' self.itemPool = [] + self.random = random def newItemPool(self, addBosses=True): self.itemPool = [] @@ -386,7 +387,7 @@ class ItemManager: return ItemManager.Items[itemType].withClass(itemClass) def createItemPool(self, exclude=None): - itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff) + itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff, self.random) self.itemPool = itemPoolGenerator.getItemPool() @staticmethod @@ -402,20 +403,20 @@ class ItemPoolGenerator(object): nbBosses = 9 @staticmethod - def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff): + def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff, random): if majorsSplit == 'Chozo': - return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff, random) elif majorsSplit == 'Plando': - return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff) + return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff, random) elif nLocs == ItemPoolGenerator.maxLocs: if majorsSplit == "Scavenger": - return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff, random) else: - return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff, random) else: - return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff) + return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff, random) - def __init__(self, itemManager, qty, sm, maxDiff): + def __init__(self, itemManager, qty, sm, maxDiff, random): self.itemManager = itemManager self.qty = qty self.sm = sm @@ -423,12 +424,13 @@ class ItemPoolGenerator(object): self.maxEnergy = 18 # 14E, 4R self.maxDiff = maxDiff self.log = log.get('ItemPool') + self.random = random def isUltraSparseNoTanks(self): # if low stuff botwoon is not known there is a hard energy req of one tank, even # with both suits lowStuffBotwoon = self.sm.knowsLowStuffBotwoon() - return random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) + return self.random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) def calcMaxMinors(self): pool = self.itemManager.getItemPool() @@ -464,7 +466,7 @@ class ItemPoolGenerator(object): rangeDict = getRangeDict(ammoQty) self.log.debug("rangeDict: {}".format(rangeDict)) while len(self.itemManager.getItemPool()) < maxItems: - item = chooseFromRange(rangeDict) + item = chooseFromRange(rangeDict, self.random) self.itemManager.addMinor(item) else: minorsTypes = ['Missile', 'Super', 'PowerBomb'] @@ -522,7 +524,7 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator): # no etank nor reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('NoEnergy', 'Chozo') - elif random.random() < 0.5: + elif self.random.random() < 0.5: # replace only etank with reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('Reserve', 'Chozo') @@ -535,9 +537,9 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator): # 4-6 # already 3E and 1R alreadyInPool = 4 - rest = randGaussBounds(2, 5) + rest = randGaussBounds(self.random, 2, 5) if rest >= 1: - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') @@ -550,13 +552,13 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator): # 8-12 # add up to 3 Reserves or ETanks (cannot add more than 3 reserves) for i in range(3): - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') # 7 already in the pool (3 E, 1 R, + the previous 3) alreadyInPool = 7 - rest = 1 + randGaussBounds(4, 3.7) + rest = 1 + randGaussBounds(self.random, 4, 3.7) for i in range(rest): self.itemManager.addItem('ETank', 'Minor') # fill the rest with NoEnergy @@ -581,10 +583,10 @@ class ItemPoolGeneratorChozo(ItemPoolGenerator): return self.itemManager.getItemPool() class ItemPoolGeneratorMajors(ItemPoolGenerator): - def __init__(self, itemManager, qty, sm, maxDiff): - super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff) - self.sparseRest = 1 + randGaussBounds(2, 5) - self.mediumRest = 3 + randGaussBounds(4, 3.7) + def __init__(self, itemManager, qty, sm, maxDiff, random): + super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff, random) + self.sparseRest = 1 + randGaussBounds(self.random,2, 5) + self.mediumRest = 3 + randGaussBounds(self.random, 4, 3.7) self.ultraSparseNoTanks = self.isUltraSparseNoTanks() def addNoEnergy(self): @@ -609,7 +611,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator): # no energy at all self.addNoEnergy() else: - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('ETank') else: self.itemManager.addItem('Reserve') @@ -620,7 +622,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator): elif energyQty == 'sparse': # 4-6 - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') @@ -639,7 +641,7 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator): alreadyInPool = 2 n = getE(3) for i in range(n): - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') @@ -676,15 +678,15 @@ class ItemPoolGeneratorMajors(ItemPoolGenerator): return self.itemManager.getItemPool() class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors): - def __init__(self, itemManager, qty, sm, maxDiff): - super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, maxDiff, random): + super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff, random) def addNoEnergy(self): self.itemManager.addItem('Nothing') class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors): - def __init__(self, itemManager, qty, sm, nLocs, maxDiff): - super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, nLocs, maxDiff, random): + super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff, random) self.maxItems = nLocs self.calcMaxAmmo() nMajors = len([itemName for itemName,item in ItemManager.Items.items() if item.Class == 'Major' and item.Category != 'Energy']) @@ -716,8 +718,8 @@ class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors): self.log.debug("maxEnergy: "+str(self.maxEnergy)) class ItemPoolGeneratorPlando(ItemPoolGenerator): - def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff): - super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff, random): + super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff, random) # in exclude dict: # in alreadyPlacedItems: # dict of 'itemType: count' of items already added in the plando. @@ -805,7 +807,7 @@ class ItemPoolGeneratorPlando(ItemPoolGenerator): if ammoQty: rangeDict = getRangeDict(ammoQty) while len(self.itemManager.getItemPool()) < maxItems and remain > 0: - item = chooseFromRange(rangeDict) + item = chooseFromRange(rangeDict, self.random) self.itemManager.addMinor(item) remain -= 1 diff --git a/worlds/sm/variaRandomizer/rando/RandoExec.py b/worlds/sm/variaRandomizer/rando/RandoExec.py index f799252f..c50e0691 100644 --- a/worlds/sm/variaRandomizer/rando/RandoExec.py +++ b/worlds/sm/variaRandomizer/rando/RandoExec.py @@ -1,4 +1,4 @@ -import sys, random, time +import sys, time from ..utils import log from ..logic.logic import Logic @@ -14,7 +14,7 @@ from ..utils.doorsmanager import DoorsManager # entry point for rando execution ("randomize" method) class RandoExec(object): - def __init__(self, seedName, vcr, randoSettings, graphSettings, player): + def __init__(self, seedName, vcr, randoSettings, graphSettings, player, random): self.errorMsg = "" self.seedName = seedName self.vcr = vcr @@ -22,6 +22,7 @@ class RandoExec(object): self.graphSettings = graphSettings self.log = log.get('RandoExec') self.player = player + self.random = random # processes settings to : # - create Restrictions and GraphBuilder objects @@ -31,7 +32,7 @@ class RandoExec(object): vcr = VCR(self.seedName, 'rando') if self.vcr == True else None self.errorMsg = "" split = self.randoSettings.restrictions['MajorMinor'] - self.graphBuilder = GraphBuilder(self.graphSettings) + self.graphBuilder = GraphBuilder(self.graphSettings, self.random) container = None i = 0 attempts = 500 if self.graphSettings.areaRando or self.graphSettings.doorsColorsRando or split == 'Scavenger' else 1 @@ -43,10 +44,10 @@ class RandoExec(object): while container is None and i < attempts and now <= endDate: self.restrictions = Restrictions(self.randoSettings) if self.graphSettings.doorsColorsRando == True: - DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player) + DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player, self.random) self.areaGraph = self.graphBuilder.createGraph(self.randoSettings.maxDiff) - services = RandoServices(self.areaGraph, self.restrictions) - setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player) + services = RandoServices(self.areaGraph, self.restrictions, random=self.random) + setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player, self.random) self.setup = setup container = setup.createItemLocContainer(endDate, vcr) if container is None: @@ -78,7 +79,7 @@ class RandoExec(object): n = nMaj elif split == 'Chozo': n = nChozo - GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n) + GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n, self.random) def postProcessItemLocs(self, itemLocs, hide): # hide some items like in dessy's @@ -89,7 +90,7 @@ class RandoExec(object): if (item.Category != "Nothing" and loc.CanHidden == True and loc.Visibility == 'Visible'): - if bool(random.getrandbits(1)) == True: + if bool(self.random.getrandbits(1)) == True: loc.Visibility = 'Hidden' # put nothing in unfilled locations filledLocNames = [il.Location.Name for il in itemLocs] diff --git a/worlds/sm/variaRandomizer/rando/RandoServices.py b/worlds/sm/variaRandomizer/rando/RandoServices.py index a3ad1f39..e060c356 100644 --- a/worlds/sm/variaRandomizer/rando/RandoServices.py +++ b/worlds/sm/variaRandomizer/rando/RandoServices.py @@ -1,5 +1,4 @@ - -import copy, random, sys, logging, os +import copy, sys, logging, os from enum import Enum, unique from ..utils import log from ..utils.parameters import infinity @@ -19,12 +18,13 @@ class ComebackCheckType(Enum): # collection of stateless services to be used mainly by fillers class RandoServices(object): - def __init__(self, graph, restrictions, cache=None): + def __init__(self, graph, restrictions, cache=None, *, random): self.restrictions = restrictions self.settings = restrictions.settings self.areaGraph = graph self.cache = cache self.log = log.get('RandoServices') + self.random = random @staticmethod def printProgress(s): @@ -217,7 +217,7 @@ class RandoServices(object): # choose a morph item location in that context morphItemLoc = ItemLocation( morph, - random.choice(morphLocs) + self.random.choice(morphLocs) ) # acquire morph in new context and see if we can still open new locs newAP = self.collect(ap, containerCpy, morphItemLoc) @@ -232,7 +232,7 @@ class RandoServices(object): if morphLocItem is None or len(itemLocDict) == 1: # no morph, or it is the only possibility: nothing to do return - morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem]) + morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem], self.random) if morphLocs is not None: itemLocDict[morphLocItem] = morphLocs else: @@ -380,10 +380,10 @@ class RandoServices(object): (itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck) assert not isProg items = list(itemLocDict.keys()) - random.shuffle(items) + self.random.shuffle(items) for item in items: cont = copy.copy(container) - loc = random.choice(itemLocDict[item]) + loc = self.random.choice(itemLocDict[item]) itemLoc1 = ItemLocation(item, loc) self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1)) newAP = self.collect(ap, cont, itemLoc1) @@ -391,8 +391,8 @@ class RandoServices(object): self.cache.reset() (ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck) if isProg: - item2 = random.choice(list(ild.keys())) - itemLoc2 = ItemLocation(item2, random.choice(ild[item2])) + item2 = self.random.choice(list(ild.keys())) + itemLoc2 = ItemLocation(item2, self.random.choice(ild[item2])) self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2)) return (itemLoc1, itemLoc2) return None diff --git a/worlds/sm/variaRandomizer/rando/RandoSettings.py b/worlds/sm/variaRandomizer/rando/RandoSettings.py index 418688f1..b0e9bb4c 100644 --- a/worlds/sm/variaRandomizer/rando/RandoSettings.py +++ b/worlds/sm/variaRandomizer/rando/RandoSettings.py @@ -1,5 +1,4 @@ - -import sys, random +import sys from collections import defaultdict from ..rando.Items import ItemManager from ..utils.utils import getRangeDict, chooseFromRange @@ -32,11 +31,11 @@ class RandoSettings(object): def isPlandoRando(self): return self.PlandoOptions is not None - def getItemManager(self, smbm, nLocs, bossesItems): + def getItemManager(self, smbm, nLocs, bossesItems, random): if not self.isPlandoRando(): - return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff) + return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff, random) else: - return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff) + return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff, random) def getExcludeItems(self, locations): if not self.isPlandoRando(): @@ -94,7 +93,7 @@ class ProgSpeedParameters(object): self.restrictions = restrictions self.nLocs = nLocs - def getVariableSpeed(self): + def getVariableSpeed(self, random): ranges = getRangeDict({ 'slowest':7, 'slow':20, @@ -102,7 +101,7 @@ class ProgSpeedParameters(object): 'fast':27, 'fastest':11 }) - return chooseFromRange(ranges) + return chooseFromRange(ranges, random) def getMinorHelpProb(self, progSpeed): if self.restrictions.split != 'Major': @@ -134,7 +133,7 @@ class ProgSpeedParameters(object): def isSlow(self, progSpeed): return progSpeed == "slow" or (progSpeed == "slowest" and self.restrictions.split == "Chozo") - def getItemLimit(self, progSpeed): + def getItemLimit(self, progSpeed, random): itemLimit = self.nLocs if self.isSlow(progSpeed): itemLimit = int(self.nLocs*0.209) # 21 for 105 diff --git a/worlds/sm/variaRandomizer/rando/RandoSetup.py b/worlds/sm/variaRandomizer/rando/RandoSetup.py index 943e3fe5..eb64c0d3 100644 --- a/worlds/sm/variaRandomizer/rando/RandoSetup.py +++ b/worlds/sm/variaRandomizer/rando/RandoSetup.py @@ -1,4 +1,4 @@ -import copy, random +import copy from ..utils import log from ..utils.utils import randGaussBounds @@ -16,8 +16,9 @@ from ..rom.rom_patches import RomPatches # checks init conditions for the randomizer: processes super fun settings, graph, start location, special restrictions # the entry point is createItemLocContainer class RandoSetup(object): - def __init__(self, graphSettings, locations, services, player): + def __init__(self, graphSettings, locations, services, player, random): self.sm = SMBoolManager(player, services.settings.maxDiff) + self.random = random self.settings = services.settings self.graphSettings = graphSettings self.startAP = graphSettings.startAP @@ -31,7 +32,7 @@ class RandoSetup(object): # print("nLocs Setup: "+str(len(self.locations))) # in minimizer we can have some missing boss locs bossesItems = [loc.BossItemType for loc in self.locations if loc.isBoss()] - self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems) + self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems, random) self.forbiddenItems = [] self.restrictedLocs = [] self.lastRestricted = [] @@ -165,7 +166,7 @@ class RandoSetup(object): return True self.log.debug("********* PRE RANDO START") container = copy.copy(self.container) - filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container) + filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container, random=self.random) condition = filler.createStepCountCondition(4) (isStuck, itemLocations, progItems) = filler.generateItems(condition) self.log.debug("********* PRE RANDO END") @@ -345,9 +346,9 @@ class RandoSetup(object): def getForbiddenItemsFromList(self, itemList): self.log.debug('getForbiddenItemsFromList: ' + str(itemList)) remove = [] - n = randGaussBounds(len(itemList)) + n = randGaussBounds(self.random, len(itemList)) for i in range(n): - idx = random.randint(0, len(itemList) - 1) + idx = self.random.randint(0, len(itemList) - 1) item = itemList.pop(idx) if item is not None: remove.append(item) diff --git a/worlds/sm/variaRandomizer/rando/Restrictions.py b/worlds/sm/variaRandomizer/rando/Restrictions.py index fabdfea4..4f9de53d 100644 --- a/worlds/sm/variaRandomizer/rando/Restrictions.py +++ b/worlds/sm/variaRandomizer/rando/Restrictions.py @@ -1,4 +1,4 @@ -import copy, random +import copy from ..utils import log from ..graph.graph_utils import getAccessPoint from ..rando.ItemLocContainer import getLocListStr @@ -112,7 +112,7 @@ class Restrictions(object): return item.Class == "Minor" # return True if we can keep morph as a possibility - def lateMorphCheck(self, container, possibleLocs): + def lateMorphCheck(self, container, possibleLocs, random): # the closer we get to the limit the higher the chances of allowing morph proba = random.randint(0, self.lateMorphLimit) if self.split == 'Full': diff --git a/worlds/sm/variaRandomizer/randomizer.py b/worlds/sm/variaRandomizer/randomizer.py index 22712aa4..c3752c42 100644 --- a/worlds/sm/variaRandomizer/randomizer.py +++ b/worlds/sm/variaRandomizer/randomizer.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 from Utils import output_path -import argparse, os.path, json, sys, shutil, random, copy, requests +import argparse, os.path, json, sys, shutil, copy, requests from .rando.RandoSettings import RandoSettings, GraphSettings from .rando.RandoExec import RandoExec @@ -39,7 +39,7 @@ objectives = defaultMultiValues['objective'] tourians = defaultMultiValues['tourian'] areaRandomizations = defaultMultiValues['areaRandomization'] -def randomMulti(args, param, defaultMultiValues): +def randomMulti(args, param, defaultMultiValues, random): value = args[param] isRandom = False @@ -250,10 +250,11 @@ class VariaRandomizer: parser.add_argument('--tourianList', help="list to choose from when random", dest='tourianList', nargs='?', default=None) - def __init__(self, options, rom, player): + def __init__(self, options, rom, player, seed, random): # parse args self.args = copy.deepcopy(VariaRandomizer.parser.parse_args(["--logic", "varia"])) #dummy custom args to skip parsing _sys.argv while still get default values self.player = player + self.random = random args = self.args args.rom = rom # args.startLocation = to_pascal_case_with_space(options.startLocation.current_key) @@ -323,11 +324,13 @@ class VariaRandomizer: logger.debug("preset: {}".format(preset)) - # if no seed given, choose one - if args.seed == 0: - self.seed = random.randrange(sys.maxsize) - else: - self.seed = args.seed + # Archipelago provides a seed for the multiworld. + self.seed = seed + # # if no seed given, choose one + # if args.seed == 0: + # self.seed = random.randrange(sys.maxsize) + # else: + # self.seed = args.seed logger.debug("seed: {}".format(self.seed)) if args.raceMagic is not None: @@ -360,12 +363,12 @@ class VariaRandomizer: logger.debug("maxDifficulty: {}".format(self.maxDifficulty)) # handle random parameters with dynamic pool of values - (_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds) - (_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs) - (majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits) - (_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours) - (_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians) - (areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations) + (_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds, random) + (_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs, random) + (majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits, random) + (_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours, random) + (_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians, random) + (areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations, random) areaRandomization = args.area in ['light', 'full'] lightArea = args.area == 'light' @@ -626,7 +629,7 @@ class VariaRandomizer: if args.objective: if (args.objectiveRandom): availableObjectives = [goal for goal in objectives if goal != "collect 100% items"] if "random" in args.objectiveList else args.objectiveList - self.objectivesManager.setRandom(args.nbObjective, availableObjectives) + self.objectivesManager.setRandom(args.nbObjective, availableObjectives, self.random) else: maxActiveGoals = Objectives.maxActiveGoals - addedObjectives if len(args.objective) > maxActiveGoals: @@ -660,7 +663,7 @@ class VariaRandomizer: # print("energyQty:{}".format(energyQty)) #try: - self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player) + self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player, self.random) self.container = self.randoExec.randomize() # if we couldn't find an area layout then the escape graph is not created either # and getDoorConnections will crash if random escape is activated. @@ -690,7 +693,7 @@ class VariaRandomizer: 'gameend.ips', 'grey_door_animals.ips', 'low_timer.ips', 'metalimals.ips', 'phantoonimals.ips', 'ridleyimals.ips'] if args.escapeRando == False: - args.patches.append(random.choice(animalsPatches)) + args.patches.append(self.random.choice(animalsPatches)) args.patches.append("Escape_Animals_Change_Event") else: optErrMsgs.append("Ignored animals surprise because of escape randomization") @@ -760,9 +763,9 @@ class VariaRandomizer: # patch local rom # romFileName = args.rom # shutil.copyfile(romFileName, outputFilename) - romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, player=self.player) + romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, player=self.player, random=self.random) else: - romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic) + romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, random=self.random) if customPrePatchApply != None: customPrePatchApply(romPatcher) diff --git a/worlds/sm/variaRandomizer/rom/rompatcher.py b/worlds/sm/variaRandomizer/rom/rompatcher.py index a350764a..7e5aa235 100644 --- a/worlds/sm/variaRandomizer/rom/rompatcher.py +++ b/worlds/sm/variaRandomizer/rom/rompatcher.py @@ -49,7 +49,7 @@ class RomPatcher: 'DoorsColors': ['beam_doors_plms.ips', 'beam_doors_gfx.ips', 'red_doors.ips'] } - def __init__(self, settings=None, romFileName=None, magic=None, player=0): + def __init__(self, settings=None, romFileName=None, magic=None, player=0, *, random): self.log = log.get('RomPatcher') self.settings = settings #self.romFileName = romFileName @@ -76,6 +76,7 @@ class RomPatcher: 0x93ea: self.forceRoomCRE } self.player = player + self.random = random def patchRom(self): self.applyIPSPatches() @@ -496,9 +497,9 @@ class RomPatcher: self.ipsPatches = [] def writeSeed(self, seed): - random.seed(seed) - seedInfo = random.randint(0, 0xFFFF) - seedInfo2 = random.randint(0, 0xFFFF) + r = random.Random(seed) + seedInfo = r.randint(0, 0xFFFF) + seedInfo2 = r.randint(0, 0xFFFF) self.romFile.writeWord(seedInfo, snes_to_pc(0xdfff00)) self.romFile.writeWord(seedInfo2) @@ -1066,7 +1067,7 @@ class RomPatcher: def writeObjectives(self, itemLocs, tourian): objectives = Objectives.objDict[self.player] - objectives.writeGoals(self.romFile) + objectives.writeGoals(self.romFile, self.random) objectives.writeIntroObjectives(self.romFile, tourian) self.writeItemsMasks(itemLocs) # hack bomb_torizo.ips to wake BT in all cases if necessary, ie chozo bots objective is on, and nothing at bombs diff --git a/worlds/sm/variaRandomizer/utils/doorsmanager.py b/worlds/sm/variaRandomizer/utils/doorsmanager.py index 6a8ecda1..425958c5 100644 --- a/worlds/sm/variaRandomizer/utils/doorsmanager.py +++ b/worlds/sm/variaRandomizer/utils/doorsmanager.py @@ -1,4 +1,3 @@ -import random from enum import IntEnum,IntFlag import copy from ..logic.smbool import SMBool @@ -123,7 +122,7 @@ class Door(object): else: return [color for color in colorsList if color not in self.forbiddenColors] - def randomize(self, allowGreyDoors): + def randomize(self, allowGreyDoors, random): if self.canRandomize(): if self.canGrey and allowGreyDoors: self.setColor(random.choice(self.filterColorList(colorsListGrey))) @@ -347,9 +346,9 @@ class DoorsManager(): currentDoors['CrabShaftRight'].forceBlue() @staticmethod - def randomize(allowGreyDoors, player): + def randomize(allowGreyDoors, player, random): for door in DoorsManager.doorsDict[player].values(): - door.randomize(allowGreyDoors) + door.randomize(allowGreyDoors, random) # set both ends of toilet to the same color to avoid soft locking in area rando toiletTop = DoorsManager.doorsDict[player]['PlasmaSparkBottom'] toiletBottom = DoorsManager.doorsDict[player]['OasisTop'] diff --git a/worlds/sm/variaRandomizer/utils/objectives.py b/worlds/sm/variaRandomizer/utils/objectives.py index 67cdb9a1..8ca2ce65 100644 --- a/worlds/sm/variaRandomizer/utils/objectives.py +++ b/worlds/sm/variaRandomizer/utils/objectives.py @@ -1,5 +1,4 @@ import copy -import random from ..rom.addresses import Addresses from ..rom.rom import pc_to_snes from ..logic.helpers import Bosses @@ -28,7 +27,7 @@ class Synonyms(object): ] alreadyUsed = [] @staticmethod - def getVerb(): + def getVerb(random): verb = random.choice(Synonyms.killSynonyms) while verb in Synonyms.alreadyUsed: verb = random.choice(Synonyms.killSynonyms) @@ -88,10 +87,10 @@ class Goal(object): # not all objectives require an ap (like limit objectives) return self.clearFunc(smbm, ap) - def getText(self): + def getText(self, random): out = "{}. ".format(self.rank) if self.useSynonym: - out += self.text.format(Synonyms.getVerb()) + out += self.text.format(Synonyms.getVerb(random)) else: out += self.text assert len(out) <= 28, "Goal text '{}' is too long: {}, max 28".format(out, len(out)) @@ -676,7 +675,7 @@ class Objectives(object): return [goal.name for goal in _goals.values() if goal.available and (not removeNothing or goal.name != "nothing")] # call from rando - def setRandom(self, nbGoals, availableGoals): + def setRandom(self, nbGoals, availableGoals, random): while self.nbActiveGoals < nbGoals and availableGoals: goalName = random.choice(availableGoals) self.addGoal(goalName) @@ -702,7 +701,7 @@ class Objectives(object): LOG.debug("tourianRequired: {}".format(self.tourianRequired)) # call from rando - def writeGoals(self, romFile): + def writeGoals(self, romFile, random): # write check functions romFile.seek(Addresses.getOne('objectivesList')) for goal in self.activeGoals: @@ -736,7 +735,7 @@ class Objectives(object): space = 3 if self.nbActiveGoals == 5 else 4 for i, goal in enumerate(self.activeGoals): addr = baseAddr + i * lineLength * space - text = goal.getText() + text = goal.getText(random) romFile.seek(addr) for c in text: if c not in char2tile: diff --git a/worlds/sm/variaRandomizer/utils/utils.py b/worlds/sm/variaRandomizer/utils/utils.py index f7d699b6..c297a3cc 100644 --- a/worlds/sm/variaRandomizer/utils/utils.py +++ b/worlds/sm/variaRandomizer/utils/utils.py @@ -1,5 +1,5 @@ import io -import os, json, re, random +import os, json, re import pathlib import sys from typing import Any @@ -88,7 +88,7 @@ def normalizeRounding(n): # gauss random in [0, r] range # the higher the slope, the less probable extreme values are. -def randGaussBounds(r, slope=5): +def randGaussBounds(random, r, slope=5): r = float(r) n = normalizeRounding(random.gauss(r/2, r/slope)) if n < 0: @@ -111,7 +111,7 @@ def getRangeDict(weightDict): return rangeDict -def chooseFromRange(rangeDict): +def chooseFromRange(rangeDict, random): r = random.random() val = None for v in sorted(rangeDict, key=rangeDict.get):