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) .filter(m => m.id)
.map(m => Dex.moves.get(m.id)?.name ?? 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 DEX_DISPLAY: Record<string, string> = { hp: 'HP', atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe' }
const evs = STAT_NAMES.map(s => `${creature.ev[s]} ${TO_DEX_STAT[s].toUpperCase().replace('SPA', 'SpA').replace('SPD', 'SpD')}`).join(' / ') 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 = [ const lines = [
species.name, species.name,
@@ -60,6 +63,18 @@ function getSpeciesMoves(speciesId: string, _level: number): string[] {
grass: ['VineWhip', 'RazorLeaf'], grass: ['VineWhip', 'RazorLeaf'],
electric: ['ThunderShock', 'Spark'], electric: ['ThunderShock', 'Spark'],
poison: ['PoisonSting', 'Smog'], 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'] return basicMoves[type] ?? ['Tackle', 'Scratch']
} }
@@ -122,31 +137,24 @@ function projectBoosts(boosts: Record<string, number> | undefined): Record<strin
function parseLogToEvents(log: string[]): BattleEvent[] { function parseLogToEvents(log: string[]): BattleEvent[] {
const events: BattleEvent[] = [] const events: BattleEvent[] = []
const parseSide = (s: string | undefined): 'player' | 'opponent' =>
s?.startsWith('p1a') ? 'player' : 'opponent'
for (const line of log) { for (const line of log) {
const parts = line.split('|')
const side = parseSide(parts[2])
if (line.startsWith('|move|')) { if (line.startsWith('|move|')) {
const parts = line.split('|') events.push({ type: 'move', side, move: parts[3], user: parts[2] })
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const moveName = parts[3]
events.push({ type: 'move', side, move: moveName, user: parts[2] })
} else if (line.startsWith('|-damage|')) { } else if (line.startsWith('|-damage|')) {
const parts = line.split('|') const [cur, max] = parseHpString(parts[3])
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)
events.push({ type: 'damage', side, amount: 0, percentage: Math.round((1 - cur / max) * 100) }) events.push({ type: 'damage', side, amount: 0, percentage: Math.round((1 - cur / max) * 100) })
} else if (line.startsWith('|-heal|')) { } else if (line.startsWith('|-heal|')) {
const parts = line.split('|') const [cur, max] = parseHpString(parts[3])
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const hpStr = parts[3]
const [cur, max] = parseHpString(hpStr)
events.push({ type: 'heal', side, amount: 0, percentage: Math.round(cur / max * 100) }) events.push({ type: 'heal', side, amount: 0, percentage: Math.round(cur / max * 100) })
} else if (line.startsWith('|faint|')) { } 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] ?? '') }) events.push({ type: 'faint', side, speciesId: toID(parts[2]?.split(': ')?.[1] ?? '') })
} else if (line.startsWith('|switch|')) { } else if (line.startsWith('|switch|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
const speciesPart = parts[3]?.split(',')[0]?.split(': ') const speciesPart = parts[3]?.split(',')[0]?.split(': ')
events.push({ type: 'switch', side, speciesId: toID(speciesPart?.[1] ?? ''), name: speciesPart?.[1] ?? '' }) events.push({ type: 'switch', side, speciesId: toID(speciesPart?.[1] ?? ''), name: speciesPart?.[1] ?? '' })
} else if (line.startsWith('|-supereffective|')) { } else if (line.startsWith('|-supereffective|')) {
@@ -156,24 +164,16 @@ function parseLogToEvents(log: string[]): BattleEvent[] {
} else if (line.startsWith('|-crit|')) { } else if (line.startsWith('|-crit|')) {
events.push({ type: 'crit' }) events.push({ type: 'crit' })
} else if (line.startsWith('|-miss|')) { } else if (line.startsWith('|-miss|')) {
const parts = line.split('|')
const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent'
events.push({ type: 'miss', side } as any) events.push({ type: 'miss', side } as any)
} else if (line.startsWith('|-status|')) { } 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]) }) events.push({ type: 'status', side, status: mapStatus(parts[3]) })
} else if (line.startsWith('|-boost|') || line.startsWith('|-unboost|')) { } 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]) const stages = line.startsWith('|-boost|') ? parseInt(parts[4]) : -parseInt(parts[4])
events.push({ type: 'statChange', side, stat: parts[3], stages }) events.push({ type: 'statChange', side, stat: parts[3], stages })
} else if (line.startsWith('|-ability|')) { } 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] }) events.push({ type: 'ability', side, ability: parts[3] })
} else if (line.startsWith('|turn|')) { } else if (line.startsWith('|turn|')) {
events.push({ type: 'turn', number: parseInt(line.split('|')[2]) }) events.push({ type: 'turn', number: parseInt(parts[2]) })
} }
} }
return events return events

View File

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

View File

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

View File

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

View File

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