diff options
Diffstat (limited to 'pokemontools/vba/autoplayer.py')
-rw-r--r-- | pokemontools/vba/autoplayer.py | 820 |
1 files changed, 454 insertions, 366 deletions
diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 9aa8f4a..af14d47 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -4,492 +4,580 @@ Programmatic speedrun of Pokémon Crystal """ import os -# bring in the emulator and basic tools -import vba - -def main(): - """ - Start the game. - """ - vba.load_rom() +import pokemontools.configuration as configuration - # get past the opening sequence - skip_intro() - - # walk to mom and handle her text - handle_mom() - - # walk outside into new bark town - walk_into_new_bark_town() - - # walk to elm and do whatever he wants - handle_elm("totodile") - - new_bark_level_grind(10, skip=False) +# bring in the emulator and basic tools +import vba as _vba def skippable(func): """ Makes a function skippable. - Saves the state before and after the function runs. - Pass "skip=True" to the function to load the previous save - state from when the function finished. + Saves the state before and after the function runs. Pass "skip=True" to the + function to load the previous save state from when the function finished. """ def wrapped_function(*args, **kwargs): + self = args[0] skip = True + override = True if "skip" in kwargs.keys(): skip = kwargs["skip"] del kwargs["skip"] + if "override" in kwargs.keys(): + override = kwargs["override"] + del kwargs["override"] + # override skip if there's no save if skip: full_name = func.__name__ + "-end.sav" - if not os.path.exists(os.path.join(vba.save_state_path, full_name)): + if not os.path.exists(os.path.join(self.config.save_state_path, full_name)): skip = False return_value = None if not skip: - vba.save_state(func.__name__ + "-start", override=True) + if override: + self.cry.save_state(func.__name__ + "-start", override=override) + return_value = func(*args, **kwargs) - vba.save_state(func.__name__ + "-end", override=True) + + if override: + self.cry.save_state(func.__name__ + "-end", override=override) elif skip: - vba.set_state(vba.load_state(func.__name__ + "-end")) + self.cry.vba.state = self.cry.load_state(func.__name__ + "-end") return return_value return wrapped_function -@skippable -def skip_intro(): +class Runner(object): """ - Skip the game boot intro sequence. + ``Runner`` is used to represent a set of functions that control an instance + of the emulator. This allows for automated runs of games. """ + pass - # copyright sequence - vba.nstep(400) +class SpeedRunner(Runner): + def __init__(self, cry=None, config=None): + super(SpeedRunner, self).__init__() - # skip the ditto sequence - vba.press("a") - vba.nstep(100) + self.cry = cry - # skip the start screen - vba.press("start") - vba.nstep(100) + if not config: + config = configuration.Config() - # click "new game" - vba.press("a", holdsteps=50, aftersteps=1) + self.config = config - # skip text up to "Are you a boy? Or are you a girl?" - vba.crystal.text_wait() + def setup(self): + """ + Configure this ``Runner`` instance to contain a reference to an active + emulator session. + """ + if not self.cry: + self.cry = _vba.crystal(config=self.config) - # select "Boy" - vba.press("a", holdsteps=50, aftersteps=1) + def main(self): + """ + Main entry point for complete control of the game as the main player. + """ + # get past the opening sequence + self.skip_intro(skip=True) - # text until "What time is it?" - vba.crystal.text_wait() + # walk to mom and handle her text + self.handle_mom(skip=True) - # select 10 o'clock - vba.press("a", holdsteps=50, aftersteps=1) + # walk outside into new bark town + self.walk_into_new_bark_town(skip=True) - # yes i mean it - vba.press("a", holdsteps=50, aftersteps=1) + # walk to elm and do whatever he wants + self.handle_elm("totodile", skip=True) - # "How many minutes?" 0 min. - vba.press("a", holdsteps=50, aftersteps=1) + self.new_bark_level_grind(17, skip=False) - # "Who! 0 min.?" yes/no select yes - vba.press("a", holdsteps=50, aftersteps=1) + @skippable + def skip_intro(self, stop_at_name_selection=False): + """ + Skip the game boot intro sequence. + """ - # read text until name selection - vba.crystal.text_wait() + # copyright sequence + self.cry.nstep(400) - # select "Chris" - vba.press("d", holdsteps=10, aftersteps=1) - vba.press("a", holdsteps=50, aftersteps=1) + # skip the ditto sequence + self.cry.vba.press("a") + self.cry.nstep(100) - def overworldcheck(): - """ - A basic check for when the game starts. - """ - return vba.get_memory_at(0xcfb1) != 0 + # skip the start screen + self.cry.vba.press("start") + self.cry.nstep(100) - # go until the introduction is done - vba.crystal.text_wait(callback=overworldcheck) + # click "new game" + self.cry.vba.press("a", hold=50, after=1) - return + # skip text up to "Are you a boy? Or are you a girl?" + self.cry.text_wait() -@skippable -def handle_mom(): - """ - Walk to mom. Handle her speech and questions. - """ + # select "Boy" + self.cry.vba.press("a", hold=50, after=1) - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") + # text until "What time is it?" + self.cry.text_wait() - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") + # select 10 o'clock + self.cry.vba.press("a", hold=50, after=1) - vba.crystal.move("d") - vba.crystal.move("d") + # yes i mean it + self.cry.vba.press("a", hold=50, after=1) - # move into mom's line of sight - vba.crystal.move("d") + # "How many minutes?" 0 min. + self.cry.vba.press("a", hold=50, after=1) - # let mom talk until "What day is it?" - vba.crystal.text_wait() + # "Who! 0 min.?" yes/no select yes + self.cry.vba.press("a", hold=50, after=1) - # "What day is it?" Sunday - vba.press("a", holdsteps=10) # Sunday + # read text until name selection + self.cry.text_wait() - vba.crystal.text_wait() + if stop_at_name_selection: + return - # "SUNDAY, is it?" yes/no - vba.press("a", holdsteps=10) # yes + # select "Chris" + self.cry.vba.press("d", hold=10, after=1) + self.cry.vba.press("a", hold=50, after=1) - vba.crystal.text_wait() + def overworldcheck(): + """ + A basic check for when the game starts. + """ + return self.cry.vba.memory[0xcfb1] != 0 - # "Is it Daylight Saving Time now?" yes/no - vba.press("a", holdsteps=10) # yes + # go until the introduction is done + self.cry.text_wait(callback=overworldcheck) - vba.crystal.text_wait() + return - # "AM DST, is that OK?" yes/no - vba.press("a", holdsteps=10) # yes + @skippable + def handle_mom(self): + """ + Walk to mom. Handle her speech and questions. + """ - # text until "know how to use the PHONE?" yes/no - vba.crystal.text_wait() + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") - # press yes - vba.press("a", holdsteps=10) + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") - # wait until mom is done talking - vba.crystal.text_wait() + self.cry.move("d") + self.cry.move("d") - # wait until the script is done running - vba.crystal.wait_for_script_running() + # move into mom's line of sight + self.cry.move("d") - return + # let mom talk until "What day is it?" + self.cry.text_wait() -@skippable -def walk_into_new_bark_town(): - """ - Walk outside after talking with mom. - """ + # "What day is it?" Sunday + self.cry.vba.press("a", hold=10) # Sunday - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("l") - vba.crystal.move("l") + self.cry.text_wait() - # walk outside - vba.crystal.move("d") + # "SUNDAY, is it?" yes/no + self.cry.vba.press("a", hold=10) # yes -@skippable -def handle_elm(starter_choice): - """ - Walk to Elm's Lab and get a starter. - """ + self.cry.text_wait() - # walk to the lab - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("u") - vba.crystal.move("u") + # "Is it Daylight Saving Time now?" yes/no + self.cry.vba.press("a", hold=10) # yes - # walk into the lab - vba.crystal.move("u") + self.cry.text_wait() - # talk to elm - vba.crystal.text_wait() + # "AM DST, is that OK?" yes/no + self.cry.vba.press("a", hold=10) # yes - # "that I recently caught." yes/no - vba.press("a", holdsteps=10) # yes + # text until "know how to use the PHONE?" yes/no + self.cry.text_wait() - # talk to elm some more - vba.crystal.text_wait() + # press yes + self.cry.vba.press("a", hold=10) - # talking isn't done yet.. - vba.crystal.text_wait() - vba.crystal.text_wait() - vba.crystal.text_wait() + # wait until mom is done talking + self.cry.text_wait() - # wait until the script is done running - vba.crystal.wait_for_script_running() + # wait until the script is done running + self.cry.wait_for_script_running() - # move toward the pokeballs - vba.crystal.move("r") + return - # move to cyndaquil - vba.crystal.move("r") + @skippable + def walk_into_new_bark_town(self): + """ + Walk outside after talking with mom. + """ - moves = 0 + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("l") + self.cry.move("l") + + # walk outside + self.cry.move("d") + + @skippable + def handle_elm(self, starter_choice): + """ + Walk to Elm's Lab and get a starter. + """ + + # walk to the lab + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("u") + self.cry.move("u") + + # walk into the lab + self.cry.move("u") + + # talk to elm + self.cry.text_wait() + + # "that I recently caught." yes/no + self.cry.vba.press("a", hold=10) # yes + + # talk to elm some more + self.cry.text_wait() + + # talking isn't done yet.. + self.cry.text_wait() + self.cry.text_wait() + self.cry.text_wait() + + # wait until the script is done running + self.cry.wait_for_script_running() + + # move toward the pokeballs + self.cry.move("r") + + # move to cyndaquil + self.cry.move("r") - if starter_choice.lower() == "cyndaquil": moves = 0 - if starter_choice.lower() == "totodile": - moves = 1 - else: - moves = 2 - for each in range(0, moves): - vba.crystal.move("r") + if starter_choice.lower() == "cyndaquil": + moves = 0 + elif starter_choice.lower() == "totodile": + moves = 1 + else: + moves = 2 - # face the pokeball - vba.crystal.move("u") + for each in range(0, moves): + self.cry.move("r") - # select it - vba.press("a", holdsteps=10, aftersteps=0) + # face the pokeball + self.cry.move("u") - # wait for the image to pop up - vba.crystal.text_wait() + # select it + self.cry.vba.press("a", hold=10, after=0) - # wait for the image to close - vba.crystal.text_wait() + # wait for the image to pop up + self.cry.text_wait() - # wait for the yes/no box - vba.crystal.text_wait() + # wait for the image to close + self.cry.text_wait() - # press yes - vba.press("a", holdsteps=10, aftersteps=0) + # wait for the yes/no box + self.cry.text_wait() - # wait for elm to talk a bit - vba.crystal.text_wait() + # press yes + self.cry.vba.press("a", hold=10, after=0) - # TODO: why didn't that finish his talking? - vba.crystal.text_wait() + # wait for elm to talk a bit + self.cry.text_wait() - # give a nickname? yes/no - vba.press("d", holdsteps=10, aftersteps=0) # move to "no" - vba.press("a", holdsteps=10, aftersteps=0) # no + # TODO: why didn't that finish his talking? + self.cry.text_wait() - # TODO: why didn't this wait until he was completely done? - vba.crystal.text_wait() - vba.crystal.text_wait() + # give a nickname? yes/no + self.cry.vba.press("d", hold=10, after=0) # move to "no" + self.cry.vba.press("a", hold=10, after=0) # no - # get the phone number - vba.crystal.text_wait() + # TODO: why didn't this wait until he was completely done? + self.cry.text_wait() + self.cry.text_wait() - # talk with elm a bit more - vba.crystal.text_wait() + # get the phone number + self.cry.text_wait() - # TODO: and again.. wtf? - vba.crystal.text_wait() + # talk with elm a bit more + self.cry.text_wait() - # wait until the script is done running - vba.crystal.wait_for_script_running() + # wait until the script is done running + self.cry.wait_for_script_running() - # move down - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") + # move down + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") - # move into the researcher's line of sight - vba.crystal.move("d") + # move into the researcher's line of sight + self.cry.move("d") - # get the potion from the person - vba.crystal.text_wait() - vba.crystal.text_wait() + # get the potion from the person + self.cry.text_wait() + self.cry.text_wait() - # wait for the script to end - vba.crystal.wait_for_script_running() + # wait for the script to end + self.cry.wait_for_script_running() - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") - # go outside - vba.crystal.move("d") + # go outside + self.cry.move("d") - return + return -@skippable -def new_bark_level_grind(level): - """ - Do level grinding in New Bark. + @skippable + def new_bark_level_grind(self, level, walk_to_grass=True): + """ + Do level grinding in New Bark. - Starting just outside of Elm's Lab, do some level grinding until the first - partymon level is equal to the given value.. - """ + Starting just outside of Elm's Lab, do some level grinding until the + first partymon level is equal to the given value.. + """ - # walk to the grass area - new_bark_level_grind_walk_to_grass(skip=False) + # walk to the grass area + if walk_to_grass: + self.new_bark_level_grind_walk_to_grass(skip=False) - # TODO: walk around in grass, handle battles - walk = ["d", "d", "u", "d", "u", "d"] - for direction in walk: - vba.crystal.move(direction) + last_direction = "u" - # wait for wild battle to completely start - vba.crystal.text_wait() + # walk around in the grass until a battle happens + while self.cry.vba.memory[0xd22d] == 0: + if last_direction == "u": + direction = "d" + else: + direction = "u" - attacks = 5 + self.cry.move(direction) - while attacks > 0: - # FIGHT - vba.press("a", holdsteps=10, aftersteps=1) + last_direction = direction - # wait to select a move - vba.crystal.text_wait() + # wait for wild battle to completely start + self.cry.text_wait() - # SCRATCH - vba.press("a", holdsteps=10, aftersteps=1) + attacks = 5 - # wait for the move to be over - vba.crystal.text_wait() + while attacks > 0: + # FIGHT + self.cry.vba.press("a", hold=10, after=1) - hp = ((vba.get_memory_at(0xd218) << 8) | vba.get_memory_at(0xd217)) - print "enemy hp is: " + str(hp) + # wait to select a move + self.cry.text_wait() - if hp == 0: - print "enemy hp is zero, exiting" - break - else: + # SCRATCH + self.cry.vba.press("a", hold=10, after=1) + + # wait for the move to be over + self.cry.text_wait() + + hp = self.cry.get_enemy_hp() print "enemy hp is: " + str(hp) - attacks = attacks - 1 - - while vba.get_memory_at(0xd22d) != 0: - vba.press("a", holdsteps=10, aftersteps=1) - - # wait for the map to finish loading - vba.nstep(50) - - print "okay, back in the overworld" - - # move up - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - - # move into new bark town - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - - # move up - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - - # move to the door - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - - # walk in - vba.crystal.move("u") - - # move up to the healing thing - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("l") - vba.crystal.move("l") - - # face it - vba.crystal.move("u") - - # interact - vba.press("a", holdsteps=10, aftersteps=1) - - # wait for yes/no box - vba.crystal.text_wait() - - # press yes - vba.press("a", holdsteps=10, aftersteps=1) - - # TODO: when is healing done? - - # wait until the script is done running - vba.crystal.wait_for_script_running() - - # wait for it to be really really done - vba.nstep(50) - - vba.crystal.move("r") - vba.crystal.move("r") - - # move to the door - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - - # walk out - vba.crystal.move("d") - - # check partymon1 level - if vba.get_memory_at(0xdcfe) < level: - new_bark_level_grind(level, skip=False) - else: - return + if hp == 0: + print "enemy hp is zero, exiting" + break + else: + print "enemy hp is: " + str(hp) + + attacks = attacks - 1 + + while self.cry.vba.memory[0xd22d] != 0: + self.cry.vba.press("a", hold=10, after=1) + + # wait for the map to finish loading + self.cry.vba.step(count=50) + + # This is used to handle any additional textbox that might be up on the + # screen. The debug parameter is set to True so that max_wait is + # enabled. This might be a textbox that is still waiting around because + # of some faint during the battle. I am not completely sure why this + # happens. + self.cry.text_wait(max_wait=30, debug=True) + + print "okay, back in the overworld" + + cur_hp = ((self.cry.vba.memory[0xdd01] << 8) | self.cry.vba.memory[0xdd02]) + move_pp = self.cry.vba.memory[0xdcf6] # move 1 pp + + # if pokemon health is >20, just continue + # if move 1 PP is 0, just continue + if cur_hp > 20 and move_pp > 5 and self.cry.vba.memory[0xdcfe] < level: + self.cry.move("u") + return self.new_bark_level_grind(level, walk_to_grass=False, skip=False) + + # move up + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + + # move into new bark town + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + + # move up + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + + # move to the door + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + + # walk in + self.cry.move("u") + + # move up to the healing thing + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("l") + self.cry.move("l") + + # face it + self.cry.move("u") + + # interact + self.cry.vba.press("a", hold=10, after=1) + + # wait for yes/no box + self.cry.text_wait() + + # press yes + self.cry.vba.press("a", hold=10, after=1) + + # TODO: when is healing done? + + # wait until the script is done running + self.cry.wait_for_script_running() + + # wait for it to be really really done + self.cry.vba.step(count=50) + + self.cry.move("r") + self.cry.move("r") + + # move to the door + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + + # walk out + self.cry.move("d") + + # check partymon1 level + if self.cry.vba.memory[0xdcfe] < level: + self.new_bark_level_grind(level, skip=False) + else: + return + + @skippable + def new_bark_level_grind_walk_to_grass(self): + """ + Move to just above the grass from outside Elm's lab. + """ + + self.cry.move("d") + self.cry.move("d") + + self.cry.move("l") + self.cry.move("l") + + self.cry.move("d") + self.cry.move("d") + + self.cry.move("l") + self.cry.move("l") -@skippable -def new_bark_level_grind_walk_to_grass(): + # move to route 29 past the trees + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + + # move to just above the grass + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + +def bootstrap(runner=None, cry=None): """ - Move to just above the grass from outside Elm's lab. + Setup the initial game and return the state. This skips the intro and + performs some other actions to get the game to a reasonable starting state. """ + if not runner: + runner = SpeedRunner(cry=cry) + runner.setup() + + # skip=False means always run the skip_intro function regardless of the + # presence of a saved after state. + runner.skip_intro(skip=True) + + # keep a reference of the current state + state = runner.cry.vba.state + + runner.cry.vba.shutdown() - vba.crystal.move("d") - vba.crystal.move("d") - - vba.crystal.move("l") - vba.crystal.move("l") - - vba.crystal.move("d") - vba.crystal.move("d") - - vba.crystal.move("l") - vba.crystal.move("l") - - # move to route 29 past the trees - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - vba.crystal.move("l") - - # move to just above the grass - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") + return state + +def main(): + """ + Setup a basic ``SpeedRunner`` instance and then run the runner. + """ + runner = SpeedRunner() + runner.setup() + return runner.main() if __name__ == "__main__": main() |