Add Go-based PokeAPI fetch tool
Replaces the Python fetch_pokeapi.py script with a Go tool that crawls a local PokeAPI instance and writes seed JSON files. Supports caching and special encounter definitions via JSON config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
271
tools/fetch-pokeapi/evolutions.go
Normal file
271
tools/fetch-pokeapi/evolutions.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// fetchEvolutionData fetches evolution chains and returns flattened pairs.
|
||||
func fetchEvolutionData(
|
||||
ctx context.Context,
|
||||
client *Client,
|
||||
speciesData map[int]*SpeciesResp,
|
||||
seededDex map[int]bool,
|
||||
) ([]EvolutionOutput, error) {
|
||||
fmt.Println("\n--- Fetching evolution chains ---")
|
||||
|
||||
// Extract unique chain IDs from species data
|
||||
chainIDSet := make(map[int]bool)
|
||||
for sid, species := range speciesData {
|
||||
if seededDex[sid] {
|
||||
chainIDSet[species.EvolutionChain.ID()] = true
|
||||
}
|
||||
}
|
||||
|
||||
chainIDs := make([]int, 0, len(chainIDSet))
|
||||
for id := range chainIDSet {
|
||||
chainIDs = append(chainIDs, id)
|
||||
}
|
||||
sort.Ints(chainIDs)
|
||||
|
||||
fmt.Printf(" Found %d unique evolution chains\n", len(chainIDs))
|
||||
|
||||
// Fetch chains concurrently
|
||||
type chainResult struct {
|
||||
chain EvolutionChainResp
|
||||
}
|
||||
results := make([]chainResult, len(chainIDs))
|
||||
var wg sync.WaitGroup
|
||||
errs := make([]error, len(chainIDs))
|
||||
|
||||
for i, cid := range chainIDs {
|
||||
wg.Add(1)
|
||||
go func(i, cid int) {
|
||||
defer wg.Done()
|
||||
data, err := client.Get(ctx, fmt.Sprintf("evolution-chain/%d", cid))
|
||||
if err != nil {
|
||||
errs[i] = err
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(data, &results[i].chain); err != nil {
|
||||
errs[i] = fmt.Errorf("parsing evolution chain %d: %w", cid, err)
|
||||
}
|
||||
}(i, cid)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten all chains
|
||||
var allPairs []EvolutionOutput
|
||||
type dedupeKey struct {
|
||||
from, to int
|
||||
trigger string
|
||||
}
|
||||
seen := make(map[dedupeKey]bool)
|
||||
|
||||
for _, r := range results {
|
||||
pairs := flattenChain(r.chain.Chain, seededDex)
|
||||
for _, p := range pairs {
|
||||
key := dedupeKey{p.FromPokeAPIID, p.ToPokeAPIID, p.Trigger}
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
allPairs = append(allPairs, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(allPairs, func(i, j int) bool {
|
||||
if allPairs[i].FromPokeAPIID != allPairs[j].FromPokeAPIID {
|
||||
return allPairs[i].FromPokeAPIID < allPairs[j].FromPokeAPIID
|
||||
}
|
||||
return allPairs[i].ToPokeAPIID < allPairs[j].ToPokeAPIID
|
||||
})
|
||||
|
||||
fmt.Printf(" Total evolution pairs: %d\n", len(allPairs))
|
||||
return allPairs, nil
|
||||
}
|
||||
|
||||
// flattenChain recursively flattens an evolution chain into (from, to) pairs.
|
||||
func flattenChain(chain ChainLink, seededDex map[int]bool) []EvolutionOutput {
|
||||
var pairs []EvolutionOutput
|
||||
fromDex := chain.Species.ID()
|
||||
|
||||
for _, evo := range chain.EvolvesTo {
|
||||
toDex := evo.Species.ID()
|
||||
|
||||
for _, detail := range evo.EvolutionDetails {
|
||||
trigger := detail.Trigger.Name
|
||||
|
||||
var minLevel *int
|
||||
if detail.MinLevel != nil {
|
||||
v := *detail.MinLevel
|
||||
minLevel = &v
|
||||
}
|
||||
|
||||
var item *string
|
||||
if detail.Item != nil {
|
||||
s := detail.Item.Name
|
||||
item = &s
|
||||
}
|
||||
|
||||
var heldItem *string
|
||||
if detail.HeldItem != nil {
|
||||
s := detail.HeldItem.Name
|
||||
heldItem = &s
|
||||
}
|
||||
|
||||
conditions := CollectEvolutionConditions(detail)
|
||||
var condition *string
|
||||
if len(conditions) > 0 {
|
||||
s := strings.Join(conditions, ", ")
|
||||
condition = &s
|
||||
}
|
||||
|
||||
if seededDex[fromDex] && seededDex[toDex] {
|
||||
pairs = append(pairs, EvolutionOutput{
|
||||
FromPokeAPIID: fromDex,
|
||||
ToPokeAPIID: toDex,
|
||||
Trigger: trigger,
|
||||
MinLevel: minLevel,
|
||||
Item: item,
|
||||
HeldItem: heldItem,
|
||||
Condition: condition,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse
|
||||
pairs = append(pairs, flattenChain(evo, seededDex)...)
|
||||
}
|
||||
|
||||
return pairs
|
||||
}
|
||||
|
||||
// EvolutionOverrides represents the evolution_overrides.json structure.
|
||||
type EvolutionOverrides struct {
|
||||
Remove []struct {
|
||||
FromDex int `json:"from_dex"`
|
||||
ToDex int `json:"to_dex"`
|
||||
} `json:"remove"`
|
||||
Add []struct {
|
||||
FromDex int `json:"from_dex"`
|
||||
ToDex int `json:"to_dex"`
|
||||
Trigger string `json:"trigger"`
|
||||
MinLevel *int `json:"min_level"`
|
||||
Item *string `json:"item"`
|
||||
HeldItem *string `json:"held_item"`
|
||||
Condition *string `json:"condition"`
|
||||
} `json:"add"`
|
||||
Modify []struct {
|
||||
FromDex int `json:"from_dex"`
|
||||
ToDex int `json:"to_dex"`
|
||||
Set map[string]interface{} `json:"set"`
|
||||
} `json:"modify"`
|
||||
}
|
||||
|
||||
// applyEvolutionOverrides applies overrides from evolution_overrides.json.
|
||||
func applyEvolutionOverrides(evolutions []EvolutionOutput, overridesPath string) ([]EvolutionOutput, error) {
|
||||
data, err := os.ReadFile(overridesPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return evolutions, nil
|
||||
}
|
||||
return nil, fmt.Errorf("reading evolution overrides: %w", err)
|
||||
}
|
||||
|
||||
var overrides EvolutionOverrides
|
||||
if err := json.Unmarshal(data, &overrides); err != nil {
|
||||
return nil, fmt.Errorf("parsing evolution overrides: %w", err)
|
||||
}
|
||||
|
||||
// Remove entries
|
||||
for _, removal := range overrides.Remove {
|
||||
filtered := evolutions[:0]
|
||||
for _, e := range evolutions {
|
||||
if !(e.FromPokeAPIID == removal.FromDex && e.ToPokeAPIID == removal.ToDex) {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
evolutions = filtered
|
||||
}
|
||||
|
||||
// Add entries
|
||||
for _, addition := range overrides.Add {
|
||||
trigger := addition.Trigger
|
||||
if trigger == "" {
|
||||
trigger = "level-up"
|
||||
}
|
||||
evolutions = append(evolutions, EvolutionOutput{
|
||||
FromPokeAPIID: addition.FromDex,
|
||||
ToPokeAPIID: addition.ToDex,
|
||||
Trigger: trigger,
|
||||
MinLevel: addition.MinLevel,
|
||||
Item: addition.Item,
|
||||
HeldItem: addition.HeldItem,
|
||||
Condition: addition.Condition,
|
||||
})
|
||||
}
|
||||
|
||||
// Modify entries
|
||||
for _, mod := range overrides.Modify {
|
||||
for i := range evolutions {
|
||||
e := &evolutions[i]
|
||||
if e.FromPokeAPIID == mod.FromDex && e.ToPokeAPIID == mod.ToDex {
|
||||
for key, value := range mod.Set {
|
||||
switch key {
|
||||
case "trigger":
|
||||
if s, ok := value.(string); ok {
|
||||
e.Trigger = s
|
||||
}
|
||||
case "min_level":
|
||||
if v, ok := value.(float64); ok {
|
||||
level := int(v)
|
||||
e.MinLevel = &level
|
||||
} else if value == nil {
|
||||
e.MinLevel = nil
|
||||
}
|
||||
case "item":
|
||||
if s, ok := value.(string); ok {
|
||||
e.Item = &s
|
||||
} else if value == nil {
|
||||
e.Item = nil
|
||||
}
|
||||
case "held_item":
|
||||
if s, ok := value.(string); ok {
|
||||
e.HeldItem = &s
|
||||
} else if value == nil {
|
||||
e.HeldItem = nil
|
||||
}
|
||||
case "condition":
|
||||
if s, ok := value.(string); ok {
|
||||
e.Condition = &s
|
||||
} else if value == nil {
|
||||
e.Condition = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-sort
|
||||
sort.Slice(evolutions, func(i, j int) bool {
|
||||
if evolutions[i].FromPokeAPIID != evolutions[j].FromPokeAPIID {
|
||||
return evolutions[i].FromPokeAPIID < evolutions[j].FromPokeAPIID
|
||||
}
|
||||
return evolutions[i].ToPokeAPIID < evolutions[j].ToPokeAPIID
|
||||
})
|
||||
|
||||
fmt.Printf(" Applied overrides: %d pairs after overrides\n", len(evolutions))
|
||||
return evolutions, nil
|
||||
}
|
||||
Reference in New Issue
Block a user