+
+ λ§μμ μ€μ¬. νκ΄ λ λ²¨μ΄ μμ© μΈμκ³Ό 건물·λ μνΌ ν΄κΈμ κ²°μ ν©λλ€.
+
+
+ νμ¬ μμ© μΈμ {state.cats.length}/{state.hall.capacity}λ§λ¦¬
+
+ {next ? (
+
+
+ Lv{state.hall.level + 1} μ¦μΆ β μμ© {next.capacity}λ§λ¦¬
+
+
+ {Object.entries(next.cost).map(([key, amount]) => {
+ const have = Math.floor(
+ state.resources[key as keyof typeof state.resources],
+ );
+ const ok = have >= (amount ?? 0);
+ return (
+ -
+ {ok ? "β" : "Β·"} {key === "wood" ? "πͺ΅ λͺ©μ¬" : "π μμ "} {have}/{amount}
+
+ );
+ })}
+ {next.reqCat && (
+ - c.level >= next.reqCat!.level).length >=
+ next.reqCat.count
+ ? "text-emerald-600 dark:text-emerald-400"
+ : ""
+ }
+ >
+ {state.cats.filter((c) => c.level >= next.reqCat!.level).length >=
+ next.reqCat.count
+ ? "β"
+ : "Β·"}{" "}
+ Lv{next.reqCat.level} μ΄μ κ³ μμ΄{" "}
+ {state.cats.filter((c) => c.level >= next.reqCat!.level).length}/
+ {next.reqCat.count}λ§λ¦¬
+
+ )}
+
+ {next.locked ? (
+
+ π {next.locked}
+
+ ) : (
+
+ )}
+
+ ) : (
+
νκ΄μ΄ μ΅κ³ λ 벨μ
λλ€.
+ )}
+
+ );
+}
+
+/* ββββββββββββββββ λΆμ κ³΅ν° ν¨λ ββββββββββββββββ */
+
+function KitchenLotPanel({ wood, onBuild }: { wood: number; onBuild: () => void }) {
+ const canBuild = wood >= KITCHEN_BUILD_COST.wood;
+ return (
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+ λΆμμ μ§μΌλ©΄ μ리 μ μ± κ³ μμ΄κ° μμ μΌλ‘ μ리λ₯Ό λ§λ€κ³ , λ°°κ³ ν
+ κ³ μμ΄λ€μ΄ μμμ λ¨ΉμΌλ¬ μ΅λλ€.
+
+
+
+ );
+}
+
+/* ββββββββββββββββ 건물 ν¨λ (μ 보 + λ°°μΉ) ββββββββββββββββ */
+
+function BuildingPanel({
+ building,
+ state,
+ onAssign,
+ onUnassign,
+ onCollect,
+}: {
+ building: Building;
+ state: GameState;
+ onAssign: (catId: string) => void;
+ onUnassign: (catId: string) => void;
+ onCollect: () => void;
+}) {
+ const isKitchen = building.type === "kitchen";
+ const recipe = activeRecipe(state);
+ const assignedCats = building.assigned
+ .map((id) => state.cats.find((c) => c.id === id))
+ .filter((c): c is Cat => Boolean(c));
+ const totalRate = assignedCats.reduce(
+ (sum, c) => sum + effectiveRate(c, building.aptitude),
+ 0,
+ );
+ const stored = Math.floor(building.stored);
+ const slotsLeft = building.maxSlots - assignedCats.length;
+ // λ°°μΉ ν보: μ΄ κ±΄λ¬Ό μ μ±μ΄ μλ λͺ¨λ κ³ μμ΄ (λ€λ₯Έ κ³³μμ μΌνλ κ³ μμ΄ ν¬ν¨)
+ const candidates = state.cats
+ .filter((c) => c.aptitude[building.aptitude] > 0 && c.assignedTo !== building.id)
+ .sort(
+ (a, b) =>
+ effectiveRate(b, building.aptitude) - effectiveRate(a, building.aptitude),
+ );
+
+ return (
+
+ {/* μ 보 */}
+
+
+ νμ μ μ±: {APTITUDE_LABEL[building.aptitude]} Β· μ¬λ‘―{" "}
+ {assignedCats.length}/{building.maxSlots}
+
+ {isKitchen ? (
+ <>
+
+ {recipe.emoji} {recipe.name} (
+ {Object.entries(recipe.input)
+ .map(([k, v]) => `${k === "fish" ? "μμ " : "λͺ©μ¬"} ${v}`)
+ .join(" + ")}
+ β ν¬λ§λ +{recipe.fullness})
+
+
μ리 μλ {totalRate.toFixed(2)}/μ΄
+
+ >
+ ) : (
+
μμ° μλ {totalRate.toFixed(2)}/μ΄
+ )}
+
+
+ {/* μ°μΆ/μ λ° */}
+ {isKitchen ? (
+
+ {recipe.emoji} μ λ° {stored}/{KITCHEN_MEAL_CAP} β λ°°κ³ ν κ³ μμ΄κ° μλμΌλ‘ λ¨Ήμ΅λλ€
+
+ ) : (
+
)}
+ {/* λ°°μΉλ κ³ μμ΄ */}
- ποΈ μμ° κ±΄λ¬Ό
-
- κ³ μμ΄λ₯Ό μ νν λ€ λΉμ리λ₯Ό ν΄λ¦ν΄ λ°°μΉνμΈμ. μ°μΆλ¬Όμ μ§μ νμν΄μΌ ν©λλ€.
-
-
- {state.buildings.map((b) => (
-
run(() => engine.assign(catId, b.id))}
- onUnassign={(catId) => run(() => engine.unassign(catId))}
- onCollect={() => run(() => engine.collect(b.id))}
+
+ μΌνλ μ€ ({assignedCats.length}/{building.maxSlots})
+
+ {assignedCats.length === 0 && (
+ μμ§ μ무λ μμ΄μ
+ )}
+
+ {assignedCats.map((cat) => (
+ onUnassign(cat.id) }}
/>
))}
- {!state.buildings.some((b) => b.type === "kitchen") && (
- run(() => engine.buildKitchen())}
- />
- )}
+ {/* λ°°μΉ κ°λ₯ν κ³ μμ΄ */}
-
- πΎ μ°λ¦¬ κ³ μμ΄λ€ ({state.cats.length}/{state.hall.capacity})
-
-
- {state.cats.map((cat) => (
-
- setSelectedCatId(cat.id === selectedCatId ? null : cat.id)
- }
- />
- ))}
+
+ λ°°μΉ κ°λ₯ν κ³ μμ΄
+
+ {candidates.length === 0 && (
+
+ {APTITUDE_LABEL[building.aptitude]} μ μ±μ΄ μλ κ³ μμ΄κ° μμ΄μ
+
+ )}
+
+ {candidates.map((cat) => {
+ const workplace = state.buildings.find((b) => b.id === cat.assignedTo);
+ return (
+ onAssign(cat.id),
+ }}
+ />
+ );
+ })}
+ {slotsLeft <= 0 && candidates.length > 0 && (
+
+ μ¬λ‘―μ΄ κ°λ μ°Όμ΄μ β λ¨Όμ ν΄μ νκ±°λ 건물μ μ¦μΆνμΈμ
+
+ )}
);
}
-/** ν¨μλΈ μ€ν¬ λ°°μ§ β λ§μ°μ€μ€λ² μ μμΈ ν΄ν */
+/** ν¨λμ© κ³ μμ΄ ν */
+function CatRow({
+ cat,
+ aptitude,
+ tag,
+ action,
+}: {
+ cat: Cat;
+ aptitude: AptitudeKey;
+ tag: string | null;
+ action: {
+ label: string;
+ style: "primary" | "danger";
+ disabled?: boolean;
+ onClick: () => void;
+ };
+}) {
+ return (
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+
+ {cat.name}{" "}
+ Lv{cat.level}
+
+
+ {APTITUDE_LABEL[aptitude]} {cat.aptitude[aptitude]} Β·{" "}
+ {effectiveRate(cat, aptitude).toFixed(2)}/μ΄
+ {fullnessPenalty(cat) < 1 && (
+ λ°°κ³ ν
+ )}
+
+ {tag &&
{tag}
}
+
+
+
+ );
+}
+
+/* ββββββββββββββββ ν¨μλΈ λ°°μ§ (ν΄ν) ββββββββββββββββ */
+
function PassiveBadge({ passiveKey }: { passiveKey: string }) {
const def = PASSIVES.find((p) => p.key === passiveKey);
if (!def) return null;
@@ -137,7 +535,82 @@ function PassiveBadge({ passiveKey }: { passiveKey: string }) {
);
}
-/** μ
μ λͺ¨λ¬ β ν보 3λ§λ¦¬ μ€ 1λ§λ¦¬ μ ν */
+/* ββββββββββββββββ κ³ μμ΄ μΉ΄λ (μ 보μ©) ββββββββββββββββ */
+
+function CatCard({ cat, buildings }: { cat: Cat; buildings: Building[] }) {
+ const workplace = buildings.find((b) => b.id === cat.assignedTo);
+ const expNeeded = cat.level * 60;
+
+ return (
+
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+ {cat.name}{" "}
+ Lv{cat.level}
+
+
{cat.breedName}
+
+
+
+
+
+ π£ λμ {cat.aptitude.fishing} Β· πͺ λ²λͺ© {cat.aptitude.logging} Β· π³ μ리{" "}
+ {cat.aptitude.cooking} Β· πͺ κ·Όλ©΄ {cat.stats.diligence}
+
+
+
+
+
+ π½οΈ {Math.floor(cat.status.fullness)} (μμ {cat.stats.appetite})
+
+ {fullnessPenalty(cat) < 1 && (
+
+ λ°°κ³ ν
+
+ )}
+
+
+ {cat.passives.length > 0 && (
+
+ {cat.passives.map((key) => (
+
+ ))}
+
+ )}
+
+
+ {workplace ? (
+
+ {workplace.emoji} {workplace.name}μμ μΌνλ μ€
+
+ ) : (
+ μ¬λ μ€
+ )}
+
+
+ );
+}
+
+/* ββββββββββββββββ μ
μ λͺ¨λ¬ ββββββββββββββββ */
+
function AdoptModal({
candidates,
onPick,
@@ -231,273 +704,9 @@ function AdoptModal({
})}
- ν λ§λ¦¬λ§ μ νν μ μμ΄μ Β· λ₯λ ₯μΉμ ν¨μλΈμ λ§μ°μ€λ₯Ό μ¬λ¦¬λ©΄ μμΈ μ λ³΄κ° λ³΄μ
λλ€
+ ν λ§λ¦¬λ§ μ νν μ μμ΄μ Β· ν¨μλΈμ λ§μ°μ€λ₯Ό μ¬λ¦¬λ©΄ μμΈ μ λ³΄κ° λ³΄μ
λλ€