From 9945a1974ea0f1d1b8e75f325b5dd75aef59978b Mon Sep 17 00:00:00 2001 From: Jakub Buciuto <46843555+MrJacob12@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:47:01 +0000 Subject: [PATCH 1/3] refactor: centralize game configuration and remove logic duplication - Create `src/config/gameConfig.ts` to house all game constants, formulas, and balancing values. - Create `src/types/game.ts` to centralize shared TypeScript interfaces and prevent circular dependencies. - Implement a unified `calculateCurrentIncome` helper function to standardize active and offline income logic. - Refactor `gameStore.ts` and UI components (PackOpening, Dimension, Upgrades, etc.) to consume centralized configuration. - Remove hardcoded values for pack requirements, upgrade costs, sell prices, and card generation weights. --- src/components/game/Header.tsx | 13 ++-- src/components/game/PackOpening.tsx | 14 ++-- src/config/gameConfig.ts | 90 ++++++++++++++++++++++++ src/pages/Collection.tsx | 5 +- src/pages/Dimension.tsx | 17 ++--- src/pages/Index.tsx | 40 +++-------- src/pages/Upgrades.tsx | 6 +- src/store/gameStore.ts | 105 ++++++++-------------------- src/types/game.ts | 39 +++++++++++ 9 files changed, 193 insertions(+), 136 deletions(-) create mode 100644 src/config/gameConfig.ts create mode 100644 src/types/game.ts diff --git a/src/components/game/Header.tsx b/src/components/game/Header.tsx index 9db563e..ba9ab6f 100644 --- a/src/components/game/Header.tsx +++ b/src/components/game/Header.tsx @@ -3,6 +3,7 @@ import { Leaf, TrendingUp, Settings, Beaker, Library } from 'lucide-react'; import { Link } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { formatNumber, formatCurrency } from '@/lib/utils'; +import { GAME_CONFIG, calculateCurrentIncome } from '@/config/gameConfig'; export function Header() { const seeds = useGameStore((s) => s.seeds); @@ -10,20 +11,14 @@ export function Header() { const inventory = useGameStore((s) => s.inventory); const upgrades = useGameStore((s) => s.upgrades); - const activeIncome = activeSlots.reduce( - (sum, slot) => sum + (slot?.income ?? 0), - 0 - ); - const inactiveCards = inventory.filter( (c) => !activeSlots.some((s) => s?.id === c.id) ).length; - const collectionBonus = inactiveCards; // +1% per inactive card - const labBonus = (upgrades.seeds || 0) * 5; // +5% per upgrade level + const collectionBonus = Math.round(inactiveCards * GAME_CONFIG.INCOME.INACTIVE_CARD_BONUS * 100); + const labBonus = Math.round((upgrades.seeds || 0) * GAME_CONFIG.UPGRADES.seeds.BONUS_PER_LEVEL * 100); - const totalMultiplier = (1 + collectionBonus / 100) * (1 + labBonus / 100); - const totalIncome = activeIncome * totalMultiplier; + const totalIncome = calculateCurrentIncome({ activeSlots, inventory, upgrades }); return (
diff --git a/src/components/game/PackOpening.tsx b/src/components/game/PackOpening.tsx index 9e65c42..03619f8 100644 --- a/src/components/game/PackOpening.tsx +++ b/src/components/game/PackOpening.tsx @@ -6,6 +6,7 @@ import { Package, Sparkles, X, ChevronRight, Lock } from 'lucide-react'; import { toast } from 'sonner'; import { formatCurrency } from '@/lib/utils'; import packs from '@/data/packs.json'; +import { GAME_CONFIG } from '@/config/gameConfig'; interface PackOpeningProps { packId: string; @@ -98,8 +99,9 @@ export function PackOpening({ packId }: PackOpeningProps) { const handleSellCard = (card: CardType) => { sellCard(card.id, card); setSoldCards((prev) => [...prev, card.id]); + const sellPrice = Math.floor(card.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER); toast.success(`${card.characterName} sold!`, { - description: `Gained ${formatCurrency(Math.floor(card.income * 100))} Mega Seeds.`, + description: `Gained ${formatCurrency(sellPrice)} Mega Seeds.`, }); }; @@ -116,11 +118,9 @@ export function PackOpening({ packId }: PackOpeningProps) { }; const getUnlockRequirement = (id: string) => { - if (id === 'mega') return 'Dimension Lvl 10'; - if (id === 'silver-rift') return 'Dimension Lvl 25'; - if (id === 'alchemists-portal') return 'Dimension Lvl 50'; - if (id === 'void-breach') return 'Dimension Lvl 100'; - return null; + const reqLevel = GAME_CONFIG.DIMENSIONS.PACK_UNLOCKS[id]; + if (reqLevel === undefined || reqLevel === 0) return null; + return `Dimension Lvl ${reqLevel}`; }; const colorClass = pack.id === 'mega' ? 'text-blue-400' : pack.id === 'central-finite-curve' ? 'text-purple-400' : 'text-primary'; @@ -238,7 +238,7 @@ export function PackOpening({ packId }: PackOpeningProps) { className="w-full font-bold text-[10px] h-8 bg-red-950/40 hover:bg-red-900 border border-red-500/50" onClick={() => handleSellCard(card)} > - SELL FOR {formatCurrency(Math.floor(card.income * 100))} + SELL FOR {formatCurrency(Math.floor(card.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER))} )} {soldCards.includes(card.id) && ( diff --git a/src/config/gameConfig.ts b/src/config/gameConfig.ts new file mode 100644 index 0000000..9e7c0c6 --- /dev/null +++ b/src/config/gameConfig.ts @@ -0,0 +1,90 @@ +import { GameCard, GameState } from "@/types/game"; + +export const GAME_CONFIG = { + INITIAL_SEEDS: 100, + INITIAL_MAX_INVENTORY: 50, + DIMENSION_ENTRY_COST: 1000, + SELL_PRICE_MULTIPLIER: 100, + MAX_OFFLINE_SECONDS: 24 * 60 * 60, // 24h + + UPGRADES: { + seeds: { + BASE_COST: 500, + COST_EXPONENT: 1.6, + JUMP_THRESHOLD: 10, + JUMP_MULTIPLIER: 5, + BONUS_PER_LEVEL: 0.05, // 5% per level + }, + power: { + BASE_COST: 800, + COST_EXPONENT: 1.6, + JUMP_THRESHOLD: 10, + JUMP_MULTIPLIER: 5, + BONUS_PER_LEVEL: 0.05, // 5% per level + } + }, + + INCOME: { + INACTIVE_CARD_BONUS: 0.01, // 1% per inactive card + }, + + CARD_GENERATION: { + DEFAULT_WEIGHTS: { + COMMON: 0.7, + RARE: 0.2, + HOLO: 0.08, + FULL_ART: 0.02, + }, + DEFAULT_COMBINE_CHANCE: 0.1, + ENEMY_WEIGHTS: { + COMMON: 0.6, + RARE: 0.3, + HOLO: 0.08, + FULL_ART: 0.02, + } + }, + + DIMENSIONS: { + BONUS_STEP: 5, + BONUS_AMOUNT: 200, // (dimensionLevel + 1) * 200 + SCALE_FACTOR: 0.25, + MAX_LEVEL: 100, + MAX_LEVEL_REWARD: 1000000, + LEVEL_REWARD_MULTIPLIER: 500, + MILESTONES: { + 10: "Mega Portal", + 25: "Silver Rift", + 50: "Alchemist Portal", + 100: "Void Breach", + } as Record, + PACK_UNLOCKS: { + "standard": 0, + "mega": 10, + "silver-rift": 25, + "alchemists-portal": 50, + "void-breach": 100, + } as Record + } +}; + +/** + * Calculates the current income per second based on the game state. + * @param state The current game state + * @returns Income per second + */ +export const calculateCurrentIncome = (state: Pick) => { + const activeIncome = state.activeSlots.reduce( + (sum: number, slot: GameCard | null) => sum + (slot?.income ?? 0), + 0, + ); + + const inactiveCards = state.inventory.filter( + (c: GameCard) => !state.activeSlots.some((s: GameCard | null) => s?.id === c.id), + ).length; + + const bonus = 1 + inactiveCards * GAME_CONFIG.INCOME.INACTIVE_CARD_BONUS; + + const upgradeBonus = 1 + state.upgrades.seeds * GAME_CONFIG.UPGRADES.seeds.BONUS_PER_LEVEL; + + return activeIncome * bonus * upgradeBonus; +}; diff --git a/src/pages/Collection.tsx b/src/pages/Collection.tsx index 0f1579f..9bea0a7 100644 --- a/src/pages/Collection.tsx +++ b/src/pages/Collection.tsx @@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button'; import { Search, Trash2, FilterX, CheckCircle2, Circle, ArrowLeft } from 'lucide-react'; import { toast } from 'sonner'; import { Link } from 'react-router-dom'; +import { GAME_CONFIG } from '@/config/gameConfig'; const Collection = () => { const inventory = useGameStore((s) => s.inventory); @@ -56,7 +57,7 @@ const Collection = () => { const totalProfit = inventory .filter(c => selectedIds.includes(c.id)) - .reduce((acc, c) => acc + (c.income * 100), 0); + .reduce((acc, c) => acc + (c.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER), 0); if (confirm(`Sell ${selectedIds.length} selected cards for ${totalProfit.toLocaleString()} Mega Seeds?`)) { sellCards(selectedIds); @@ -189,7 +190,7 @@ const Collection = () => {

{selectedIds.length} Selected

- Total: {inventory.filter(c => selectedIds.includes(c.id)).reduce((acc, c) => acc + (c.income * 100), 0).toLocaleString()} Seeds + Total: {inventory.filter(c => selectedIds.includes(c.id)).reduce((acc, c) => acc + (c.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER), 0).toLocaleString()} Seeds

diff --git a/src/pages/Dimension.tsx b/src/pages/Dimension.tsx index 49f203f..0b091b0 100644 --- a/src/pages/Dimension.tsx +++ b/src/pages/Dimension.tsx @@ -8,6 +8,7 @@ import { useGameStore, GameCard as GameCardType } from "@/store/gameStore"; import { GameCard } from "@/components/game/GameCard"; import { toast } from "sonner"; import { formatNumber, formatCurrency } from "@/lib/utils"; +import { GAME_CONFIG } from "@/config/gameConfig"; const Dimension = () => { const { @@ -31,20 +32,20 @@ const Dimension = () => { if (inventory.length === 0) return null; const strongest = [...inventory].sort((a, b) => b.power - a.power)[0]; - // Lab Power Upgrade: +5% per level - const powerMultiplier = 1 + upgrades.power * 0.05; + // Lab Power Upgrade + const powerMultiplier = 1 + upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL; const totalPower = Math.floor(strongest.power * powerMultiplier); return { card: strongest, basePower: strongest.power, totalPower, - bonusPercent: upgrades.power * 5, + bonusPercent: Math.round(upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL * 100), }; }, [inventory, upgrades.power]); const handleStart = () => { - if (seeds < 1000) { + if (seeds < GAME_CONFIG.DIMENSION_ENTRY_COST) { toast.error("Not enough Mega Seeds!"); return; } @@ -61,11 +62,11 @@ const Dimension = () => { setIsFighting(true); setTimeout(() => { - if (dimensionLevel == 100) { + if (dimensionLevel >= GAME_CONFIG.DIMENSIONS.MAX_LEVEL) { toast.success(`EPIC VICTORY! You conquered the Dimension Rift!`, { description: `You've reached the maximum level and unlocked all rewards!`, }); - resetDimension(1000000); + resetDimension(GAME_CONFIG.DIMENSIONS.MAX_LEVEL_REWARD); } else if (playerStats.totalPower >= currentEnemy.power) { const { bonus, milestoneUnlocked } = nextDimensionLevel(); @@ -82,7 +83,7 @@ const Dimension = () => { toast.success(`Victory! Level ${dimensionLevel} cleared.`); } } else { - const reward = dimensionLevel * 500; + const reward = dimensionLevel * GAME_CONFIG.DIMENSIONS.LEVEL_REWARD_MULTIPLIER; toast.error(`Defeat! You reached level ${dimensionLevel}.`, { description: `Final Reward: ${formatCurrency(reward)} Mega Seeds.`, }); @@ -119,7 +120,7 @@ const Dimension = () => {
- Lab Bonus: +{upgrades.power * 5}% Power + Lab Bonus: +{Math.round(upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL * 100)}% Power
diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 6411267..e6069e5 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -9,8 +9,7 @@ import { Button } from "@/components/ui/button"; import { Package, Map, Beaker } from "lucide-react"; import { toast } from "sonner"; import { formatCurrency } from "@/lib/utils"; - -const MAX_OFFLINE_SECONDS = 24 * 60 * 60; // 24h +import { GAME_CONFIG, calculateCurrentIncome } from "@/config/gameConfig"; const Index = () => { const offlineProcessed = useRef(false); @@ -23,23 +22,13 @@ const Index = () => { const state = useGameStore.getState(); const elapsed = Math.min( (Date.now() - state.lastSaved) / 1000, - MAX_OFFLINE_SECONDS, + GAME_CONFIG.MAX_OFFLINE_SECONDS, ); if (elapsed > 10) { - const activeIncome = state.activeSlots.reduce( - (sum, slot) => sum + (slot?.income ?? 0), - 0, - ); - const inactiveCards = state.inventory.filter( - (c) => !state.activeSlots.some((s) => s?.id === c.id), - ).length; - const bonus = 1 + inactiveCards / 100; - - // Apply Upgrade Multiplier (5% per level) - const upgradeBonus = 1 + state.upgrades.seeds * 0.05; - - const earned = Math.floor(activeIncome * bonus * upgradeBonus * elapsed); + const incomePerSecond = calculateCurrentIncome(state); + const earned = Math.floor(incomePerSecond * elapsed); + if (earned > 0) { state.updateSeeds(earned); toast.success(`Welcome back!`, { @@ -53,21 +42,10 @@ const Index = () => { useEffect(() => { const interval = setInterval(() => { const state = useGameStore.getState(); - const activeIncome = state.activeSlots.reduce( - (sum, slot) => sum + (slot?.income ?? 0), - 0, - ); - const inactiveCards = state.inventory.filter( - (c) => !state.activeSlots.some((s) => s?.id === c.id), - ).length; - const bonus = 1 + inactiveCards / 100; - - // Apply Upgrade Multiplier (5% per level) - const upgradeBonus = 1 + state.upgrades.seeds * 0.05; - - const earned = activeIncome * bonus * upgradeBonus; - if (earned > 0) { - state.updateSeeds(earned); + const incomePerSecond = calculateCurrentIncome(state); + + if (incomePerSecond > 0) { + state.updateSeeds(incomePerSecond); } }, 1000); return () => clearInterval(interval); diff --git a/src/pages/Upgrades.tsx b/src/pages/Upgrades.tsx index cc2cee4..68b33c0 100644 --- a/src/pages/Upgrades.tsx +++ b/src/pages/Upgrades.tsx @@ -13,6 +13,7 @@ import { import { useGameStore } from "@/store/gameStore"; import { toast } from "sonner"; import { formatNumber, formatCurrency } from "@/lib/utils"; +import { GAME_CONFIG } from "@/config/gameConfig"; const Upgrades = () => { const { seeds, upgrades, buyUpgrade, getUpgradeCost } = useGameStore(); @@ -35,7 +36,8 @@ const Upgrades = () => { const getEffect = (type: "seeds" | "power") => { const level = upgrades[type]; - return `+${(level * 5).toFixed(0)}%`; + const bonus = GAME_CONFIG.UPGRADES[type].BONUS_PER_LEVEL; + return `+${(level * bonus * 100).toFixed(0)}%`; }; const upgradesList = [ @@ -144,7 +146,7 @@ const Upgrades = () => {

- +{((level + 1) * 5).toFixed(0)}% + + {((level + 1) * GAME_CONFIG.UPGRADES[type].BONUS_PER_LEVEL * 100).toFixed(0)}%

diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts index 4567b4f..959c217 100644 --- a/src/store/gameStore.ts +++ b/src/store/gameStore.ts @@ -3,45 +3,10 @@ import { persist } from "zustand/middleware"; import characters from "@/data/characters.json"; import cardTypes from "@/data/cardTypes.json"; import packs from "@/data/packs.json"; +import { GAME_CONFIG } from "@/config/gameConfig"; +import { CardType, GameCard, GameState as BaseGameState } from "@/types/game"; -export interface CardType { - id: string; - label: string; - multiplier: number; - color: string; - canCombine: boolean; -} - -export interface GameCard { - id: string; - name: string; - characterName: string; - types: string[]; // e.g. ["HOLO", "FULL_ART"] - income: number; - power: number; - avatarId?: number; - customImage?: string; - origin: string; - location: string; - status: string; - species: string; - timestamp: number; -} - -interface GameState { - seeds: number; - inventory: GameCard[]; - maxInventory: number; - activeSlots: (GameCard | null)[]; - dimensionLevel: number; - maxDimensionLevel: number; - isDimensionActive: boolean; - currentEnemy: GameCard | null; - upgrades: { - seeds: number; - power: number; - }; - lastSaved: number; +interface GameState extends BaseGameState { addCard: (card: GameCard) => boolean; addCards: (cards: GameCard[]) => boolean; sellCard: (cardId: string, card?: GameCard) => void; @@ -64,13 +29,8 @@ interface GameState { } const generateCard = ( - weights: Record = { - COMMON: 0.7, - RARE: 0.2, - HOLO: 0.08, - FULL_ART: 0.02, - }, - combineChance: number = 0.1, + weights: Record = GAME_CONFIG.CARD_GENERATION.DEFAULT_WEIGHTS, + combineChance: number = GAME_CONFIG.CARD_GENERATION.DEFAULT_COMBINE_CHANCE, ): GameCard => { const character = characters[Math.floor(Math.random() * characters.length)]; @@ -132,9 +92,9 @@ const generateCard = ( export const useGameStore = create()( persist( (set, get) => ({ - seeds: 100, + seeds: GAME_CONFIG.INITIAL_SEEDS, inventory: [], - maxInventory: 50, + maxInventory: GAME_CONFIG.INITIAL_MAX_INVENTORY, activeSlots: [null, null, null, null], dimensionLevel: 1, maxDimensionLevel: 1, @@ -170,7 +130,7 @@ export const useGameStore = create()( if (!targetCard) return s; - const sellPrice = Math.floor(targetCard.income * 100); + const sellPrice = Math.floor(targetCard.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER); return { inventory: s.inventory.filter((c) => c.id !== cardId), activeSlots: s.activeSlots.map((sl) => @@ -184,7 +144,7 @@ export const useGameStore = create()( set((s) => { const cardsToSell = s.inventory.filter((c) => cardIds.includes(c.id)); const totalProfit = cardsToSell.reduce( - (acc, c) => acc + Math.floor(c.income * 100), + (acc, c) => acc + Math.floor(c.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER), 0, ); @@ -199,12 +159,9 @@ export const useGameStore = create()( isPackUnlocked: (packId) => { const { maxDimensionLevel } = get(); - if (packId === "standard") return true; - if (packId === "mega") return maxDimensionLevel >= 10; - if (packId === "silver-rift") return maxDimensionLevel >= 25; - if (packId === "alchemists-portal") return maxDimensionLevel >= 50; - if (packId === "void-breach") return maxDimensionLevel >= 100; - return false; + const reqLevel = GAME_CONFIG.DIMENSIONS.PACK_UNLOCKS[packId]; + if (reqLevel === undefined) return false; + return maxDimensionLevel >= reqLevel; }, buyPack: (packId) => { @@ -250,16 +207,15 @@ export const useGameStore = create()( startDimension: () => { const { seeds, generateRandomCard } = get(); - if (seeds < 1000) return false; + if (seeds < GAME_CONFIG.DIMENSION_ENTRY_COST) return false; const enemy = generateRandomCard( - { COMMON: 0.6, RARE: 0.3, HOLO: 0.08, FULL_ART: 0.02 }, - 0.1, + GAME_CONFIG.CARD_GENERATION.ENEMY_WEIGHTS, + GAME_CONFIG.CARD_GENERATION.DEFAULT_COMBINE_CHANCE, ); - // Strength scale for lvl 1 is already handled by base power set((s) => ({ - seeds: s.seeds - 1000, + seeds: s.seeds - GAME_CONFIG.DIMENSION_ENTRY_COST, isDimensionActive: true, dimensionLevel: 1, currentEnemy: enemy @@ -272,21 +228,18 @@ export const useGameStore = create()( let bonus = 0; let milestoneUnlocked = null; - if ((dimensionLevel + 1) % 5 === 0) { - bonus = (dimensionLevel + 1) * 200; + if ((dimensionLevel + 1) % GAME_CONFIG.DIMENSIONS.BONUS_STEP === 0) { + bonus = (dimensionLevel + 1) * GAME_CONFIG.DIMENSIONS.BONUS_AMOUNT; } const nextLvl = dimensionLevel + 1; - if (nextLvl === 10) milestoneUnlocked = "Mega Portal"; - if (nextLvl === 25) milestoneUnlocked = "Silver Rift"; - if (nextLvl === 50) milestoneUnlocked = "Alchemist Portal"; - if (nextLvl === 100) milestoneUnlocked = "Void Breach"; + milestoneUnlocked = GAME_CONFIG.DIMENSIONS.MILESTONES[nextLvl] || null; const newEnemy = generateRandomCard( - { COMMON: 0.6, RARE: 0.3, HOLO: 0.08, FULL_ART: 0.02 }, - 0.1, + GAME_CONFIG.CARD_GENERATION.ENEMY_WEIGHTS, + GAME_CONFIG.CARD_GENERATION.DEFAULT_COMBINE_CHANCE, ); - const scaleFactor = 1 + (nextLvl - 1) * 0.25; + const scaleFactor = 1 + (nextLvl - 1) * GAME_CONFIG.DIMENSIONS.SCALE_FACTOR; newEnemy.power = Math.floor(newEnemy.power * scaleFactor); set((s) => { @@ -315,14 +268,12 @@ export const useGameStore = create()( getUpgradeCost: (type) => { const { upgrades } = get(); const level = upgrades[type]; - const baseCost = type === "seeds" ? 500 : 800; + const config = GAME_CONFIG.UPGRADES[type]; - // Exponential growth: base * 1.5^level. - // Significant jump every 10 levels (multiplier * 5) - let cost = baseCost * Math.pow(1.6, level); - const jumps = Math.floor(level / 10); + let cost = config.BASE_COST * Math.pow(config.COST_EXPONENT, level); + const jumps = Math.floor(level / config.JUMP_THRESHOLD); if (jumps > 0) { - cost = cost * Math.pow(5, jumps); + cost = cost * Math.pow(config.JUMP_MULTIPLIER, jumps); } return Math.floor(cost); @@ -346,9 +297,9 @@ export const useGameStore = create()( hardReset: () => { set({ - seeds: 100, + seeds: GAME_CONFIG.INITIAL_SEEDS, inventory: [], - maxInventory: 50, + maxInventory: GAME_CONFIG.INITIAL_MAX_INVENTORY, activeSlots: [null, null, null, null], dimensionLevel: 1, maxDimensionLevel: 1, diff --git a/src/types/game.ts b/src/types/game.ts new file mode 100644 index 0000000..b033bb7 --- /dev/null +++ b/src/types/game.ts @@ -0,0 +1,39 @@ +export interface CardType { + id: string; + label: string; + multiplier: number; + color: string; + canCombine: boolean; +} + +export interface GameCard { + id: string; + name: string; + characterName: string; + types: string[]; // e.g. ["HOLO", "FULL_ART"] + income: number; + power: number; + avatarId?: number; + customImage?: string; + origin: string; + location: string; + status: string; + species: string; + timestamp: number; +} + +export interface GameState { + seeds: number; + inventory: GameCard[]; + maxInventory: number; + activeSlots: (GameCard | null)[]; + dimensionLevel: number; + maxDimensionLevel: number; + isDimensionActive: boolean; + currentEnemy: GameCard | null; + upgrades: { + seeds: number; + power: number; + }; + lastSaved: number; +} From ddf52c7462008dbb241052886dd6a151ed117dc7 Mon Sep 17 00:00:00 2001 From: Jacob Date: Tue, 10 Mar 2026 17:14:38 +0100 Subject: [PATCH 2/3] fix: improve type safety and resolve broken GameCard imports Fix [FIX] Improve type safety and remove any casts Fixes #5 --- src/components/game/Footer.tsx | 9 +- src/components/game/GameCard.tsx | 2 +- src/components/game/PackOpening.tsx | 210 ++++++++++++++++++---------- src/pages/Collection.tsx | 2 +- src/pages/Dimension.tsx | 3 +- src/store/gameStore.ts | 112 ++++++++------- src/types/game.ts | 17 ++- 7 files changed, 230 insertions(+), 125 deletions(-) diff --git a/src/components/game/Footer.tsx b/src/components/game/Footer.tsx index 8133377..223eda5 100644 --- a/src/components/game/Footer.tsx +++ b/src/components/game/Footer.tsx @@ -1,8 +1,8 @@ -import { Github, Info } from 'lucide-react'; +import { Github, Info } from "lucide-react"; export function Footer() { const currentYear = new Date().getFullYear(); - + return (