This study will explore the optimization of Pokemon battling through a computational lens. Many factors will affect the conditions of a battle, such as a Pokemon's base stats, typing, and moveset. Game graphs will help to simulate 'one-directional' battles, where only one Pokemon is allowed to attack. The optimized paths found in these graphs will be used to produce a heat map of all Generation I Pokemon's battling capabilities. Additionally, the results of multiple graphs will be compared in order to predict the winner of a battle between any two of these Pokemon.

Introduction

When engaged in a battle, two Pokemon take turns dealing damage to each other until one of them has zero hit points remaining. There are many factors that influence the possible outcomes of a battle. In order to simulate this, each process can be broken down. Game graphs will be used to isolate a single Pokemon within the scope of a battle and optimize their play. Specific parameters, such as how much damage each move does, will be observed on high-depth graphs to find the fastest path to victory. The culmination of these parameters can be scaled up to observe potential trends, both for evaluating the overall strength of specific Pokemon, and for identifying broad tendencies in the data.

Gathering Pokemon Data

Each Pokemon has many different traits that affect their battling capabilities. The code below gathers information about three categories of these traits, being a Pokemon's base stats, their moves, and their typing. Base stats represent a general overview of how strong a Pokemon is in battle through values for health, attack, defense, etc. Moves are a Pokemon's method of dealing damage. Moves can vary in damage, accuracy, and typing. A Pokemon will receive more or less damage from a move depending on the types involved. For example, a fire-type Pokemon will receive 2x damage from a water-type move.
Retrieve the HTML code of the selected Pokemon’s Bulbapedia webpage:
In[]:=
pokemonHTMLCode[PokemonName_String]:=Import["https://bulbapedia.bulbagarden.net/wiki/"<>PokemonName<>"_(Pokémon)",{"HTML","Data"}];
Associate the Pokemon’s type with damage multipliers (e.g., a fire-type Pokemon would receive 2x damage from a water-type move):
In[]:=
pokemonHTMLTyping[HTMLCode_List]:=Drop[Select[HTMLCode[[2,1,3,1]],First[#,#]=="Under normal battle conditions in Generation IX, this Pokémon is: "&][[1,2,2]],-1];​​pokemonTypings[HTMLTyping_List]:=AssociationThread[StringDrop[First[#],-1]&/@HTMLTyping,StringDrop[Last[#],-1]&/@HTMLTyping]/.{"\275"->0.5,"\274"->0.25,"1"->1,"2"->2,"4"->4,"0"->0};
Create an association of the stat values for the Pokemon (e.g., health, attack, speed):
In[]:=
pokemonHTMLStats[HTMLCode_List]:=Select[HTMLCode[[2,1,3,1]],First[#,#]==={"Stat ","Range"}&];​​rawPokemonStats[HTMLStats_List]:=ToExpression[StringExtract[#[[1]],-2]&/@HTMLStats[[1,3;;8]]];​​pokemonStats[RawStats_List]:=<|"HP"->#1,"Attack"->#2,"Defense"->#3,"SAttack"->#4,"SDefense"->#5,"Speed"->#6|>&@@RawStats;
Out[]=
Import the Pokemon’s possible moves from Serebii:
In[]:=
pokemonDexNumber[PokemonName_String]:=StringTake[StringJoin[ToString/@PadLeft[{ToString[EntityValue[Interpreter["Pokemon"][PokemonName],"NationalDexNumber"]]},3]],-3];​​getPokemonMovesHTML[PokemonIndexNumber_]:=Import["https://www.serebii.net/pokedex/"<>PokemonIndexNumber<>".shtml",{"HTML","Data"}];​​rawPokemonMoves[PokemonMovesHTML_List]:=Select[Partition[Drop[PokemonMovesHTML[[2,3,3,3,7]],2],2],IntegerQ[#[[1,3]]]&&StringContainsQ[#[[2]],"-type"]&];​​pokemonMoveData[RawPokemonMoveList_List]:=Flatten[{#[[1,2;;4]],StringCases[#[[2]],RegularExpression["[A-Z]{2,15}"]][[1]]}]&/@RawPokemonMoveList;
Out[]=
Organizes the previous functions to give all the necessary data for any Pokemon:
In[]:=
assembleCompletePokemon[PokemonStats_Association,PokemonMoveData_List,PokemonTypings_Association]:=PokemonStats~Join~<|"Moves"-><|#1-><|"Power"->#2,"Accuracy"->#3,"Type"->Capitalize[ToLowerCase[#4]],"Name"->#1|>&@@#&/@PokemonMoveData|>|>~Join~<|"Typings"->PokemonTypings|>;​​generateCompletePokemon[PokemonName_String]:=Module[{HTMLData=pokemonHTMLCode[PokemonName]},assembleCompletePokemon[pokemonStats[rawPokemonStats[pokemonHTMLStats[HTMLData]]],pokemonMoveData[rawPokemonMoves[getPokemonMovesHTML[pokemonDexNumber[PokemonName]]]],pokemonTypings[pokemonHTMLTyping[HTMLData]]]​​];

Simulating a Battle Between Pokemon

With the data collected by the previous functions, we can simulate battles between two Pokemon through the use of token event graphs. In these graphs, each event (represented by a yellow circle) shows the use of a move from the attacking Pokemon. Each branch off of this event leads to a token (represented by a light blue rectangle) that contains updated information about the defending Pokemon.

Set Up Battle Parameters

Calculate the damage that a move will do in a specific battle situation:
In[]:=
attackDamageFormula[PokemonLevel_Integer,AttackPower_Integer,MoveType_String,AttackStat_Integer,OpponentDefenseStat_Integer,OpponentTypeEffectiveness_Association]:=IntegerPart@
2*PokemonLevel
5
+2*AttackPower*
AttackStat
OpponentDefenseStat
50
+2*OpponentTypeEffectiveness[[MoveType]];​​getDamage[Attacker_Association,Defender_Association,MoveName_String]:=Module[{dmg=attackDamageFormula[10,Attacker[["Moves",MoveName,"Power"]],Attacker[["Moves",MoveName,"Type"]],Attacker[["Attack"]],Defender[["Defense"]],Defender[["Typings"]]]},​​If[dmg==0,1,dmg]];
Simulate a battle between any two Pokemon:
In[]:=
selector[{x_Integer}]:={x};​​selector[x:{_List..}]:=Select[x,#[[2]]>0&];​​selector[{}]={};​​toZero[x_Integer]:=If[Negative[x],0,x];​​createPokemonGraph[AttackingPokemon_String,DefendingPokemon_String,GraphDepth_Integer,DisplayAttacks_Symbol]:=Module{Attacker=generateCompletePokemon[AttackingPokemon],Defender=generateCompletePokemon[DefendingPokemon],Labelled=If[DisplayAttacks,"EdgeTag",""]},ResourceFunction["TokenEventGraph"]​​<|"Attack"->n_|>:><|toZero[n-getDamage[Attacker,Defender,Attacker[["Moves",#[["Name"]],"Name"]]]]&/@Attacker[["Moves"]]|>,{Defender[["HP"]]},GraphDepth,​​
,​​"TokenDeduplication"->False,​​"TokenDecompositionFunction"->selector,​​EdgeLabels->Labelled​​​​;

Generating Token Event Graphs

Get the data of two Pokemon:
In[]:=
Pokemon1=generateCompletePokemon["Bulbasaur"];​​Pokemon2=generateCompletePokemon["Charmander"];
Out[]=
Outcome for each move with a 100% hit rate. Values represent remaining health of Pokemon 1:
In[]:=
ResourceFunction["TokenEventGraph"][n_:>{n-getDamage[Pokemon1,Pokemon2,#[["Name"]]]},{Pokemon1[["HP"]]},5,"TokenDeduplication"->False,"RemoveEventLoops"->True]&/@Pokemon1[["Moves"]]
Out[]=
The graphic below takes into account each move’s accuracy as well as damage. Damage is the left value in each token, and chance of occurrence is the right value:
In[]:=
ResourceFunction["TokenEventGraph"]{{{n_,acc_}}:>{{n-getDamage[Pokemon1,Pokemon2,#[["Name"]]],Round[acc*(#[["Accuracy"]]/100),0.1]},{n,Round[acc*(1-#[["Accuracy"]]/100),0.1]}}},{{Pokemon1[["HP"]],100}},2,"TokenDeduplication"->None,"RemoveEventLoops"->True,
&/@Pokemon1[["Moves"]]
Out[]=
This graphic represents all available move options for the attacking Pokemon:
In[]:=
ResourceFunction["TokenEventGraph"]<|"Attack"->n_|>:><|"HP"->n-getDamage[Pokemon2,Pokemon1,Pokemon2[["Moves",#[["Name"]],"Name"]]]&/@Pokemon2[["Moves"]]|>,{Pokemon1[["HP"]]},1,EdgeLabels->"EdgeTag",
,"TokenDeduplication"->False
Out[]=
Run at depth 2:
Run at depth 3:
Skipping to depth 7, the health of the defending Pokemon is reduced to zero in all possible outcomes:

Scaling the Results

The graphs produced in the previous section are able to compute the possible outcomes of a one-directional battle, where only one Pokemon is attacking the other. By mapping the results of many of these battles together, we are able to create a heat map to observe and predict the winner of any Pokemon battle.

Creating a Heat Map of Battle Outcomes

A heat map is a beneficial method of arranging the token event graphs, as it takes into account both the offensive and the defensive capabilities of each Pokemon. Because both of these are taken into account, a heat map offers a more representative overview of the data.
Get the name of every Pokemon:
Remove types of Pokemon that shouldn't be selected:
Isolate a subset of this list to be used for a heat map. Takes approximately 3 minutes to run:
Two functions that offer alternative methods of generating battle graphs:
Use a generated one-directional graph to find the minimum amount of moves required for the defending Pokemon's health to reach zero:
Generate a bar chart that represents the outcome of a certain amount of battles between randomly selected Pokemon:
Example of 200 of these random battles. Each x-value represents a battle, and the corresponding y-value represents the amount of moves it will take for the attacking Pokemon to defeat the defending Pokemon:
Comparison of two Pokemon using a bar chart. Note that a lower average number of moves to win means that a Pokemon is stronger in battle:
Simulate every battle that could occur between two Pokemon on the generated list:
Gather the data for each of these battles. Takes approximately 5 minutes to run:
Create a heat map with this data. Each point represents a battle, colored to show the amount of moves required for the attacking Pokemon to defeat the defending Pokemon:

Predicting the Winner of a Battle

Compare the 'movesToZeroHP' value for both Pokemon in a battle and predict the winner. The winner of the battle is the Pokemon that is able to reduce its opponent's health to zero in the least amount of steps. In the case of a tie, the 'speed' stat of both Pokemon is what decides the winner of the battle, as this value determines the order in which Pokemon are allowed to attack.
Display the winner of the battle:

Conclusion and Future Work

Through the use of game graphs, this study was able to analyze the possible outcomes of a Pokemon battle. With this information, specific parameters were optimized in order to predict the result of a battle between any two Generation I Pokemon. There is a certain level of variability in battling, as seen in the graph that incorporated accuracy chances into its calculations. Another model of Pokemon battling may choose to include this factor into its results. Additionally, other generations of Pokemon have different battle mechanics which can be analyzed to yield interest results.

Acknowledgements

I would like to thank the Wolfram Summer Research Program for giving me the opportunity to create this project and learn about the Wolfram Language. I would also like to thank my mentor, Isabel Skidmore, who provided continuous support throughout the duration of the project.

Bibliography

1
.
KelsiDavis, Archaic, LettersFrom, Cybelyrium, &amp; Nate, M. (1970, July 12). The original Pokémon Community. Bulbagarden. https://bulbagarden.net/
2
.
Where legends come to life. Serebii.net Header. (n.d.). https://www.serebii.net/

CITE THIS NOTEBOOK

Create and analyze a game graph for Pokémon battles​
by Joey Mussalli​
Wolfram Community, STAFF PICKS, July 13, 2023
​https://community.wolfram.com/groups/-/m/t/2962470