feat: 计划完成

This commit is contained in:
claude-code-best
2026-04-21 23:56:03 +08:00
parent 970fcd627f
commit 336159ee18
4 changed files with 774 additions and 0 deletions

189
plans/phase-0.md Normal file
View File

@@ -0,0 +1,189 @@
# Phase 0: 清除重复 — 委托 @pkmn
## 目标
删除所有与 @pkmn 重复的硬编码数据和手写公式,统一委托给 @pkmn 生态。**零新功能,纯重构。**
## 设计原则
先清后建。先消除重复代码,再在干净基础上添加新功能。此 Phase 不引入任何新类型或新功能。
## 改动
| 文件 | 操作 | 说明 |
|------|------|------|
| `types.ts` | 删除 `NATURES` 常量27 行) | `Dex.natures` 已有完整数据 |
| `data/nature.ts` | 重写 | `ALL_NATURE_NAMES` → 遍历 `Dex.natures``getNatureEffect()` → 查询 `Dex.natures.get()` |
| `data/evolution.ts` | 重写 | `EVOLUTION_CHAINS``Dex.species.get(id).evos`/`.evoLevel` 动态查询 |
| `core/creature.ts` | 重写 `calculateStats()` | 手写公式 → `gen.stats.calc()`Nature ±10% 自动处理) |
| `data/species.ts` | 简化 `buildEvolutionChain()` | 已部分使用 Dex.evos统一用新 `getNextEvolution()` |
| `data/pkmn.ts` | 统一 `gens` 单例 | 导出 `gen``TO_DEX_STAT` 映射,消除多处重复创建 `Generations` |
## 使用 @pkmn 包
- `@pkmn/sim`Dex.natures, Dex.species
- `@pkmn/data`stats.calc, Generations
## 保留不变
- `types.ts` 中的 `NatureName`/`NatureStat`/`NatureEffect` 类型定义Creature 类型约束需要)
- `data/species.ts``SUPPLEMENT`growthRate/captureRate/flavorText 不在 Dex 中)
- `data/names.ts`i18n 多语言名称)
- `data/xpTable.ts``data/evMapping.ts`(完全自定义)
- `core/` 其他文件、`ui/``sprites/`
## 详细实现
### 1. types.ts — 删除 NATURES
删除 `types.ts``NATURES` 常量(约 27 行手写数据)。保留 `NatureName``NatureStat``NatureEffect` 类型。
### 2. data/nature.ts — 委托 Dex.natures
```typescript
import { Dex } from '@pkmn/sim'
import type { NatureName, NatureEffect, NatureStat } from '../types'
export function getAllNatureNames(): NatureName[] {
const names: NatureName[] = []
for (const nature of Dex.natures) {
if (nature.exists) names.push(nature.id as NatureName)
}
return names
}
export function randomNature(): NatureName {
const names = getAllNatureNames()
return names[Math.floor(Math.random() * names.length)]!
}
export function getNatureEffect(nature: NatureName): NatureEffect {
const n = Dex.natures.get(nature)
if (!n?.exists) return { plus: null, minus: null }
return {
plus: (n.plus as NatureStat | undefined) ?? null,
minus: (n.minus as NatureStat | undefined) ?? null,
}
}
```
### 3. data/evolution.ts — 委托 Dex.species
```typescript
import { Dex } from '@pkmn/sim'
import type { SpeciesId } from '../types'
export interface EvolutionChainStep {
from: SpeciesId
to: SpeciesId
trigger: 'level_up' | 'item' | 'trade' | 'friendship'
minLevel?: number
}
/** 查找物种的下一个进化(从 Dex 动态获取) */
export function getNextEvolution(speciesId: SpeciesId): EvolutionChainStep | undefined {
const dex = Dex.species.get(speciesId)
if (!dex?.evos?.length) return undefined
const target = dex.evos[0]!.toLowerCase()
const targetDex = Dex.species.get(target)
if (!targetDex?.exists) return undefined
const trigger = dex.evoType === 'trade' ? 'trade'
: dex.evoType === 'useItem' ? 'item'
: dex.evoType === 'levelFriendship' ? 'friendship'
: 'level_up'
return {
from: speciesId,
to: target as SpeciesId,
trigger,
minLevel: targetDex.evoLevel,
}
}
```
### 4. data/pkmn.ts — 统一 gens 单例 + 导出 stat 映射
```typescript
import { Dex } from '@pkmn/sim'
import { Generations } from '@pkmn/data'
import type { StatName } from '../types'
// 统一单例(全包唯一入口)
const gens = new Generations(Dex as unknown as import('@pkmn/data').Dex)
export const gen = gens.get(9)
// Stat key 映射:@pkmn 缩写 → 我们的 StatName
export const FROM_DEX_STAT: Record<string, StatName> = {
hp: 'hp', atk: 'attack', def: 'defense',
spa: 'spAtk', spd: 'spDef', spe: 'speed',
}
// Stat key 映射:我们的 StatName → @pkmn 缩写
export const TO_DEX_STAT: Record<StatName, string> = {
hp: 'hp', attack: 'atk', defense: 'def',
spAtk: 'spa', spDef: 'spd', speed: 'spe',
}
// ...保留现有 getSpecies, getMove, getAbility, getType, mapBaseStats, mapGenderRatio, getPrimaryAbility ...
```
### 5. core/creature.ts — calculateStats 委托 stats.calc
```typescript
import { gen, TO_DEX_STAT } from '../data/pkmn'
import { STAT_NAMES } from '../types'
import type { Creature, StatsResult } from '../types'
export function calculateStats(creature: Creature): StatsResult {
const species = gen.species.get(creature.speciesId)
if (!species) throw new Error(`Species ${creature.speciesId} not found`)
const nature = creature.nature ? gen.natures.get(creature.nature) : undefined
const result = {} as StatsResult
for (const stat of STAT_NAMES) {
const dexKey = TO_DEX_STAT[stat] as 'hp' | 'atk' | 'def' | 'spa' | 'spd' | 'spe'
result[stat] = gen.stats.calc(
dexKey,
species.baseStats[dexKey],
creature.iv[stat],
creature.ev[stat],
creature.level,
nature ?? undefined,
)
}
return result
}
```
**注意**`gen.stats.calc()` 内部已处理 Nature ±10% 修正。Phase -1 计划中的手写 Nature 修正代码不再需要。
### 6. data/species.ts — 简化 buildEvolutionChain
现有的 `buildEvolutionChain()` 已使用 `dex.evos`,只需确保它与新的 `getNextEvolution()` 一致。可简化为:
```typescript
function buildEvolutionChain(speciesId: SpeciesId): SpeciesData['evolutionChain'] {
const evo = getNextEvolution(speciesId)
if (!evo) return undefined
return [{ trigger: evo.trigger, level: evo.minLevel, into: evo.to }]
}
```
## 验证
1. `bun run typecheck` 零错误
2. `bun test packages/pokemon/` 全部通过
3. `bun run dev``/buddy` 所有现有功能正常pet、dex、egg、switch
4. 性格效果正确Adamant Charmander Lv50 ATK 应比 Hardy 高 ~10%SPA 低 ~10%
5. 进化判定正确Charmander Lv16 → CharmeleonSquirtle Lv16 → Wartortle
6. stat 计算结果与旧实现数值一致
## 代码量
- 删除:~55 行NATURES 27 行 + EVOLUTION_CHAINS 12 行 + calculateStats 手写公式 18 行 - 2 行)
- 新增:~40 行(委托代码)
- 净变化:-15 行