Last active
September 16, 2024 01:05
-
-
Save Prof9/f7bf07b5255bd07768f1 to your computer and use it in GitHub Desktop.
Mega Man Battle Network 4 Tournament Generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from enum import Enum | |
class Object(object): | |
pass | |
class Version(Enum): | |
RedSun = 0 | |
BlueMoon = 1 | |
class Navi(Enum): | |
Empty = -1 | |
GutsMan = 0 | |
FireMan = 1 | |
NumberMan = 2 | |
AquaMan = 3 | |
Roll = 4 | |
WindMan = 5 | |
MetalMan = 6 | |
WoodMan = 7 | |
ThunderMan = 8 | |
SearchMan = 9 | |
ProtoMan = 10 | |
JunkMan = 11 | |
SparkMan = 12 | |
TopMan = 13 | |
ColdMan = 14 | |
BurnerMan = 15 | |
VideoMan = 16 | |
KendoMan = 17 | |
Ponta1 = 20 | |
Tetsu1 = 21 | |
Ponta2 = 22 | |
Tetsu2 = 23 | |
Flave1 = 24 | |
Riki = 25 | |
Flave2 = 26 | |
Paulie1 = 27 | |
JackBomber = 28 | |
Paulie2 = 29 | |
MegaMan = 30 | |
class Rng(object): | |
def __init__(self): | |
self.seed = 0xA338244F | |
def print_seed(self): | |
print(format(self.seed, "08X")) | |
def next(self): | |
self.seed = (((self.seed << 1) + (self.seed >> 31) + 1) ^ 0x873CA9E5) & 0xFFFFFFFF | |
return self.seed | |
def nextInt(self, max): | |
return self.next() % max | |
class TournamentGenerator(object): | |
def __init__(self, version): | |
self.allSoulNavis = [ | |
[ | |
[Navi.GutsMan, Navi.FireMan], | |
[Navi.Roll, Navi.WindMan], | |
[Navi.ThunderMan, Navi.SearchMan] | |
], [ | |
[Navi.AquaMan, Navi.NumberMan], | |
[Navi.MetalMan, Navi.WoodMan], | |
[Navi.ProtoMan, Navi.JunkMan] | |
] | |
] | |
self.allUniqueNavis = [ | |
[Navi.SparkMan, Navi.TopMan], | |
[Navi.VideoMan, Navi.BurnerMan], | |
[Navi.KendoMan, Navi.ColdMan] | |
] | |
self.allOtherNavis = [ | |
[ | |
[Navi.NumberMan, Navi.AquaMan], | |
[Navi.MetalMan, Navi.WoodMan], | |
[Navi.ProtoMan, Navi.JunkMan] | |
], [ | |
[Navi.GutsMan, Navi.FireMan], | |
[Navi.Roll, Navi.WindMan], | |
[Navi.ThunderMan, Navi.SearchMan] | |
] | |
] | |
self.rng = Rng() | |
self.version = version | |
self.game = 0 | |
self.tourney = 0 | |
self.soulSwapped = False | |
self.soulNavis = [ | |
[Navi.Empty, Navi.Empty], | |
[Navi.Empty, Navi.Empty], | |
[Navi.Empty, Navi.Empty] | |
] | |
self.uniqueNavis = [ | |
[Navi.Empty, Navi.Empty, Navi.Empty, Navi.Empty], | |
[Navi.Empty, Navi.Empty, Navi.Empty, Navi.Empty], | |
[Navi.Empty, Navi.Empty, Navi.Empty, Navi.Empty] | |
] | |
self.normalNavis = [ | |
[Navi.Ponta1, Navi.Ponta2], | |
[Navi.Flave1, Navi.Flave2], | |
[Navi.JackBomber] | |
] | |
self.heelNavis = [ | |
[Navi.Tetsu1, Navi.Tetsu2], | |
[Navi.Riki], | |
[Navi.Paulie1, Navi.Paulie2] | |
] | |
self.doneNavis = [] | |
self.matches = [Navi.Empty, Navi.Empty, Navi.Empty] | |
def addSoulNavis(self): | |
if self.game == 0: | |
if self.tourney == 0 or self.rng.nextInt(2) == 0: | |
self.soulNavis[self.tourney] = self.allSoulNavis[self.version.value][self.tourney] | |
else: | |
self.soulNavis[self.tourney] = self.allSoulNavis[self.version.value][self.tourney][::-1] | |
# TODO: Can be True/False randomly (50%/50%) | |
elif self.tourney == 2 or (self.tourney == 1 and False): | |
if not self.soulSwapped: | |
self.soulSwapped = True | |
self.soulNavis[self.tourney] = self.soulNavis[self.tourney][::-1] | |
def addUniqueNavis(self): | |
navis = self.allUniqueNavis[self.tourney] | |
# Run at least once | |
for i in range(0, max(self.rng.nextInt(4), 1)): | |
a = self.rng.nextInt(8) % len(navis) | |
# Actually what the game does. | |
# Not a typo. | |
b = (a + 1) & (len(navis) - 1) | |
navis[a], navis[b] = navis[b], navis[a] | |
done = [] | |
notDone = [] | |
for navi in navis: | |
if navi in self.doneNavis: | |
done.append(navi) | |
else: | |
notDone.append(navi) | |
for navi in notDone: | |
if navi in self.allOtherNavis[self.version.value][self.tourney]: | |
first = navi | |
break | |
else: | |
if notDone: | |
first = notDone[0] | |
else: | |
first = done[0] | |
# Check if chosen unique Navi is same as last time. | |
if first == self.uniqueNavis[self.tourney][0]: | |
first = done[1] | |
last = self.uniqueNavis[self.tourney][0] | |
if last == Navi.Empty: | |
last = notDone[1] | |
elif last in navis: | |
last = last | |
elif False: | |
print("Check this weird edge case"); | |
# what is this? | |
last = notDone[1] | |
else: | |
print("Check this weird edge case"); | |
# huh? | |
last = Navi.GutsMan | |
self.uniqueNavis[self.tourney][0] = first | |
self.uniqueNavis[self.tourney][len(navis) - 1] = last | |
i = 1 | |
for navi in navis: | |
if navi != first and navi != last: | |
self.uniqueNavis[self.tourney][i] = navi | |
i += 1 | |
if not first in self.doneNavis: | |
self.doneNavis.append(first) | |
def fillBracket(self): | |
soulNavi = self.soulNavis[self.tourney][0] | |
uniqueNavi = self.uniqueNavis[self.tourney][0] | |
normalNavi = self.normalNavis[self.tourney][0] | |
heelNavi = self.heelNavis[self.tourney][0] | |
bracketL = [Navi.MegaMan] | |
bracketR = [] | |
if self.game == 0 and self.tourney == 0: | |
if self.rng.nextInt(2) == 0: | |
bracketL.append(heelNavi) | |
bracketL.append(soulNavi) | |
bracketR.append(uniqueNavi) | |
else: | |
bracketL.append(normalNavi) | |
bracketL.append(soulNavi) | |
bracketR.append(uniqueNavi) | |
else: | |
ra, rb = self.rng.nextInt(4), self.rng.nextInt(2) | |
if ra == 0 and rb == 0: | |
bracketL.append(uniqueNavi) | |
bracketL.append(normalNavi) | |
bracketR.append(soulNavi) | |
bracketR.append(heelNavi) | |
elif ra == 0 and rb == 1: | |
bracketL.append(uniqueNavi) | |
bracketL.append(heelNavi) | |
bracketR.append(soulNavi) | |
bracketR.append(normalNavi) | |
elif ra == 1 and rb == 0: | |
bracketL.append(soulNavi) | |
bracketL.append(normalNavi) | |
bracketR.append(uniqueNavi) | |
bracketR.append(heelNavi) | |
elif ra == 1 and rb == 1: | |
bracketL.append(soulNavi) | |
bracketL.append(heelNavi) | |
bracketR.append(uniqueNavi) | |
bracketR.append(normalNavi) | |
elif ra == 2 and rb == 0: | |
bracketL.append(normalNavi) | |
bracketL.append(uniqueNavi) | |
bracketR.append(soulNavi) | |
elif ra == 2 and rb == 1: | |
bracketL.append(normalNavi) | |
bracketL.append(soulNavi) | |
bracketR.append(uniqueNavi) | |
elif ra == 3 and rb == 0: | |
bracketL.append(heelNavi) | |
bracketL.append(uniqueNavi) | |
bracketR.append(soulNavi) | |
elif ra == 3 and rb == 1: | |
bracketL.append(heelNavi) | |
bracketL.append(soulNavi) | |
bracketR.append(uniqueNavi) | |
loseNavis = [] | |
loseNavis.append(self.soulNavis[self.tourney][1]) | |
for navi in reversed(self.uniqueNavis[self.tourney]): | |
if navi != Navi.Empty: | |
loseNavis.append(navi) | |
break | |
for navi in reversed(self.normalNavis[self.tourney]): | |
if navi != Navi.Empty: | |
if navi not in (bracketL + bracketR): | |
loseNavis.append(navi) | |
break | |
for navi in reversed(self.heelNavis[self.tourney]): | |
if navi != Navi.Empty: | |
if navi not in (bracketL + bracketR): | |
loseNavis.append(navi) | |
break | |
# This should be len(loseNavis) - 1, but they forgot to push that onto the stack | |
# Put a random lose Navi in the final left bracket slot | |
prev = self.rng.seed >> 31 | |
r = self.rng.nextInt(8) | |
while (r > prev): | |
r -= 1 + prev | |
bracketL.append(loseNavis[r]) | |
# Append unfinished right bracket to finished left bracket | |
bracket = bracketL + bracketR | |
# Gather all leftover Navis | |
allNavis = [] | |
allNavis += self.soulNavis[self.tourney] | |
allNavis += self.uniqueNavis[self.tourney] | |
allNavis += self.normalNavis[self.tourney] | |
allNavis += self.heelNavis[self.tourney] | |
leftNavis = [] | |
for navi in allNavis: | |
if navi != Navi.Empty and navi not in bracket: | |
leftNavis.append(navi) | |
# Shuffle leftover Navis | |
for i in range(5): | |
ra = self.rng.nextInt(8) % len(leftNavis) | |
rb = (ra + 1) % len(leftNavis) | |
leftNavis[ra], leftNavis[rb] = leftNavis[rb], leftNavis[ra] | |
# Fill rest of bracket with leftover Navis | |
r = self.rng.nextInt(8) % len(leftNavis) | |
while (len(bracket) < 8): | |
bracket.append(leftNavis[r]) | |
r = (r + 1) % len(leftNavis) | |
# Flip matchups | |
r = self.rng.next() | |
for i in range(0, 8, 2): | |
if r & 1: | |
bracket[i], bracket[i + 1] = bracket[i + 1], bracket[i] | |
r >>= 4 | |
# Flip semifinals | |
if (self.rng.nextInt(2) == 0): | |
bracket[0], bracket[2] = bracket[2], bracket[0] | |
bracket[1], bracket[3] = bracket[3], bracket[1] | |
bracket[4], bracket[6] = bracket[6], bracket[4] | |
bracket[5], bracket[7] = bracket[7], bracket[5] | |
# Flip finals | |
if (self.rng.nextInt(2) == 0): | |
bracket[0], bracket[4] = bracket[4], bracket[0] | |
bracket[1], bracket[5] = bracket[5], bracket[1] | |
bracket[2], bracket[6] = bracket[6], bracket[2] | |
bracket[3], bracket[7] = bracket[7], bracket[3] | |
for i in range(8): | |
navi = bracket[i] | |
if navi == Navi.MegaMan: | |
priority = 0 | |
elif navi in self.soulNavis[self.tourney]: | |
priority = self.soulNavis[self.tourney].index(navi) | |
elif navi in self.uniqueNavis[self.tourney]: | |
priority = self.uniqueNavis[self.tourney].index(navi) | |
elif navi in self.normalNavis[self.tourney]: | |
priority = self.normalNavis[self.tourney].index(navi) | |
elif navi in self.heelNavis[self.tourney]: | |
priority = self.heelNavis[self.tourney].index(navi) | |
self.bracket[i] = Object() | |
self.bracket[i].navi = navi | |
self.bracket[i].priority = priority | |
def getMatches(self): | |
self.matches[0] = self.getMatch(self.bracket) | |
round2 = self.advanceRound(self.bracket) | |
self.matches[1] = self.getMatch(round2) | |
round3 = self.advanceRound(round2) | |
self.matches[2] = self.getMatch(round3) | |
return self.matches | |
def advanceRound(self, round): | |
next = [] | |
for i in range(len(round) >> 1): | |
left = round[i * 2] | |
right = round[i * 2 + 1] | |
next.append(self.matchResult(left, right)) | |
return next | |
def getMatch(self, round): | |
for i in range(0, len(round), 2): | |
if round[i].navi == Navi.MegaMan: | |
return round[i + 1].navi | |
elif round[i + 1].navi == Navi.MegaMan: | |
return round[i].navi | |
def matchResult(self, left, right): | |
if left.navi == Navi.MegaMan: | |
return left | |
elif right.navi == Navi.MegaMan: | |
return right | |
if left.priority < right.priority: | |
return left | |
elif right.priority < left.priority: | |
return right | |
if left.navi.value < 20 and right.navi.value >= 20: | |
return left | |
elif right.navi.value < 20 and left.navi.value >= 20: | |
return right | |
if self.rng.nextInt(2) == 0: | |
return left | |
else: | |
return right | |
def create(self): | |
self.bracket = [[Navi.Empty, 0]] * 8 | |
self.addSoulNavis() | |
self.addUniqueNavis() | |
self.fillBracket() | |
self.tourney += 1 | |
if (self.tourney == 3): | |
self.game += 1 | |
self.tourney = 0 | |
return self.getMatches() | |
def padNaviName(self, name): | |
r = name | |
if r[-1] in "12": | |
r = name[:-1] | |
if len(r) < 8: | |
r += "\t" | |
return r | |
def bracket_to_string(self): | |
s = "" | |
for entry in self.bracket: | |
s += "\t" + self.padNaviName(entry.navi.name) | |
return s[1:] | |
def matches_to_string(self): | |
s = "" | |
for navi in self.matches: | |
s += "\t" + self.padNaviName(navi.name) | |
return s[1:] | |
rng = Rng() | |
for i in range(10000): | |
tg = TournamentGenerator(Version.BlueMoon) | |
tg.rng.seed = rng.seed | |
s = "frame " + str(i).rjust(4) + "\t" | |
s += "seed " + format(tg.rng.seed, "08X") | |
if (True): | |
tg.create() | |
s += "\n\t1:\t" + tg.bracket_to_string() | |
tg.create() | |
s += "\n\t2:\t" + tg.bracket_to_string() | |
tg.create() | |
s += "\n\t3:\t" + tg.bracket_to_string() | |
else: | |
tg.create() | |
s += "\t" + tg.matches_to_string() | |
tg.create() | |
s += "\t" + tg.matches_to_string() | |
tg.create() | |
s += "\t" + tg.matches_to_string() | |
rng.next() | |
print(s) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is there a patch for this tournament generator