"use client"; // 回响星核 / Echo Nexus — 解码共振谜题(核心原创玩法) // // 谐振序列:按目标颜色顺序点击四邻接节点,重建回路。 import { useState } from "react"; import { useGameStore } from "@/store/gameStore"; import { useToast } from "@/hooks/use-toast"; import { sfx } from "@/hooks/useAudio"; import { COLOR_VISUAL } from "@/lib/game/config"; import { isSolvable, canStartFrom } from "@/lib/game/decode"; import type { DecodeNode, ResonanceColor } from "@/lib/game/types"; import { Button } from "@/components/ui/button"; import { RotateCcw, X, Sparkles, Check, Undo2, AlertTriangle } from "lucide-react"; export function DecodeArray() { const activePuzzle = useGameStore((s) => s.activePuzzle); const clickNode = useGameStore((s) => s.clickNode); const undoStep = useGameStore((s) => s.undoStep); const retryPuzzle = useGameStore((s) => s.retryPuzzle); const abandonPuzzle = useGameStore((s) => s.abandonPuzzle); const { toast } = useToast(); const [hoverId, setHoverId] = useState(null); const [flash, setFlash] = useState<{ id: number; ok: boolean } | null>(null); const [warned, setWarned] = useState(false); if (!activePuzzle) return null; const { grid, rows, cols, target, path, stepLimit, tier } = activePuzzle; const currentTargetIdx = path.length; const currentColor: ResonanceColor | undefined = target[currentTargetIdx]; // 当前局面是否仍可解(精确判定) const stuck = path.length > 0 && path.length < target.length && !isSolvable(activePuzzle); const handleNodeClick = (node: DecodeNode) => { const res = clickNode(node.id); if (res.ok && res.finished) { setWarned(false); sfx("decodeSuccess"); if (res.failReason) { const ids = res.failReason.split(",").filter(Boolean); if (ids.length) { sfx("fragmentUnlock"); toast({ title: "✦ 记忆碎片浮现", description: "新的回响被拼入图谱,前往「记忆图谱」查看。", }); } } toast({ title: "谐振闭合!", description: "晶体解码成功,记忆已释放。", }); return; } if (!res.ok) { sfx("decodeFail"); setFlash({ id: node.id, ok: false }); setTimeout(() => setFlash(null), 280); } else { sfx("decodeClick", { color: node.color }); setFlash({ id: node.id, ok: true }); setTimeout(() => setFlash(null), 280); // 点击后若不可解,提示玩家撤销 if (res.solvable === false && !warned) { setWarned(true); toast({ title: "谐振受阻", description: "此路不通,建议撤销上一步或重排。", }); } if (res.solvable === true) setWarned(false); } }; const handleUndo = () => { undoStep(); setWarned(false); setFlash(null); }; const handleRetry = () => { retryPuzzle(); setWarned(false); setFlash(null); }; // 计算节点位置(百分比) const cellW = 100 / cols; const cellH = 100 / rows; // 路径线段 const pathNodes = path .map((id) => grid.find((n) => n.id === id)!) .filter(Boolean); return (
{/* 目标序列 */}
目标谐振 {target.map((c, i) => { const done = i < currentTargetIdx; const active = i === currentTargetIdx; const v = COLOR_VISUAL[c]; return (
{done && } {active && ( 下一个 )}
); })}
{/* 节点阵列 */}
{/* 背景网格线 */}
{/* 路径线 SVG */} {pathNodes.map((n, i) => { if (i === 0) return null; const prev = pathNodes[i - 1]; const x1 = (prev.col + 0.5) * cellW; const y1 = (prev.row + 0.5) * cellH; const x2 = (n.col + 0.5) * cellW; const y2 = (n.row + 0.5) * cellH; const v = COLOR_VISUAL[n.color]; return ( ); })} {/* 节点 */} {grid.map((node) => { const v = COLOR_VISUAL[node.color]; const used = node.used; const isCurrentTarget = currentColor !== undefined && node.color === currentColor && !used; // 选起点阶段:标记「可行起点」(选了之后仍可解),避免玩家踩死起点 const isViableStart = path.length === 0 && isCurrentTarget && canStartFrom(activePuzzle, node.id); // 选起点阶段:是目标色但选了会死局的起点(淡化提示) const isDeadStart = path.length === 0 && isCurrentTarget && !isViableStart; const isHover = hoverId === node.id; const isFlash = flash?.id === node.id; const cx = (node.col + 0.5) * cellW; const cy = (node.row + 0.5) * cellH; return ( ); })}
{/* 卡死提示横幅 */} {stuck && (
谐振受阻,此路已无解。
)} {/* 底栏:步数 + 操作 */}
步数 = stepLimit ? "text-rose-400" : "text-foreground" }`} > {path.length} / {stepLimit} T{tier}
{/* 规则提示 */}

{path.length === 0 ? <>点击高亮的可行起点开始,按目标顺序走相邻同色节点;走错可撤销。 : <>按「目标谐振」顺序,点击上下左右相邻的同色节点。每个节点仅可使用一次;走错可撤销。}

); } /** 解码面板的容器(带标题) */ export function DecodePanel() { const activePuzzle = useGameStore((s) => s.activePuzzle); const pendingCrystals = useGameStore((s) => s.pendingCrystals); const startDecode = useGameStore((s) => s.startDecode); const crystalsPerSec = useGameStore((s) => s.crystalsPerSec); const crystalCap = useGameStore((s) => s.crystalCap); const crystals = useGameStore((s) => s.crystals); if (activePuzzle) { return (

解码共振阵列

); } // 无活跃谜题:展示待解码晶体队列 return (

待解码晶体

产能 {crystalsPerSec.toFixed(1)}/s · {Math.floor(crystals)}/{Math.floor(crystalCap)}
{pendingCrystals.length === 0 ? (

晶体正在生成中…

自治无人机每数秒回收一颗记忆晶体,点击下方晶体开启解码。

) : (
{pendingCrystals.map((c) => ( startDecode(c.id)} /> ))}
)}
); } function CrystalCard({ crystal, onClick, }: { crystal: { id: string; tier: 1 | 2 | 3; value: number }; onClick: () => void; }) { const tierColor = crystal.tier === 1 ? "#34d399" : crystal.tier === 2 ? "#fbbf24" : "#e879f9"; const tierLabel = crystal.tier === 1 ? "常见" : crystal.tier === 2 ? "稀有" : "史诗"; return ( ); }