From 3c64113d77b587e081ec97bc45866e08b02c67cd Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Wed, 22 Apr 2026 06:07:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20BattleFlow=20?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E9=94=AE=E7=9B=98=E8=BE=93=E5=85=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 所有 phase 现在都支持键盘交互: - config: Enter/1=随机战斗, 2=指定对手, ESC=取消 - configSelect: Enter=确认, ESC=返回 - battle: 1-4=选招, S=换人, I=道具 - switch: 1-6=选队友, ESC=取消 - item: 1-9=选道具, ESC=取消 - result: Enter=继续 - learnMoves: 1-4=替换招式, S=跳过 - evolution: Enter=确认进化 Co-Authored-By: Claude Opus 4.6 --- packages/pokemon/src/ui/BattleFlow.tsx | 145 +++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 12 deletions(-) diff --git a/packages/pokemon/src/ui/BattleFlow.tsx b/packages/pokemon/src/ui/BattleFlow.tsx index 04cb20d6e..1de91b5aa 100644 --- a/packages/pokemon/src/ui/BattleFlow.tsx +++ b/packages/pokemon/src/ui/BattleFlow.tsx @@ -1,6 +1,7 @@ import React, { useState, useCallback } from 'react' import { Box, Text, useInput } from '@anthropic/ink' import type { BuddyData, Creature, SpeciesId } from '../types' +import { ALL_SPECIES_IDS } from '../types' import { saveBuddyData } from '../core/storage' import { createBattle, executeTurn, type BattleInit } from '../battle/engine' import { settleBattle, applyMoveLearn, applyEvolution } from '../battle/settlement' @@ -10,10 +11,12 @@ import { SwitchPanel } from './SwitchPanel' import { ItemPanel } from './ItemPanel' import { BattleResultPanel } from './BattleResultPanel' import { MoveLearnPanel } from './MoveLearnPanel' +import { chooseAIMove } from '../battle/ai' import type { BattleState, PlayerAction } from '../battle/types' type Phase = | 'config' + | 'configSelect' | 'battle' | 'switch' | 'item' @@ -38,13 +41,136 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) 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() + // ─── Input handling ─── + + useInput((input: string, key: { escape?: boolean; return?: boolean; upArrow?: boolean; downArrow?: boolean }) => { + // Config phase: Enter = random battle, ESC = cancel + if (phase === 'config') { + if (key.escape) { + onClose() + } else if (key.return || input === '1') { + handleRandomBattle() + } else if (input === '2') { + setPhase('configSelect') + } + return + } + + // Config select: pick species by number + if (phase === 'configSelect') { + if (key.escape) { + setPhase('config') + } else if (key.return) { + handleStartBattle(opponentSpeciesId, buddyData.party[0] ? getActiveCreatureLevel() : 5) + } + return + } + + // Battle phase: 1-4 = move, S = switch, I = item, ESC = cancel + if (phase === 'battle') { + if (key.escape) { + // Can't flee from wild battle - do nothing + return + } + if (input >= '1' && input <= '4') { + const idx = parseInt(input) - 1 + if (battleState && idx < battleState.playerPokemon.moves.length) { + handleAction({ type: 'move', moveIndex: idx }) + } + } else if (input.toLowerCase() === 's') { + setPhase('switch') + } else if (input.toLowerCase() === 'i') { + setPhase('item') + } + return + } + + // Switch phase: 1-6 = select, ESC = cancel + if (phase === 'switch') { + if (key.escape) { + setPhase('battle') + } else if (input >= '1' && input <= '6') { + const idx = parseInt(input) - 1 + const partyCreatures = getPartyCreatures() + if (battleState && partyCreatures[idx] && partyCreatures[idx]!.id !== battleState.playerPokemon.id) { + handleAction({ type: 'switch', creatureId: partyCreatures[idx]!.id }) + setPhase('battle') + } + } + return + } + + // Item phase: 1-9 = select item, ESC = cancel + if (phase === 'item') { + if (key.escape) { + setPhase('battle') + } else if (input >= '1' && input <= '9') { + if (battleState) { + const idx = parseInt(input) - 1 + const items = battleState.usableItems + if (items[idx]) { + handleAction({ type: 'item', itemId: items[idx]!.id }) + setPhase('battle') + } + } + } + return + } + + // Result phase: Enter = continue + if (phase === 'result') { + if (key.return) { + handleResultContinue() + } + return + } + + // Move learn phase: 1-4 = replace, S = skip + if (phase === 'learnMoves') { + if (input.toLowerCase() === 's') { + handleMoveSkip() + } else if (input >= '1' && input <= '4') { + const idx = parseInt(input) - 1 + setReplaceIndex(idx) + handleMoveLearn(idx) + } + return + } + + // Evolution phase: Enter = confirm + if (phase === 'evolution') { + if (key.return) { + handleEvolutionConfirm() + } + return } }) + // ─── Helpers ─── + + function getActiveCreatureLevel(): number { + const id = buddyData.party[0] + if (!id) return 5 + const c = buddyData.creatures.find(cr => cr.id === id) + return c?.level ?? 5 + } + + function getPartyCreatures(): Creature[] { + return 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) + } + + // ─── Actions ─── + + const handleRandomBattle = useCallback(() => { + const opponentLevel = getActiveCreatureLevel() + const speciesList = ALL_SPECIES_IDS + const randomSpecies = speciesList[Math.floor(Math.random() * speciesList.length)]! + handleStartBattle(randomSpecies, opponentLevel) + }, [buddyData]) + // Config phase: start battle const handleStartBattle = useCallback((speciesId: SpeciesId, level: number) => { setOpponentSpeciesId(speciesId) @@ -144,18 +270,13 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) } }, [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': + case 'configSelect': return ( @@ -175,7 +296,7 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) if (!battleState) return null return ( { handleAction({ type: 'switch', creatureId })