refactor: 代码优化 — 扩展类型覆盖、修复变量遮蔽、移除未使用导入

- engine.ts: 扩展 getSpeciesMoves 覆盖全部 18 种属性
- settlement.ts: 重命名 species → oppSpecies 避免遮蔽外层变量
- storage.ts: addItemToBag/removeItemFromBag 深拷贝 bag items 避免修改原对象
- BattleFlow.tsx: 移除未使用导入和条件 useInput 调用(React hooks 规则)
- BattleView.tsx: 移除未使用的 BattlePokemon/MoveOption/Dex 导入

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-22 00:49:48 +08:00
parent 100b1589f2
commit 74682b2a82
5 changed files with 32 additions and 38 deletions

View File

@@ -19,8 +19,11 @@ function creatureToSetString(creature: Creature): string {
.filter(m => m.id)
.map(m => Dex.moves.get(m.id)?.name ?? m.id)
const ivs = STAT_NAMES.map(s => `${creature.iv[s]} ${TO_DEX_STAT[s].toUpperCase().replace('SPA', 'SpA').replace('SPD', 'SpD')}`).join(' / ')
const evs = STAT_NAMES.map(s => `${creature.ev[s]} ${TO_DEX_STAT[s].toUpperCase().replace('SPA', 'SpA').replace('SPD', 'SpD')}`).join(' / ')
const DEX_DISPLAY: Record<string, string> = { hp: 'HP', atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe' }
const formatStatLine = (vals: Record<string, number>) =>
STAT_NAMES.map(s => `${vals[s]} ${DEX_DISPLAY[TO_DEX_STAT[s]]}`).join(' / ')
const ivs = formatStatLine(creature.iv)
const evs = formatStatLine(creature.ev)
const lines = [
species.name,
@@ -60,6 +63,18 @@ function getSpeciesMoves(speciesId: string, _level: number): string[] {
grass: ['VineWhip', 'RazorLeaf'],
electric: ['ThunderShock', 'Spark'],
poison: ['PoisonSting', 'Smog'],
ice: ['IceShard', 'PowderSnow'],
fighting: ['KarateChop', 'LowKick'],
ground: ['MudSlap', 'SandAttack'],
flying: ['Gust', 'WingAttack'],
psychic: ['Confusion', 'Psybeam'],
bug: ['BugBite', 'StringShot'],
rock: ['RockThrow', 'SandAttack'],
ghost: ['Lick', 'ShadowSneak'],
dragon: ['DragonRage', 'Twister'],
dark: ['Bite', 'Pursuit'],
steel: ['MetalClaw', 'IronTail'],
fairy: ['FairyWind', 'DisarmingVoice'],
}
return basicMoves[type] ?? ['Tackle', 'Scratch']
}
@@ -122,31 +137,24 @@ function projectBoosts(boosts: Record<string, number> | undefined): Record<strin
function parseLogToEvents(log: string[]): BattleEvent[] {
const events: BattleEvent[] = []
const parseSide = (s: string | undefined): 'player' | 'opponent' =>
s?.startsWith('p1a') ? 'player' : 'opponent'
for (const line of log) {
const parts = line.split('|')
const side = parseSide(parts[2])
if (line.startsWith('|move|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const moveName = parts[3]
events.push({ type: 'move', side, move: moveName, user: parts[2] })
events.push({ type: 'move', side, move: parts[3], user: parts[2] })
} else if (line.startsWith('|-damage|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const hpStr = parts[3] // e.g. "16/20" or "16/20[1]"
const [cur, max] = parseHpString(hpStr)
const [cur, max] = parseHpString(parts[3])
events.push({ type: 'damage', side, amount: 0, percentage: Math.round((1 - cur / max) * 100) })
} else if (line.startsWith('|-heal|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const hpStr = parts[3]
const [cur, max] = parseHpString(hpStr)
const [cur, max] = parseHpString(parts[3])
events.push({ type: 'heal', side, amount: 0, percentage: Math.round(cur / max * 100) })
} else if (line.startsWith('|faint|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
events.push({ type: 'faint', side, speciesId: toID(parts[2]?.split(': ')?.[1] ?? '') })
} else if (line.startsWith('|switch|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const speciesPart = parts[3]?.split(',')[0]?.split(': ')
events.push({ type: 'switch', side, speciesId: toID(speciesPart?.[1] ?? ''), name: speciesPart?.[1] ?? '' })
} else if (line.startsWith('|-supereffective|')) {
@@ -156,24 +164,16 @@ function parseLogToEvents(log: string[]): BattleEvent[] {
} else if (line.startsWith('|-crit|')) {
events.push({ type: 'crit' })
} else if (line.startsWith('|-miss|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
events.push({ type: 'miss', side } as any)
} else if (line.startsWith('|-status|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
events.push({ type: 'status', side, status: mapStatus(parts[3]) })
} else if (line.startsWith('|-boost|') || line.startsWith('|-unboost|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const stages = line.startsWith('|-boost|') ? parseInt(parts[4]) : -parseInt(parts[4])
events.push({ type: 'statChange', side, stat: parts[3], stages })
} else if (line.startsWith('|-ability|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
events.push({ type: 'ability', side, ability: parts[3] })
} else if (line.startsWith('|turn|')) {
events.push({ type: 'turn', number: parseInt(line.split('|')[2]) })
events.push({ type: 'turn', number: parseInt(parts[2]) })
}
}
return events

View File

@@ -26,8 +26,8 @@ export function settleBattle(
}
// Calculate XP reward (simplified: base XP from species)
const species = Dex.species.get(opponentSpeciesId)
const baseXp = (species?.baseStats?.hp ?? 50) * opponentLevel / 7
const oppSpecies = Dex.species.get(opponentSpeciesId)
const baseXp = (oppSpecies?.baseStats?.hp ?? 50) * opponentLevel / 7
const xpGained = Math.max(1, Math.floor(baseXp))
// Calculate EV reward

View File

@@ -382,7 +382,7 @@ export function getAllCreatureIds(data: BuddyData): string[] {
// ─── Bag operations ───
export function addItemToBag(data: BuddyData, itemId: string, count = 1): BuddyData {
const items = [...data.bag.items]
const items = data.bag.items.map(e => ({ ...e }))
const existing = items.find(e => e.id === itemId)
if (existing) {
existing.count += count
@@ -393,7 +393,7 @@ export function addItemToBag(data: BuddyData, itemId: string, count = 1): BuddyD
}
export function removeItemFromBag(data: BuddyData, itemId: string, count = 1): { data: BuddyData; removed: boolean } {
const items = [...data.bag.items]
const items = data.bag.items.map(e => ({ ...e }))
const existing = items.find(e => e.id === itemId)
if (!existing || existing.count < count) return { data, removed: false }

View File

@@ -1,7 +1,6 @@
import React, { useState, useCallback } from 'react'
import { Box, Text, useInput } from '@anthropic/ink'
import type { BuddyData, Creature, SpeciesId } from '../types'
import { getActiveCreature, getCreatureName } from '../core/creature'
import { saveBuddyData } from '../core/storage'
import { createBattle, executeTurn, type BattleInit } from '../battle/engine'
import { settleBattle, applyMoveLearn, applyEvolution } from '../battle/settlement'
@@ -232,9 +231,6 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps)
case 'evolution': {
if (pendingEvos.length === 0) return null
const evo = pendingEvos[0]!
useInput(() => {
handleEvolutionConfirm()
})
return (
<Box flexDirection="column" borderStyle="round" paddingX={1}>
<Text bold color="ansi:yellow"> </Text>

View File

@@ -1,8 +1,6 @@
import React from 'react'
import { Box, Text, type Color } from '@anthropic/ink'
import type { BattleState, BattleEvent, BattlePokemon, MoveOption } from '../battle/types'
import { getSpeciesData } from '../data/species'
import { Dex } from '@pkmn/sim'
import type { BattleState, BattleEvent } from '../battle/types'
const CYAN = 'ansi:cyan'
const GREEN = 'ansi:green'