mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
- BattleFlow.tsx: useInput hook 提升到顶层避免 React hooks 规则违规 - 删除未使用的 battle/adapter.ts 和 battle/handler.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
254 lines
7.1 KiB
TypeScript
254 lines
7.1 KiB
TypeScript
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'
|
||
import { BattleConfigPanel } from './BattleConfigPanel'
|
||
import { BattleView } from './BattleView'
|
||
import { SwitchPanel } from './SwitchPanel'
|
||
import { ItemPanel } from './ItemPanel'
|
||
import { BattleResultPanel } from './BattleResultPanel'
|
||
import { MoveLearnPanel } from './MoveLearnPanel'
|
||
import type { BattleState, PlayerAction } from '../battle/types'
|
||
|
||
type Phase =
|
||
| 'config'
|
||
| 'battle'
|
||
| 'switch'
|
||
| 'item'
|
||
| 'result'
|
||
| 'learnMoves'
|
||
| 'evolution'
|
||
| 'done'
|
||
|
||
interface BattleFlowProps {
|
||
buddyData: BuddyData
|
||
onClose: () => void
|
||
}
|
||
|
||
export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) {
|
||
const [phase, setPhase] = useState<Phase>('config')
|
||
const [buddyData, setBuddyData] = useState(initialData)
|
||
const [battleInit, setBattleInit] = useState<BattleInit | null>(null)
|
||
const [battleState, setBattleState] = useState<BattleState | null>(null)
|
||
const [opponentSpeciesId, setOpponentSpeciesId] = useState<SpeciesId>('pikachu')
|
||
const [opponentLevel, setOpponentLevel] = useState(5)
|
||
const [pendingMoves, setPendingMoves] = useState<{ creatureId: string; moveId: string; moveName: string }[]>([])
|
||
const [pendingEvos, setPendingEvos] = useState<{ creatureId: string; from: SpeciesId; to: SpeciesId }[]>([])
|
||
const [replaceIndex, setReplaceIndex] = useState(0)
|
||
|
||
// Evolution phase input — must be at top level (React hooks rule)
|
||
useInput((_input: string, key: { return?: boolean }) => {
|
||
if (phase === 'evolution' && key.return) {
|
||
handleEvolutionConfirm()
|
||
}
|
||
})
|
||
|
||
// Config phase: start battle
|
||
const handleStartBattle = useCallback((speciesId: SpeciesId, level: number) => {
|
||
setOpponentSpeciesId(speciesId)
|
||
setOpponentLevel(level)
|
||
|
||
const creatures = buddyData.party
|
||
.filter((id): id is string => id !== null)
|
||
.map(id => buddyData.creatures.find(c => c.id === id))
|
||
.filter((c): c is Creature => c !== undefined)
|
||
|
||
if (creatures.length === 0) return
|
||
|
||
const bagItems = buddyData.bag.items
|
||
const init = createBattle(creatures, speciesId, level, bagItems)
|
||
setBattleInit(init)
|
||
setBattleState(init.state)
|
||
setPhase('battle')
|
||
}, [buddyData])
|
||
|
||
// Battle phase: handle action
|
||
const handleAction = useCallback((action: PlayerAction) => {
|
||
if (!battleInit) return
|
||
const state = executeTurn(battleInit, action)
|
||
setBattleState(state)
|
||
|
||
if (state.finished && state.result) {
|
||
const participants = buddyData.party.filter((id): id is string => id !== null)
|
||
const result = { ...state.result, participantIds: participants }
|
||
const settled = settleBattle(buddyData, result, opponentSpeciesId, opponentLevel)
|
||
|
||
setBuddyData(settled.data)
|
||
setPendingMoves(settled.learnableMoves)
|
||
setPendingEvos(settled.pendingEvolutions)
|
||
setBattleState({ ...state, result })
|
||
setPhase('result')
|
||
}
|
||
}, [battleInit, buddyData, opponentSpeciesId, opponentLevel])
|
||
|
||
// Result phase: continue to move learning
|
||
const handleResultContinue = useCallback(() => {
|
||
if (pendingMoves.length > 0) {
|
||
setPhase('learnMoves')
|
||
} else if (pendingEvos.length > 0) {
|
||
setPhase('evolution')
|
||
} else {
|
||
saveBuddyData(buddyData)
|
||
setPhase('done')
|
||
onClose()
|
||
}
|
||
}, [pendingMoves, pendingEvos, buddyData, onClose])
|
||
|
||
// Move learning
|
||
const handleMoveLearn = useCallback((idx: number) => {
|
||
if (pendingMoves.length === 0) return
|
||
const move = pendingMoves[0]!
|
||
const updated = applyMoveLearn(buddyData, move.creatureId, move.moveId, idx)
|
||
setBuddyData(updated)
|
||
const remaining = pendingMoves.slice(1)
|
||
setPendingMoves(remaining)
|
||
if (remaining.length === 0) {
|
||
if (pendingEvos.length > 0) {
|
||
setPhase('evolution')
|
||
} else {
|
||
saveBuddyData(updated)
|
||
setPhase('done')
|
||
onClose()
|
||
}
|
||
}
|
||
}, [pendingMoves, pendingEvos, buddyData, onClose])
|
||
|
||
const handleMoveSkip = useCallback(() => {
|
||
const remaining = pendingMoves.slice(1)
|
||
setPendingMoves(remaining)
|
||
if (remaining.length === 0) {
|
||
if (pendingEvos.length > 0) {
|
||
setPhase('evolution')
|
||
} else {
|
||
saveBuddyData(buddyData)
|
||
setPhase('done')
|
||
onClose()
|
||
}
|
||
}
|
||
}, [pendingMoves, pendingEvos, buddyData, onClose])
|
||
|
||
// Evolution
|
||
const handleEvolutionConfirm = useCallback(() => {
|
||
if (pendingEvos.length === 0) return
|
||
const evo = pendingEvos[0]!
|
||
const updated = applyEvolution(buddyData, evo.creatureId, evo.to)
|
||
setBuddyData(updated)
|
||
const remaining = pendingEvos.slice(1)
|
||
setPendingEvos(remaining)
|
||
if (remaining.length === 0) {
|
||
saveBuddyData(updated)
|
||
setPhase('done')
|
||
onClose()
|
||
}
|
||
}, [pendingEvos, buddyData, onClose])
|
||
|
||
// Switch: convert BattlePokemon to Creature for SwitchPanel
|
||
const partyCreatures = buddyData.party
|
||
.filter((id): id is string => id !== null)
|
||
.map(id => buddyData.creatures.find(c => c.id === id))
|
||
.filter((c): c is Creature => c !== undefined)
|
||
|
||
// Render by phase
|
||
switch (phase) {
|
||
case 'config':
|
||
return (
|
||
<BattleConfigPanel
|
||
party={partyCreatures}
|
||
onSubmit={handleStartBattle}
|
||
onCancel={onClose}
|
||
/>
|
||
)
|
||
|
||
case 'battle': {
|
||
if (!battleState) return null
|
||
return (
|
||
<BattleView
|
||
state={battleState}
|
||
onAction={handleAction}
|
||
/>
|
||
)
|
||
}
|
||
|
||
case 'switch': {
|
||
if (!battleState) return null
|
||
return (
|
||
<SwitchPanel
|
||
party={partyCreatures}
|
||
activeId={battleState.playerPokemon.id}
|
||
onSelect={(creatureId) => {
|
||
handleAction({ type: 'switch', creatureId })
|
||
setPhase('battle')
|
||
}}
|
||
onCancel={() => setPhase('battle')}
|
||
/>
|
||
)
|
||
}
|
||
|
||
case 'item': {
|
||
if (!battleState) return null
|
||
return (
|
||
<ItemPanel
|
||
items={battleState.usableItems}
|
||
onSelect={(itemId) => {
|
||
handleAction({ type: 'item', itemId })
|
||
setPhase('battle')
|
||
}}
|
||
onCancel={() => setPhase('battle')}
|
||
/>
|
||
)
|
||
}
|
||
|
||
case 'result': {
|
||
if (!battleState?.result) return null
|
||
return (
|
||
<BattleResultPanel
|
||
result={battleState.result}
|
||
playerPokemon={battleState.playerPokemon}
|
||
onContinue={handleResultContinue}
|
||
/>
|
||
)
|
||
}
|
||
|
||
case 'learnMoves': {
|
||
if (pendingMoves.length === 0) return null
|
||
const move = pendingMoves[0]!
|
||
const creature = buddyData.creatures.find(c => c.id === move.creatureId)
|
||
if (!creature) return null
|
||
return (
|
||
<MoveLearnPanel
|
||
creature={creature}
|
||
newMoveId={move.moveId}
|
||
replaceIndex={replaceIndex}
|
||
onLearn={handleMoveLearn}
|
||
onSkip={handleMoveSkip}
|
||
onSelectReplace={setReplaceIndex}
|
||
/>
|
||
)
|
||
}
|
||
|
||
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>
|
||
<Text> {evo.from} 正在进化为 {evo.to}!</Text>
|
||
<Text color="ansi:white"> [Enter] 继续</Text>
|
||
</Box>
|
||
)
|
||
}
|
||
|
||
case 'done':
|
||
return null
|
||
|
||
default:
|
||
return null
|
||
}
|
||
}
|