diff --git a/src/components/game/Game.tsx b/src/components/game/Game.tsx index f9fb787..f74bdf9 100644 --- a/src/components/game/Game.tsx +++ b/src/components/game/Game.tsx @@ -6,18 +6,21 @@ import VillageView from "@/components/game/VillageView"; import { engine, effectiveRate, activeRecipe, fullnessPenalty } from "@/lib/game/engine"; import { APTITUDE_LABEL, + BREEDS, HALL_LEVELS, KITCHEN_BUILD_COST, KITCHEN_MEAL_CAP, PASSIVES, TOOL_IMG, } from "@/lib/game/data"; +import type { AptitudeKey } from "@/lib/game/types"; import type { Building, Cat, GameState } from "@/lib/game/types"; export default function Game() { const { state, dispatch } = useGame(); const [selectedCatId, setSelectedCatId] = useState(null); const [message, setMessage] = useState(null); + const [candidates, setCandidates] = useState(null); if (!state) { return ( @@ -36,10 +39,21 @@ export default function Game() {
run(() => engine.adopt())} + onAdopt={() => setCandidates(engine.generateCandidates())} onUpgrade={() => run(() => engine.upgradeHall())} /> + {candidates && ( + { + run(() => engine.adoptCandidate(cat)); + setCandidates(null); + }} + onClose={() => setCandidates(null)} + /> + )} + {message && ( @@ -96,6 +110,128 @@ export default function Game() { ); } +/** 패시브 스킬 배지 — 마우스오버 시 상세 툴팁 */ +function PassiveBadge({ passiveKey }: { passiveKey: string }) { + const def = PASSIVES.find((p) => p.key === passiveKey); + if (!def) return null; + const color = + def.kind === "positive" + ? "bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300" + : def.kind === "negative" + ? "bg-red-50 text-red-600 dark:bg-red-950 dark:text-red-300" + : "bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300"; + const kindLabel = + def.kind === "positive" ? "긍정" : def.kind === "negative" ? "부정" : "복합"; + return ( + + {def.name} + + + {def.name} ({kindLabel}) + + {def.desc} + + + + ); +} + +/** 입양 모달 — 후보 3마리 중 1마리 선택 */ +function AdoptModal({ + candidates, + onPick, + onClose, +}: { + candidates: Cat[]; + onPick: (cat: Cat) => void; + onClose: () => void; +}) { + return ( +
+
e.stopPropagation()} + > +
+

😻 입양할 고양이를 선택하세요

+ +
+
+ {candidates.map((cat) => { + const breed = BREEDS.find((b) => b.key === cat.breed); + const aptitudes = (Object.keys(APTITUDE_LABEL) as AptitudeKey[]).filter( + (k) => cat.aptitude[k] > 0, + ); + return ( +
+
+
{cat.emoji}
+
{cat.name}
+
+ {cat.breedName}{" "} + + {"★".repeat(breed?.rarity ?? 1)} + +
+
+ +
+ 💪 근면 {cat.stats.diligence} · ❤️ 체력 {cat.stats.stamina} · 🍽️ + 식욕 {cat.stats.appetite} +
+ +
+ {aptitudes.length > 0 ? ( + aptitudes.map((k) => ( + + {APTITUDE_LABEL[k]} {cat.aptitude[k]} + + )) + ) : ( + 적성 없음… + )} +
+ +
+ {cat.passives.length > 0 ? ( + cat.passives.map((key) => ) + ) : ( + 패시브 없음 + )} +
+ + +
+ ); + })} +
+

+ 한 마리만 선택할 수 있어요 · 능력치와 패시브에 마우스를 올리면 상세 정보가 보입니다 +

+
+
+ ); +} + function TopBar({ state, onAdopt, @@ -335,25 +471,9 @@ function CatCard({ {cat.passives.length > 0 && (
- {cat.passives.map((key) => { - const def = PASSIVES.find((p) => p.key === key); - if (!def) return null; - const color = - def.kind === "positive" - ? "bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300" - : def.kind === "negative" - ? "bg-red-50 text-red-600 dark:bg-red-950 dark:text-red-300" - : "bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300"; - return ( - - {def.name} - - ); - })} + {cat.passives.map((key) => ( + + ))}
)} diff --git a/src/lib/game/engine.ts b/src/lib/game/engine.ts index ff9da3e..9c45dea 100644 --- a/src/lib/game/engine.ts +++ b/src/lib/game/engine.ts @@ -243,10 +243,17 @@ export const engine = { state.updatedAt = Date.now(); }, - adopt(): string | null { + /** 입양 후보 생성 (아직 마을에 합류하지 않음) */ + generateCandidates(count = 3): Cat[] { + return Array.from({ length: count }, () => generateCat()); + }, + + /** 후보 중 한 마리 입양 확정 */ + adoptCandidate(cat: Cat): string | null { if (!state) return null; - if (state.cats.length >= state.hall.capacity) return "수용 인원이 가득 찼습니다 (회관 증축 필요)"; - state.cats.push(generateCat()); + if (state.cats.length >= state.hall.capacity) + return "수용 인원이 가득 찼습니다 (회관 증축 필요)"; + state.cats.push(cat); return null; },