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() | 
