def PickupReturn(seed, partySize): pickups = {} steps = 0 # We return the number of steps to advance before trying again, to make # this more efficient (for example, if we didn't get any pickups, we know # we can skip the entire party size window instead of trying each extra # frame individually). retSteps = -1 for i in range(partySize): seed = rng.advanceRng(seed, 1) steps += 1 if rng.top(seed) % 10 != 0: continue # If we encounter a pickup in a slot other than the first, we should # try again with that pickup in the first slot, to maximize number of # potential pickups retSteps = steps - 1 if retSteps == -1 else retSteps seed = rng.advanceRng(seed, 1) steps += 1 pickup = rng.top(seed) % 100 if pickup >= 40 and pickup < 50: pickups['Ultra Ball'] = pickups.get('Ultra Ball', 0) + 1 elif pickup >= 50 and pickup < 60: pickups['Rare Candy'] = pickups.get('Rare Candy', 0) + 1 elif pickup >= 80 and pickup < 90: pickups['Nugget'] = pickups.get('Nugget', 0) + 1 elif pickup >= 90 and pickup < 95: pickups['Protein'] = pickups.get('Protein', 0) + 1 elif pickup >= 95 and pickup < 99: pickups['PP Up'] = pickups.get('PP Up', 0) + 1 else: pickups['useless'] = pickups.get('useless', 0) + 1 return (pickups, retSteps if retSteps != -1 else steps)
def getTrendyWord(seed, index): "The half-word which describes a word to put in the trendy phrase." max_num = MAX_NUMBERS[index] seed = rng.advanceRng(seed, 1) value = rng.top(seed) % max_num index = (index & 0x7F) << 9 result = value & 0x1FF return (seed, index | result)
def getComparator(seed, injectVblank): """The half-word which is used to compare candidates.""" # I have no idea why this individual bit needs it's own RNG call. seed = rng.advanceRng(seed, 1) halfword = 0 if rng.top(seed) & 1 == 0 else 0x40 if injectVblank == 4: seed = rng.advanceRng(seed, 1) # This is how is makes a variable number of RNG calls. seed = rng.advanceRng(seed, 1) if rng.top(seed) % 0x62 > 0x32: if injectVblank == 5: seed = rng.advanceRng(seed, 1) seed = rng.advanceRng(seed, 1) if rng.top(seed) % 0x62 > 0x50: if injectVblank == 6: seed = rng.advanceRng(seed, 1) seed = rng.advanceRng(seed, 1) rand = rng.top(seed) % 0x62 top7 = rand + 0x1E # The game also does a & 0x7F here, but we're guaranteed not to lose # information even without it. top7 <<= 7 halfword |= top7 if injectVblank == 7: rng.advanceRng(seed, 1) seed = rng.advanceRng(seed, 1) # You thought variable number of RNG calls was weird, now we're doing a # random number between 0 and a random number. bottom7 = rng.top(seed) % (rand + 1) bottom7 += 0x1E # Again the game does a & 07F, but again we don't lose information. halfword |= bottom7 # bit 6 is now the random number from the top or'ed with this new random # number (which is not likely to be 1). It's...uhh...weird. And I don't # fully understand why it's needed. return (seed, halfword)
def GoodFrames(): """Use This to find locally good item grabs.""" seed = 0x8E0222DD partySize = 6 framesToSearch = 4800 steps = 0 while steps < framesToSearch: thisSeed = rng.advanceRng(seed, steps) pickups, advSteps = PickupReturn(thisSeed, partySize) candies = pickups.get('Rare Candy', 0) if candies > 1: print('MULTI-CANDY: Advance %d "steps" and get %s' % (steps, pickups)) elif candies == 1: if ('Ultra Ball' in pickups or 'Nugget' in pickups or 'Protein' in pickups or 'PP Up' in pickups): print('CANDY+: Advance %d "steps" and get %s' % (steps, pickups)) if advSteps > 0: steps += advSteps else: steps += 1
def searchAt(seed, index1, list2, index2): fidMatches = set() # Advance 1 for SID, and 1 more for frame advance seed = rng.advanceRng(seed, 2) # The first candidate is always generated with no problem with VBlank seed, firstCandidate = fid.generateCandidateFID(seed) # Starting with the second candidate FID, things get chaotic due to VBlank. # Within the 2nd word, the trendy phrase can be changed based on where the # VBlank occurs. If it doesn't, it may affect which FID is generated. # This can all affect the number of RNG advances during the 2nd FID, which # means we have different possibilities to check for subsequent FIDs as # well. for inject in range(9): nextSeed, candidate = fid.generateCandidateFID(seed, inject) candidates = [firstCandidate, candidate] # 3 more FIDs to generate. for i in range(3): nextSeed, candidate = fid.generateCandidateFID(nextSeed) candidates.append(candidate) candidates.sort() if candidateMatches(candidates[0], index1, list2, index2): fidMatches.add(candidates[0].FID) return fidMatches
def generateCandidateFID(seed, injectVblank=-1): """Generates a full CandidateFID at the given seed, injecting an extra RNG advancement at the index given by injectVblank. Args: seed: the starting seed to generate from. injectVblank: optional. Where to inject an extra rng advancement. Valid values are 0 to 8 inclusive.""" if injectVblank == 0: seed = rng.advanceRng(seed, 1) seed, firstTrendyWord = getTrendyWord(seed, 0xA) if injectVblank == 1: seed = rng.advanceRng(seed, 1) seed = rng.advanceRng(seed, 1) nextIndex = 0xD if rng.top(seed) & 1 == 0 else 0xC if injectVblank == 2: seed = rng.advanceRng(seed, 1) seed, secondTrendyWord = getTrendyWord(seed, nextIndex) if injectVblank == 3: seed = rng.advanceRng(seed, 1) seed, comparator = getComparator(seed, injectVblank) if injectVblank == 8: seed = rng.advanceRng(seed, 1) seed = rng.advanceRng(seed, 1) FID = rng.top(seed) # print("\t%x" % firstHalfWord) # print("\tFID: %x" % FID) # print("\t%x" % thirdHalfWord) # print("\t%x" % fourthHalfWord) return seed, Candidate(comparator, FID, firstTrendyWord, secondTrendyWord)
def main(): seed = 0x6529CF57 seed = rng.advanceRng(seed, 77) seed = rng.advanceRng(seed, 2) # Either 1 or 2 saveSeed = seed generateFID(seed)
def main(): """This does an A* search to find a good battle/menuing chain to get to TO_FIND items. It should reutrn something close to optimal, but to keep things efficient, the heuristic can be outdone by superb RNG.""" # Best g seen for each inventory state. Used to not explore nodes that we # know can't be good. bestSeen = {} pq = [] heapq.heappush(pq, Node({}, 0, 0, [])) # TODO: insert first node while len(pq) > 0: node = heapq.heappop(pq) if node.heuristic() == 0: print("DONE!") print(node.items) print(node.actions) print("Expected advances: %d" % node.advances) break # If there are any items on the party, consider removing them if node.partyItems > 0: newItems = {} copyItems(node.items, newItems) newActions = [] for action in node.actions: newActions.append(action) newActions.append(Action('M', menuFrames(node.partyItems))) heapq.heappush( pq, Node(newItems, 0, node.advances + menuFrames(node.partyItems), newActions)) # Start searching for candies after at least 1 battle seed = rng.advanceRng(INITIAL_SEED, node.advances + LOWEST_RNG_ADVANCES_PER_BATTLE) party = MAX_PARTY_SIZE - node.partyItems # Assuming that if we wait this long, we might as well have gotten # another item in between. framesToSearch = 4 * LOWEST_RNG_ADVANCES_PER_BATTLE steps = 0 while steps < framesToSearch: thisSeed = rng.advanceRng(seed, steps) pickups, advSteps = PickupReturn(thisSeed, party) oldSteps = steps if advSteps > 0: steps += advSteps else: steps += 1 numTotalPickups = 0 numUsefulPickups = 0 newItems = {} for item in [ 'Rare Candy', 'Protein', 'Ultra Ball', 'PP Up', 'Nugget' ]: numTotalPickups += pickups.get(item, 0) newItems[item] = node.items.get(item, 0) + pickups.get(item, 0) if node.items.get(item, 0) < TO_FIND.get(item, 0): numUsefulPickups += pickups.get(item, 0) # We didn't find anything good, so don't bother recording this action. if numUsefulPickups == 0: continue # Don't forget to count useless pickups because it still affects party size! numTotalPickups += pickups.get('useless', 0) newActions = [] for action in node.actions: newActions.append(action) newActions.append(Action('B', oldSteps)) newPartyItems = node.partyItems + numTotalPickups newAdvances = node.advances + LOWEST_RNG_ADVANCES_PER_BATTLE + oldSteps + 90 newPartySize = party - numTotalPickups if newPartySize < 0: newPartySize = 0 print('uh oh') if newPartySize == 0: # The +90 is for fade out/in on the battle. I need to structure # this better to get rid of that... newActions.append(Action('M', menuFrames(MAX_PARTY_SIZE))) newAdvances += menuFrames(MAX_PARTY_SIZE) newPartyItems = 0 # Check if this new node has already been explored at the same or # earlier frame. inv = hashableInventory(newItems, newPartyItems) seen = bestSeen.get(inv, BIG_NUMBER) if newAdvances >= seen: # Note that if we're equal we also abandon this state. Something # else has already investigated from this position (or better) so # we don't need to contine. continue bestSeen[inv] = newAdvances heapq.heappush( pq, Node(newItems, newPartyItems, newAdvances, newActions))