Skip to content

Commit 6fc365c

Browse files
feat: 从 PokeAPI 批量导入物种数据,替换硬编码
- 新增 pokedex-data.ts:1024 个物种的 baseExperience、EV yield、growthRate、captureRate、baseHappiness、hatchCounter - 新增 fetch-pokedex-data.ts:PokeAPI 数据抓取脚本(可重复运行) - 新增 fetch-species-names.ts:多语言名称抓取脚本(中/日/英) - species.ts 改为使用 pokedex-data 替代硬编码 supplement 条目 - 解决 #1 XP 数据源、#2 EV 数据源、#13 Growth Rate 覆盖不全问题 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6a89a51 commit 6fc365c

4 files changed

Lines changed: 1321 additions & 45 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Fetch base_experience, EV yield, and growth_rate for all species from PokeAPI.
3+
* Generates src/dex/pokedex-data.ts
4+
*
5+
* Usage: bun run scripts/fetch-pokedex-data.ts
6+
*/
7+
import { Dex } from '@pkmn/sim'
8+
9+
const GROWTH_RATE_MAP: Record<string, string> = {
10+
'slow-then-very-fast': 'erratic',
11+
'fast-then-very-slow': 'fluctuating',
12+
'medium': 'medium-fast',
13+
'medium-slow': 'medium-slow',
14+
'slow': 'slow',
15+
'fast': 'fast',
16+
}
17+
18+
const STAT_MAP: Record<string, string> = {
19+
'hp': 'hp',
20+
'attack': 'atk',
21+
'defense': 'def',
22+
'special-attack': 'spa',
23+
'special-defense': 'spd',
24+
'speed': 'spe',
25+
}
26+
27+
interface SpeciesPokedex {
28+
baseExperience: number
29+
evs: Record<string, number>
30+
growthRate: string
31+
captureRate: number
32+
baseHappiness: number
33+
hatchCounter: number
34+
}
35+
36+
async function fetchSpeciesData(id: number): Promise<SpeciesPokedex | null> {
37+
try {
38+
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
39+
if (!res.ok) return null
40+
const data = await res.json() as any
41+
42+
// Get growth rate from species endpoint
43+
const speciesRes = await fetch(`https://pokeapi.co/api/v2/pokemon-species/${id}`)
44+
if (!speciesRes.ok) return null
45+
const speciesData = await speciesRes.json() as any
46+
47+
const evs: Record<string, number> = {}
48+
for (const stat of data.stats || []) {
49+
if (stat.effort > 0) {
50+
const statName = STAT_MAP[stat.stat.name]
51+
if (statName) evs[statName] = stat.effort
52+
}
53+
}
54+
55+
const growthRateName = GROWTH_RATE_MAP[speciesData.growth_rate?.name] ?? 'medium-slow'
56+
57+
return {
58+
baseExperience: data.base_experience ?? 50,
59+
evs,
60+
growthRate: growthRateName,
61+
captureRate: speciesData.capture_rate ?? 45,
62+
baseHappiness: speciesData.base_happiness ?? 70,
63+
hatchCounter: speciesData.hatch_counter ?? 20,
64+
}
65+
} catch {
66+
return null
67+
}
68+
}
69+
70+
async function main() {
71+
// Get all base species IDs from Dex
72+
const rawSpecies = Dex.data.Species as Record<string, { num: number; forme?: string }>
73+
const species: { id: string; num: number }[] = []
74+
for (const [id, s] of Object.entries(rawSpecies)) {
75+
if (s.num > 0 && Number.isInteger(s.num) && !s.forme) {
76+
species.push({ id, num: s.num })
77+
}
78+
}
79+
species.sort((a, b) => a.num - b.num)
80+
81+
console.log(`Fetching data for ${species.length} species from PokeAPI...`)
82+
83+
const results: Record<string, SpeciesPokedex> = {}
84+
let fetched = 0
85+
const BATCH_SIZE = 20
86+
87+
for (let i = 0; i < species.length; i += BATCH_SIZE) {
88+
const batch = species.slice(i, i + BATCH_SIZE)
89+
const promises = batch.map(async (s) => {
90+
const data = await fetchSpeciesData(s.num)
91+
if (data) results[s.id] = data
92+
fetched++
93+
})
94+
await Promise.all(promises)
95+
process.stdout.write(`\rFetched ${fetched}/${species.length}...`)
96+
// Small delay to avoid rate limiting
97+
await new Promise(r => setTimeout(r, 200))
98+
}
99+
100+
console.log(`\nFetched ${Object.keys(results).length} species.`)
101+
102+
// Generate TypeScript file
103+
const lines: string[] = [
104+
'// Auto-generated from PokeAPI. Run: bun run scripts/fetch-pokedex-data.ts',
105+
'// eslint-disable-next-line @typescript-eslint/no-extraneous-class',
106+
'export interface PokedexEntry {',
107+
' baseExperience: number',
108+
' evs: Record<string, number>',
109+
' growthRate: string',
110+
' captureRate: number',
111+
' baseHappiness: number',
112+
' hatchCounter?: number',
113+
'}',
114+
'',
115+
'export const POKEDEX_DATA: Record<string, PokedexEntry> = {',
116+
]
117+
118+
for (const [id, data] of Object.entries(results)) {
119+
const evsStr = Object.keys(data.evs).length > 0
120+
? `{ ${Object.entries(data.evs).map(([k, v]) => `${k}: ${v}`).join(', ')} }`
121+
: '{}'
122+
lines.push(` '${id}': { baseExperience: ${data.baseExperience}, evs: ${evsStr}, growthRate: '${data.growthRate}', captureRate: ${data.captureRate}, baseHappiness: ${data.baseHappiness}, hatchCounter: ${data.hatchCounter} },`)
123+
}
124+
125+
lines.push('}')
126+
lines.push('')
127+
128+
const outputPath = new URL('../src/dex/pokedex-data.ts', import.meta.url)
129+
await Bun.write(outputPath, lines.join('\n'))
130+
console.log(`Written to ${outputPath.pathname}`)
131+
}
132+
133+
main().catch(console.error)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Fetch multilingual species names (en, ja, zh) from PokeAPI.
3+
* Generates src/dex/species-names.ts
4+
*
5+
* Usage: bun run scripts/fetch-species-names.ts
6+
*/
7+
import { Dex } from '@pkmn/sim'
8+
9+
interface SpeciesNames {
10+
en: string
11+
ja: string
12+
zh: string
13+
}
14+
15+
async function fetchSpeciesNames(id: number): Promise<SpeciesNames | null> {
16+
try {
17+
const res = await fetch(`https://pokeapi.co/api/v2/pokemon-species/${id}`)
18+
if (!res.ok) return null
19+
const data = await res.json() as any
20+
21+
const names: SpeciesNames = { en: '', ja: '', zh: '' }
22+
for (const entry of data.names || []) {
23+
const lang = entry.language.name as string
24+
if (lang === 'en') names.en = entry.name
25+
else if (lang === 'ja') names.ja = entry.name
26+
else if (lang === 'zh-Hant' || lang === 'zh-Hans') names.zh = entry.name
27+
}
28+
// Fallback to English if zh/ja missing
29+
if (!names.zh) names.zh = names.en
30+
if (!names.ja) names.ja = names.en
31+
if (!names.en) return null
32+
33+
return names
34+
} catch {
35+
return null
36+
}
37+
}
38+
39+
async function main() {
40+
const rawSpecies = Dex.data.Species as Record<string, { num: number; forme?: string }>
41+
const species: { id: string; num: number }[] = []
42+
for (const [id, s] of Object.entries(rawSpecies)) {
43+
if (s.num > 0 && Number.isInteger(s.num) && !s.forme) {
44+
species.push({ id, num: s.num })
45+
}
46+
}
47+
species.sort((a, b) => a.num - b.num)
48+
49+
console.log(`Fetching names for ${species.length} species from PokeAPI...`)
50+
51+
const results: Record<string, SpeciesNames> = {}
52+
let fetched = 0
53+
const BATCH_SIZE = 20
54+
55+
for (let i = 0; i < species.length; i += BATCH_SIZE) {
56+
const batch = species.slice(i, i + BATCH_SIZE)
57+
const promises = batch.map(async (s) => {
58+
const data = await fetchSpeciesNames(s.num)
59+
if (data) results[s.id] = data
60+
fetched++
61+
})
62+
await Promise.all(promises)
63+
process.stdout.write(`\rFetched ${fetched}/${species.length}...`)
64+
await new Promise(r => setTimeout(r, 200))
65+
}
66+
67+
console.log(`\nFetched ${Object.keys(results).length} species names.`)
68+
69+
// Generate TypeScript file
70+
const lines: string[] = [
71+
'// Auto-generated from PokeAPI. Run: bun run scripts/fetch-species-names.ts',
72+
'',
73+
'export interface SpeciesI18n { en: string; ja: string; zh: string }',
74+
'',
75+
'export const SPECIES_I18N_DATA: Record<string, SpeciesI18n> = {',
76+
]
77+
78+
for (const [id, data] of Object.entries(results)) {
79+
lines.push(` '${id}': { en: '${data.en.replace(/'/g, "\\'")}', ja: '${data.ja}', zh: '${data.zh}' },`)
80+
}
81+
82+
lines.push('}')
83+
lines.push('')
84+
85+
const outputPath = new URL('../src/dex/species-names.ts', import.meta.url)
86+
await Bun.write(outputPath, lines.join('\n'))
87+
console.log(`Written to ${outputPath.pathname}`)
88+
}
89+
90+
main().catch(console.error)

0 commit comments

Comments
 (0)