mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
fix: PokedexView 改为百分比统计视图,不再渲染全部 1025 只精灵
- 进度条固定 30 字符宽度,按百分比填充 - 新增分代统计(Gen I-IX),每代显示迷你进度条 - 只展示已发现的精灵,而非全部 1025 条 - 删除 groupByChain() 及进化链渲染(列表太长) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,105 +3,140 @@ import { Box, Text, type Color } from '@anthropic/ink'
|
|||||||
import type { BuddyData, SpeciesId } from '../types'
|
import type { BuddyData, SpeciesId } from '../types'
|
||||||
import { ALL_SPECIES_IDS } from '../types'
|
import { ALL_SPECIES_IDS } from '../types'
|
||||||
import { getSpeciesData } from '../dex/species'
|
import { getSpeciesData } from '../dex/species'
|
||||||
import { getNextEvolution } from '../dex/evolution'
|
|
||||||
|
|
||||||
const CYAN: Color = 'ansi:cyan'
|
const CYAN: Color = 'ansi:cyan'
|
||||||
const GREEN: Color = 'ansi:green'
|
const GREEN: Color = 'ansi:green'
|
||||||
const GRAY: Color = 'ansi:white'
|
const GRAY: Color = 'ansi:white'
|
||||||
const YELLOW: Color = 'ansi:yellow'
|
const YELLOW: Color = 'ansi:yellow'
|
||||||
const WHITE: Color = 'ansi:whiteBright'
|
const WHITE: Color = 'ansi:whiteBright'
|
||||||
const RED: Color = 'ansi:red'
|
|
||||||
const BLUE: Color = 'ansi:blue'
|
const BAR_WIDTH = 30
|
||||||
|
|
||||||
|
/** Gen ranges for stats */
|
||||||
|
const GEN_RANGES = [
|
||||||
|
{ label: 'Gen I', start: 1, end: 151 },
|
||||||
|
{ label: 'Gen II', start: 152, end: 251 },
|
||||||
|
{ label: 'Gen III', start: 252, end: 386 },
|
||||||
|
{ label: 'Gen IV', start: 387, end: 493 },
|
||||||
|
{ label: 'Gen V', start: 494, end: 649 },
|
||||||
|
{ label: 'Gen VI', start: 650, end: 721 },
|
||||||
|
{ label: 'Gen VII', start: 722, end: 809 },
|
||||||
|
{ label: 'Gen VIII',start: 810, end: 905 },
|
||||||
|
{ label: 'Gen IX', start: 906, end: 1025 },
|
||||||
|
]
|
||||||
|
|
||||||
interface PokedexViewProps {
|
interface PokedexViewProps {
|
||||||
buddyData: BuddyData
|
buddyData: BuddyData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pokédex view — shows all species with collection status,
|
* Pokédex view — shows collection progress, per-gen stats,
|
||||||
* evolution chains, and active creature indicator.
|
* and discovered species list.
|
||||||
*/
|
*/
|
||||||
export function PokedexView({ buddyData }: PokedexViewProps) {
|
export function PokedexView({ buddyData }: PokedexViewProps) {
|
||||||
const dexMap = new Map(buddyData.dex.map((d) => [d.speciesId, d]))
|
const dexMap = new Map(buddyData.dex.map((d) => [d.speciesId, d]))
|
||||||
const collected = buddyData.dex.length
|
const collected = buddyData.dex.length
|
||||||
const total = ALL_SPECIES_IDS.length
|
const total = ALL_SPECIES_IDS.length
|
||||||
|
const percent = total > 0 ? collected / total : 0
|
||||||
|
|
||||||
// Group species by evolution chain
|
// Build dex number set for quick lookup
|
||||||
const chains = groupByChain()
|
const collectedNums = new Set<number>()
|
||||||
|
for (const entry of buddyData.dex) {
|
||||||
|
const data = getSpeciesData(entry.speciesId)
|
||||||
|
collectedNums.add(data.dexNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-gen stats
|
||||||
|
const genStats = GEN_RANGES.map(g => {
|
||||||
|
const genTotal = ALL_SPECIES_IDS.filter(id => {
|
||||||
|
const n = getSpeciesData(id).dexNumber
|
||||||
|
return n >= g.start && n <= g.end
|
||||||
|
}).length
|
||||||
|
const genCollected = [...collectedNums].filter(n => n >= g.start && n <= g.end).length
|
||||||
|
return { ...g, total: genTotal, collected: genCollected }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Discovered species (for compact display)
|
||||||
|
const discovered = buddyData.dex
|
||||||
|
.map(entry => {
|
||||||
|
const species = getSpeciesData(entry.speciesId)
|
||||||
|
return { entry, species }
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.species.dexNumber - b.species.dexNumber)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" borderStyle="round" paddingX={1}>
|
<Box flexDirection="column" borderStyle="round" paddingX={1}>
|
||||||
{/* Header */}
|
{/* Header with percentage */}
|
||||||
<Box justifyContent="space-between" marginBottom={0}>
|
<Box justifyContent="space-between">
|
||||||
<Text bold color={CYAN}>Pokédex</Text>
|
<Text bold color={CYAN}>Pokédex</Text>
|
||||||
<Text>
|
<Text>
|
||||||
<Text bold color={collected === total ? GREEN : WHITE}>{collected}</Text>
|
<Text bold color={collected === total ? GREEN : WHITE}>{collected}</Text>
|
||||||
<Text color={GRAY}>/{total} </Text>
|
<Text color={GRAY}>/{total} </Text>
|
||||||
<Text color={GRAY}>collected</Text>
|
<Text bold color={GREEN}>{(percent * 100).toFixed(1)}%</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Progress bar */}
|
{/* Fixed-width progress bar */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={GREEN}>{'█'.repeat(collected)}</Text>
|
<Text color={GREEN}>{'█'.repeat(Math.round(percent * BAR_WIDTH))}</Text>
|
||||||
<Text color={GRAY}>{'░'.repeat(total - collected)}</Text>
|
<Text color={GRAY}>{'░'.repeat(BAR_WIDTH - Math.round(percent * BAR_WIDTH))}</Text>
|
||||||
<Text> {Math.floor((collected / total) * 100)}%</Text>
|
<Text> {Math.floor(percent * 100)}%</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Species list grouped by evolution chains */}
|
{/* Per-gen stats */}
|
||||||
{chains.map((chain, ci) => (
|
<Box flexDirection="column" marginTop={0}>
|
||||||
<Box key={ci} flexDirection="column" marginTop={ci > 0 ? 0 : 0}>
|
<Text color={GRAY}>─── 分代统计 ───</Text>
|
||||||
{chain.map((speciesId, si) => {
|
{genStats.map(g => {
|
||||||
const species = getSpeciesData(speciesId)
|
const p = g.total > 0 ? g.collected / g.total : 0
|
||||||
const entry = dexMap.get(speciesId)
|
const miniBar = '█'.repeat(Math.round(p * 10)) + '░'.repeat(10 - Math.round(p * 10))
|
||||||
const discovered = !!entry
|
return (
|
||||||
const isActive = buddyData.party[0]
|
<Box key={g.label}>
|
||||||
? buddyData.creatures.some((c) => c.id === buddyData.party[0] && c.speciesId === speciesId)
|
<Text color={GRAY}>{g.label.padEnd(8)}</Text>
|
||||||
: false
|
<Text color={p >= 1 ? GREEN : p > 0 ? YELLOW : GRAY}>{miniBar}</Text>
|
||||||
const nextEvo = getNextEvolution(speciesId)
|
<Text> <Text bold>{g.collected}</Text><Text color={GRAY}>/{g.total}</Text></Text>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Discovered species list */}
|
||||||
|
{discovered.length > 0 && (
|
||||||
|
<Box flexDirection="column" marginTop={0}>
|
||||||
|
<Text color={GRAY}>─── 已发现 ({discovered.length}) ───</Text>
|
||||||
|
{discovered.map(({ entry, species }) => {
|
||||||
|
const isActive = buddyData.party[0]
|
||||||
|
? buddyData.creatures.some(c => c.id === buddyData.party[0] && c.speciesId === species.id)
|
||||||
|
: false
|
||||||
return (
|
return (
|
||||||
<Box key={speciesId} flexDirection="column">
|
<Box key={species.id}>
|
||||||
<Box>
|
<Text>{isActive ? <Text color={YELLOW}>▶</Text> : ' '}</Text>
|
||||||
{/* Chain connector */}
|
<Text color={GRAY}>#{String(species.dexNumber).padStart(3, '0')} </Text>
|
||||||
<Text color={GRAY}>{si === 0 ? ' ' : '├'}</Text>
|
<Text color={WHITE} bold={isActive}>
|
||||||
{/* Active indicator */}
|
{(species.names as Record<string, string>).zh ?? species.name}
|
||||||
<Text>{isActive ? <Text color={YELLOW}>▶</Text> : ' '}</Text>
|
</Text>
|
||||||
{/* Dex number */}
|
<Text>
|
||||||
<Text color={GRAY}>#{String(species.dexNumber).padStart(3, '0')} </Text>
|
{' '}
|
||||||
{/* Name */}
|
{species.types.filter((t): t is string => Boolean(t)).map((t, ti) => (
|
||||||
<Text color={discovered ? WHITE : GRAY} bold={isActive}>
|
<Text key={t} color={getTypeColor(t)}>
|
||||||
{discovered
|
{ti > 0 ? '/' : ''}{t.slice(0, 3).toUpperCase()}
|
||||||
? (species.names.zh ?? species.name)
|
|
||||||
: '???'}
|
|
||||||
</Text>
|
|
||||||
{/* Type badges */}
|
|
||||||
{discovered && (
|
|
||||||
<Text>
|
|
||||||
{' '}
|
|
||||||
{species.types.filter((t): t is string => Boolean(t)).map((t, ti) => (
|
|
||||||
<Text key={t} color={getTypeColor(t)}>
|
|
||||||
{ti > 0 ? '/' : ''}{t.slice(0, 3).toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
))}
|
||||||
{/* Level / unknown indicator */}
|
</Text>
|
||||||
{discovered && entry ? (
|
<Text color={GREEN}> Lv.{entry.bestLevel}</Text>
|
||||||
<Text color={GREEN}> Lv.{entry.bestLevel}</Text>
|
{entry.caughtCount > 1 && (
|
||||||
) : (
|
<Text color={GRAY}> x{entry.caughtCount}</Text>
|
||||||
<Text color={GRAY}> ───</Text>
|
)}
|
||||||
)}
|
|
||||||
{/* Evolution arrow */}
|
|
||||||
{nextEvo && (
|
|
||||||
<Text color={GRAY}> →<Text color={CYAN}>Lv.{nextEvo.minLevel}</Text></Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
)}
|
||||||
|
|
||||||
|
{discovered.length === 0 && (
|
||||||
|
<Box marginTop={0}>
|
||||||
|
<Text dimColor> 还没有发现任何精灵,开始冒险吧!</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Stats row */}
|
{/* Stats row */}
|
||||||
<Box marginTop={0} flexDirection="column">
|
<Box marginTop={0} flexDirection="column">
|
||||||
@@ -151,36 +186,3 @@ function getTypeColor(type: string): Color {
|
|||||||
}
|
}
|
||||||
return colors[type] ?? 'ansi:white'
|
return colors[type] ?? 'ansi:white'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Group species by evolution chain for visual display */
|
|
||||||
function groupByChain(): SpeciesId[][] {
|
|
||||||
const visited = new Set<SpeciesId>()
|
|
||||||
const chains: SpeciesId[][] = []
|
|
||||||
|
|
||||||
for (const id of ALL_SPECIES_IDS) {
|
|
||||||
if (visited.has(id)) continue
|
|
||||||
|
|
||||||
// Walk back to find chain head
|
|
||||||
let head: SpeciesId = id
|
|
||||||
for (const candidate of ALL_SPECIES_IDS) {
|
|
||||||
const evo = getNextEvolution(candidate)
|
|
||||||
if (evo?.to === head) {
|
|
||||||
head = candidate
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk forward to build chain
|
|
||||||
const chain: SpeciesId[] = []
|
|
||||||
let current: SpeciesId | undefined = head
|
|
||||||
while (current && !visited.has(current)) {
|
|
||||||
chain.push(current)
|
|
||||||
visited.add(current)
|
|
||||||
current = getNextEvolution(current)?.to
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chain.length > 0) chains.push(chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chains
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user