claude-code-best/packages/pokemon/src/core/egg.ts
claude-code-best 12cbb7c4c7 feat: Phase 1 — 数据模型升级 Creature v2 + PCBox/Bag
- 新增 MoveSlot, PCBox, Bag, ItemId 类型
- Creature 扩展 nature/moves/ability/heldItem/pokeball 字段
- BuddyData 升级 v2: 新增 boxes, bag, battlesWon/battlesLost
- 新建 data/learnsets.ts: getDefaultMoveset/getDefaultAbility/getNewLearnableMoves
- storage.ts v1→v2 迁移: 回填 nature/moves/ability,新增 PCBox/Bag
- 新增 PCBox 操作: deposit/withdraw/move/rename/findLocation/release
- 新增 Bag 操作: add/remove/getCount
- generateCreature/loadBuddyData/hatchEgg 改为 async (Dex.learnsets.get 异步)
- 修复 PokedexView: activeCreatureId → party[0]
- 更新测试文件: async/await + v2 BuddyData fixtures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 00:20:08 +08:00

112 lines
3.7 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import type { BuddyData, Creature, Egg, SpeciesId } from '../types'
import { ALL_SPECIES_IDS } from '../types'
import { getSpeciesData } from '../data/species'
import { generateCreature } from './creature'
import { addToParty, depositToBox } from './storage'
/** Days of consecutive coding needed to be eligible for an egg */
export const EGG_REQUIRED_DAYS = 3
/**
* Check if the player is eligible to receive an egg.
* Conditions: consecutiveDays >= EGG_REQUIRED_DAYS AND totalTurns % 50 === 0 AND eggs.length < 1
*/
export function checkEggEligibility(buddyData: BuddyData): boolean {
if (buddyData.eggs.length >= 1) return false
if (buddyData.stats.consecutiveDays < EGG_REQUIRED_DAYS) return false
if (buddyData.stats.totalTurns % 50 !== 0) return false
return true
}
/**
* Generate a new egg with a species the player hasn't collected yet.
* Priority: uncollected species > random from all species.
*/
export function generateEgg(buddyData: BuddyData): Egg {
// Find uncollected species
const collectedSpecies = new Set(buddyData.creatures.map((c) => c.speciesId))
const uncollected = ALL_SPECIES_IDS.filter((id) => !collectedSpecies.has(id))
// Pick species (prefer uncollected, fall back to random starter)
const starters: SpeciesId[] = ['bulbasaur', 'charmander', 'squirtle', 'pikachu']
const speciesId = uncollected.length > 0
? uncollected[Math.floor(Math.random() * uncollected.length)]
: starters[Math.floor(Math.random() * starters.length)]
// Steps based on rarity (capture rate: lower = rarer = more steps)
const species = getSpeciesData(speciesId)
const baseSteps = Math.floor(2000 + ((255 - species.captureRate) / 255) * 3000)
return {
id: randomUUID(),
obtainedAt: Date.now(),
stepsRemaining: baseSteps,
totalSteps: baseSteps,
speciesId,
}
}
/**
* Advance egg steps by a given amount.
* Returns updated egg or null if egg hatched.
*/
export function advanceEggSteps(egg: Egg, steps: number): Egg {
const newSteps = Math.max(0, egg.stepsRemaining - steps)
return { ...egg, stepsRemaining: newSteps }
}
/**
* Check if an egg is ready to hatch.
*/
export function isEggReadyToHatch(egg: Egg): boolean {
return egg.stepsRemaining <= 0
}
/**
* Hatch an egg, creating a new creature and updating buddy data.
* Tries to add to party first, then deposits to PC box.
*/
export async function hatchEgg(buddyData: BuddyData, egg: Egg): Promise<{ buddyData: BuddyData; creature: Creature }> {
const creature = await generateCreature(egg.speciesId)
creature.hatchedAt = Date.now()
// Add creature to list
let updatedData: BuddyData = {
...buddyData,
creatures: [...buddyData.creatures, creature],
eggs: buddyData.eggs.filter((e) => e.id !== egg.id),
dex: updateDexEntry(buddyData.dex, egg.speciesId, creature.level),
stats: {
...buddyData.stats,
totalEggsObtained: buddyData.stats.totalEggsObtained + 1,
},
}
// Place in party or PC box
const partyResult = addToParty(updatedData, creature.id)
if (partyResult.added) {
updatedData = partyResult.data
} else {
const boxResult = depositToBox(updatedData, creature.id)
if (boxResult.deposited) updatedData = boxResult.data
}
return { buddyData: updatedData, creature }
}
/**
* Update or create a dex entry for a species.
*/
function updateDexEntry(dex: BuddyData['dex'], speciesId: SpeciesId, level: number): BuddyData['dex'] {
const existing = dex.find((d) => d.speciesId === speciesId)
if (existing) {
return dex.map((d) =>
d.speciesId === speciesId
? { ...d, caughtCount: d.caughtCount + 1, bestLevel: Math.max(d.bestLevel, level) }
: d,
)
}
return [...dex, { speciesId, discoveredAt: Date.now(), caughtCount: 1, bestLevel: level }]
}