diff options
Diffstat (limited to 'pokemontools/vba/battle.py')
| -rw-r--r-- | pokemontools/vba/battle.py | 188 | 
1 files changed, 188 insertions, 0 deletions
| diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py new file mode 100644 index 0000000..5aeb923 --- /dev/null +++ b/pokemontools/vba/battle.py @@ -0,0 +1,188 @@ +""" +Code that attempts to model a battle. +""" + +from pokemontools.vba.vba import crystal as emulator +import pokemontools.vba.vba as vba + +class BattleException(Exception): +    """ +    Something went terribly wrong in a battle. +    """ + +class EmulatorController(object): +    """ +    Controls the emulator. I don't have a good reason for this. +    """ + +class Battle(EmulatorController): +    """ +    Wrapper around the battle routine inside of the game. This object controls +    the emulator and provides a sanitized interface for interacting with a +    battle through python. +    """ + +    def __init__(self, emulator=None): +        """ +        Setup the battle. +        """ +        self.emulator = emulator + +    def is_in_battle(self): +        """ +        @rtype: bool +        """ +        return self.emulator.is_in_battle() + +    def is_input_required(self): +        """ +        Detects if the battle is waiting for player input. +        """ +        return self.is_player_turn() or self.is_mandatory_switch() + +    def is_fight_pack_run_menu(self): +        """ +        Attempts to detect if the current menu is fight-pack-run. This is only +        for whether or not the player needs to choose what to do next. +        """ +        signs = ["FIGHT", "PACK", "RUN"] +        screentext = self.emulator.get_text() +        return all([sign in screentext for sign in signs]) + +    def is_player_turn(self): +        """ +        Detects if the battle is waiting for the player to choose an attack. +        """ +        return self.is_fight_pack_run_menu() + +    def is_mandatory_switch(self): +        """ +        Detects if the battle is waiting for the player to choose a next +        pokemon. +        """ +        # TODO: test when "no" fails to escape for wild battles. +        # trainer battles: menu asks to select the next mon +        # wild battles: yes/no box first +        # The following conditions are probably sufficient: +        #   1) current pokemon hp is 0 +        #   2) game is polling for input + +        # TODO: detect the mandatory switch menu +        return False + +    def skip_start_text(self, max_loops=20): +        """ +        Skip any initial conversation until the player can select an action. +        This includes skipping any text that appears on a map from an NPC as +        well as text that appears prior to the first time the action selection +        menu appears. +        """ +        if not self.is_in_battle(): +            while not self.is_in_battle() and max_loops > 0: +                self.emulator.text_wait() +                max_loops -= 1 + +            if max_loops <= 0: +                raise Exception("Couldn't start the battle.") +        else: +            self.emulator.text_wait() + +    def skip_end_text(self, loops=20): +        """ +        Skip through any text that appears after the final attack. +        """ +        if not self.is_in_battle(): +            # TODO: keep talking until the character can move? A battle can be +            # triggered inside of a script, and after the battle is ver the +            # player may not be able to move until the script is done. The +            # script might only finish after other player input is given, so +            # using "text_wait() until the player can move" is a bad idea here. +            self.emulator.text_wait() +        else: +            while self.is_in_battle() and loops > 0: +                self.emulator.text_wait() +                loops -= 1 + +            if loops <= 0: +                raise Exception("Couldn't get out of the battle.") + +    def skip_until_input_required(self): +        """ +        Waits until the battle needs player input. +        """ +        while not self.is_input_required(): +            self.emulator.text_wait() + +    def run(self): +        """ +        Step through the entire battle. +        """ +        # Advance to the battle from either of these states: +        #   1) the player is talking with an npc +        #   2) the battle has already started but there's initial text +        # xyz wants to battle, a wild foobar appeared +        self.skip_start_text() + +        while self.is_in_battle(): +            self.skip_until_input_required() + +            if self.is_player_turn(): +                # battle hook provides input to handle this situation +                self.handle_turn() +            elif self.is_mandatory_switch(): +                # battle hook provides input to handle this situation too +                self.handle_mandatory_switch() +            else: +                raise BattleException("unknown state, aborting") + +        # "how did i lose? wah" +        self.skip_end_text() + +        # TODO: return should indicate win/loss (blackout) + +    def handle_mandatory_switch(self): +        """ +        Something fainted, pick the next mon. +        """ +        raise NotImplementedError + +    def handle_turn(self): +        """ +        Take actions inside of a battle based on the game state. +        """ +        raise NotImplementedError + +class BattleStrategy(Battle): +    """ +    Throw a pokeball until everyone dies. +    """ + +    def handle_mandatory_switch(self): +        """ +        Something fainted, pick the next mon. +        """ +        for pokemon in self.emulator.party: +            if pokemon.hp > 0: +                break +        else: +            # the game failed to do a blackout.. not sure what to do now. +            raise BattleException("No partymons left. wtf?") + +        return pokemon.id + +    def handle_turn(self): +        """ +        Take actions inside of a battle based on the game state. +        """ +        self.throw_pokeball() + +class SimpleBattleStrategy(BattleStrategy): +    """ +    Attack the enemy with the first move. +    """ + +    def handle_turn(self): +        """ +        Always attack the enemy with the first move. +        """ +        self.attack(self.battle.party[0].moves[0].name) | 
