mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: 添加 BattleFlow 战斗状态机组件 (Phase 3)
实现完整的战斗 UI 流程:配置 → 战斗 → 换人/道具 → 结果 → 学招 → 进化 → 完成 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
246
packages/pokemon/src/ui/BattleFlow.tsx
Normal file
246
packages/pokemon/src/ui/BattleFlow.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
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)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user