pmariglia / showdown

A Pokemon Showdown Battle Bot written in Python
GNU General Public License v3.0
254 stars 175 forks source link

Inferring choicescarf fails if opponent knocks out the bot's Pokemon #78

Open pmariglia opened 3 years ago

pmariglia commented 3 years ago

This line in the check_choicescarf function causes the check to exit early when exactly 2 moves are not found from the turn's log. This means that if both Pokemon select a same-priority move but the opponent (being faster) knocks out the bot's pokemon before the bot's pokemon gets a chance to move, then the check is stopped when in reality there is enough information to infer a choicescarf from the turn.

Checking for exactly 2 moves was done so that if either side decided to switch, this check wouldn't be done.

Here is an example test that will need to be included in a commit fixing this. It should be put in this test class

    def test_guess_choicescarf_when_opponent_knocks_out_the_bots_pokemon(self):
        self.battle.user.active.stats[constants.SPEED] = 210  # opponent's speed should not be greater than 207 (max speed caterpie)

        messages = [
            '|move|p2a: Caterpie|Tackle|',
            '|-damage|p1a: Caterpie|0 fnt',
            '|faint|p1a: Caterpie',
            '|',
            '|upkeep',
        ]

        check_choicescarf(self.battle, messages)

        self.assertEqual('choicescarf', self.battle.opponent.active.item)

There are undoubtedly more edge-cases to test - this is just one example. The actual solution will require far more tests. For example: a move versus a switch-out.

mancho2000 commented 2 years ago

Hey, I tried solving this but not sure if its correct. Let me know what you think:

moves = [get_move_information(m) for m in msg_lines if m.startswith('|move|')]
#get any switches
switch_moves = [m for m in msg_lines if m.startswith('|switch|')]

#check if the only attack is from opponent and there are no switches
if moves[0][0].startswith(battle.opponent.name) and len(moves) == 1 and len(switch_moves) == 0:
    pass
elif len(switch_moves) > 0 or moves[0][0].startswith(battle.user.name) or moves[0][1][constants.PRIORITY] != moves[1][1][constants.PRIORITY]: # return if there are any switches 
    return
pmariglia commented 2 years ago

This alone isn't enough. As it is, it will actually error in certain situations because it is trying to access moves[0][0] before checking that there is at least 1 move with if len(moves) == 2.

A small refactor of what you have here would in theory work, but I remembered why this was something I didn't add in the first place.

In the event that the opponent moves first and knocks out the user's active pokemon, the move that the user selected isn't available in the battle-log (because it never got a chance to occur). The decision is important however because the priority of the moves need to be compared to ensure that the bot would've gone first in the event that the opponent didn't have a choicescarf. This requires using the LastUsedMove attribute of the Battler object.

There's a bunch more edge cases that need to be considered when 2 moves aren't shown in the log. For example if the opponent knocked you out with sucker-punch, the move you choice is important in inferring a choice scarf.