claude-code-best/packages/pokemon/src/core/egg.ts
claude-code-best c37b274406 fix: 蛋孵化步数改用真实 hatchCounter 数据
- 孵化步数从 captureRate 反推改为 hatchCounter * 257(原版公式)
- getHatchCounter 支持进化阶段/传说回退分类
- fetch-pokedex-data.ts 已更新以采集 hatchCounter 字段
- 解决 #17 蛋系统孵化步数不准确

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 11:48:40 +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 { getHatchCounter } from '../dex/pokedex-data'
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 real hatch_counter from PokeAPI (steps = cycles * 257)
const hatchCounter = getHatchCounter(speciesId)
const baseSteps = hatchCounter * 257
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 }]
}