This project is a fork of pmariglia's showdown bots project (meant to be an application) that extracts the battle engine and creates an installable package.
It can be used to determine the different types of transpositions a pair of moves will result in.
This battle engine is meant to capture important aspects of Pokemon for the purposes of competitive single battles. It is nowhere near as complete or robust as the PokemonShowdown battle engine.
The battle engine operates using State, Side, and Pokemon objects.
Note that any string values used within the engine (pokemon names, move names, ability names, item names, etc) must be stripped of spaces/special characters.
To convert values you can use the normalize_name
function
>>> from poke_engine.helpers import normalize_name
>>> normalize_name('Pikachu')
'pikachu'
>>> normalize_name('Choice Scarf')
'choicescarf'
>>> normalize_name('Giratina-Origin')
'giratinaorigin'
>>> normalize_name('Flabébé')
'flabebe'
from poke_engine import Pokemon
pokemon = Pokemon(
# mandatory upon initialization
identifier='pikachu',
level=100,
types=['electric'],
hp=100,
maxhp=100,
ability='static',
item='lightball',
attack=100,
defense=100,
special_attack=100,
special_defense=100,
speed=100,
# the remaining attributes are optional and have default values if not specified
# nature is a string, evs are a tuple
nature="serious",
evs=(85,) * 6,
# boosts: integer value between -6 and 6
attack_boost=0,
defense_boost=0,
special_attack_boost=0,
special_defense_boost=0,
speed_boost=0,
accuracy_boost=0,
evasion_boost=0,
# status: <string> or None
status=None,
# volatile_status: <set>
volatile_status=set(),
# moves: <list> of <dict>
moves=[
{'id': 'volttackle', 'disabled': False, 'current_pp': 8},
]
)
This object represents one side of battle.
It contains an active
Pokemon , a dictionary of reserve
Pokemon, and a dictionary of side_conditions
from poke_engine import Side
from poke_engine import Pokemon
side = Side(
active=Pokemon(...),
reserve={
'caterpie': Pokemon(...),
'pidgey': Pokemon(...),
...
},
wish=(0, 0),
side_conditions={
'stealth_rock': 1,
'spikes': 3,
'toxic_spikes': 2,
'tailwind': 1
},
future_sight=(0, 0)
)
This object represents the entire battle.
from poke_engine import State
from poke_engine import Side
state = State(
user=Side(...),
opponent=Side(...),
weather='sunnyday',
field='electricterrain',
trick_room=False
)
The primary feature of this battle engine is the ability to generate and apply instructions.
Instructions are a list of tuples. They can be applied and reversed to mutate the state.
from poke_engine import State
from poke_engine import StateMutator
state = State(...) # initialize your state
state.user.active.hp = 100
print(state.user.active.hp) # prints '100'
mutator = StateMutator(state)
instructions = [
('damage', 'user', 1)
]
mutator.apply(instructions)
print(state.user.active.hp) # prints '99'
mutator.reverse(instructions)
print(state.user.active.hp) # prints '100'
Instructions can be generated from a state if a pair of moves are provided.
The generated instructions will represent all possible paths the battle could take given the randomness of the moves.
Calling get_all_state_instructions
will generate a list of TransposeInstruction objects,
each of which contains a list of instructions, as well as a likelihood (percentage) of its occurrence
Keep in mind that these are arbitrary examples, and the State generation is skipped for brevity. Obviously, changes to the state will affect the generated instructions
Example: tackle being used by both combatants
>> from poke_engine import State
>> from poke_engine import StateMutator
>> from poke_engine import get_all_state_instructions
>> state = State(...) # initialize your state
>> mutator = StateMutator(state)
>> my_move = 'tackle'
>> your_move = 'tackle'
>> transpose_instructions = get_all_state_instructions(mutator, my_move, your_move)
>> len(transpose_instructions)
>> 1 # no randomness here
>> first_instruction = transpose_instructions[0]
>> print(first_instruction.percentage)
>> 1.0 # 100% chance of happening
>> print(first_instruction.instructions)
>> [('damage', 'user', 15), ('damage', 'opponent', 15)]
Example: thunderbolt being used by both combatants
>> from poke_engine import State
>> from poke_engine import StateMutator
>> from poke_engine import get_all_state_instructions
>> state = State(...) # initialize your state
>> mutator = StateMutator(state)
>> my_move = 'thunderbolt'
>> your_move = 'thunderbolt'
>> transpose_instructions = get_all_state_instructions(mutator, my_move, your_move)
# randomness with secondary effects (paralysis in this case) means there are 5 different sets of instructions that could happen here
>> len(transpose_instructions)
>> 5
>> first_instruction = transpose_instructions[0]
# The first instruction is when both thunderbolts paralyze
# it has a 0.75 % chance of happening
>> print(first_instruction.percentage)
>> 0.0075000000000000015
>> print(first_instruction.instructions)
>> [('damage', 'opponent', 45), ('apply_status', 'opponent', 'par'), ('damage', 'user', 45), ('apply_status', 'user', 'par')]
# Looking at another instruction
# this one is when the first thunderbolt paralyzes, and the other pokemon is fully-paralyzed and does not move
# it has a 2.5% chance of happening
>> another_instruction = transpose_instructions[2]
>> print(another_instruction.percentage)
>> 0.025
>> print(another_instruction.instructions)
>> [('damage', 'opponent', 45), ('apply_status', 'opponent', 'par')]
Notice that damage calculations are constant per move. This is done for simplicity - the default behaviour is that only the average damage amount is used. This behaviour can be changed by setting a global configuration value.
Obviously, if this is done then the number of instructions generated will become very large even for simple pairs of moves