diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-11-11 10:36:33 -0800 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-11-11 10:36:33 -0800 |
commit | a11b084a2824dbe9c1df84d9ea205b8495f3da13 (patch) | |
tree | be94d8ec6e86db5e883db5ebe9edffc2bdc951ff /pokemontools/vba/battle.py | |
parent | ff55a23ece3f74ad30ef2f10d81c54f464d5988b (diff) | |
parent | 966985411f01b799fa71f4823da7a8cd6d9cc47b (diff) |
Merge pull request #39 from kanzure/vba-automation
More VBA automation
Diffstat (limited to 'pokemontools/vba/battle.py')
-rw-r--r-- | pokemontools/vba/battle.py | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py new file mode 100644 index 0000000..87cd7b1 --- /dev/null +++ b/pokemontools/vba/battle.py @@ -0,0 +1,193 @@ +""" +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 + + if "CANCEL Which ?" in self.emulator.get_text(): + return True + else: + 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() + + # let the text draw so that the state is more obvious + self.emulator.vba.step(count=10) + + 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) |