The problem
Older Pokemon games had a mechanic where certain moves could only be learned through breeding — you'd need to find a chain of Pokemon that could breed with each other, starting from one that naturally knew the move and ending with the one you actually wanted. The chains could get long and convoluted. For example, to get Bulbasaur a move like Sludge, you might need to go through Koffing → Shellos → Mudkip → Bulbasaur. Figuring that out manually meant cross-referencing egg group tables and move lists across multiple wiki pages.
It's a graph problem. Every Pokemon is a node. If two Pokemon share an egg group, there's an edge between them. You want the shortest path from any Pokemon that can learn the move naturally to your target. I built this as a collaboration with the University of Illinois Data Driven Discovery Group — it was a genuinely interesting data modeling exercise, and I learned a lot about web scraping and graph representation along the way. The dataset ended up being 917 nodes and 46,145 edges across a directed graph, one per egg move.
In modern Pokemon games the egg move transfer system has been simplified substantially, so the acute need for a tool like this has largely gone away. The other tools that existed around the same time are mostly defunct now as far as I can tell — this one is still running, though it has bugs and needs a proper rework.
How it works
For each egg move, the scraper builds an adjacency list: source nodes are Pokemon that learn the move through level-up or TM, target nodes are Pokemon that can only learn it by breeding. An edge from source to target exists if they share an egg group.
for moveFileName in os.listdir("../data/moves/"):
with open('../data/moves/' + moveFileName, 'rb') as moveFile:
moveData = list(reader)
sourceNodes = [] # Pokemon that learn move by level-up/TM
targetNodes = [] # Pokemon that can only learn it by breeding
for source in sourceNodes:
for target in targetNodes:
if share_egg_group(source, target):
add_edge(source, target)
At query time, NetworkX loads the precomputed adjacency list for the requested move and runs all_shortest_paths from every valid source to the target Pokemon.
G = nx.read_adjlist(desiredMove + '.adjlist', create_using=nx.DiGraph())
all_paths = []
for pokemon in source_pokemon:
try:
paths = nx.all_shortest_paths(G, source=pokemon, target=desiredPokemon)
all_paths.extend(paths)
except nx.exception.NetworkXNoPath:
continue
output = list(set(all_paths))
for path in output:
print(" => ".join(path))
| Metric | Value |
|---|---|
| Total nodes (Pokemon) | 917 |
| Total edges (breeding relationships) | 46,145 |
| Graph type | Directed |
| Path algorithm | BFS via NetworkX / jsnetworkx |
Usage
The web interface lets you pick a Pokemon and an egg move and see all shortest breeding chains. The command line version works the same way:
python emc.py
Which egg move are you interested in? Sludge
Which Pokemon would you like to breed Sludge onto? Bulbasaur
Possible breeding chains are as follows:
Koffing => Shellos => Mudkip => Bulbasaur
Data is stored as precomputed adjacency lists per move, plus CSVs for egg groups and name mappings:
data/
├── pokemonEggMove.json # Complete egg move database
├── egg_groups.csv
├── pokemonNames.csv
├── adjLists/
│ ├── Sludge.adjlist
│ └── ... # One file per egg move
└── moves/
├── Sludge.csv
└── ...
Collaborators
| Contributor | Role | Contribution |
|---|---|---|
| Aravind Sundararajan | PhD Student | Algorithm development, graph theory implementation |
| Anna Buyevich | BS Computer Science | Web interface development |
| Emily Chen | PhD Computational Linguistics | Data processing |
| Wade Fagen-Ulmschneider | Faculty Advisor | Project supervision |