Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/components/game/Footer.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<footer className="w-full py-6 px-6 border-t border-border bg-card/20 mt-auto">
<div className="max-w-7xl mx-auto flex flex-col items-center gap-4">
Expand All @@ -15,8 +15,9 @@ export function Footer() {

<div className="max-w-4xl text-center space-y-2">
<p className="text-[10px] text-muted-foreground/80 leading-tight font-body">
Purely hobbyist, non-commercial open-source project. All character names, images, and assets from "Rick and
Morty" are property of <strong>Adult Swim, Warner Bros. Discovery</strong>.
Purely hobbyist, non-commercial open-source project. All character
names, images, and assets from "Rick and Morty" are property of{" "}
<strong>Adult Swim, Warner Bros. Discovery</strong>.
</p>

<div className="flex flex-col md:flex-row items-center justify-center gap-x-4 gap-y-1 opacity-60">
Expand Down
2 changes: 1 addition & 1 deletion src/components/game/GameCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type GameCard as GameCardType } from "@/store/gameStore";
import { type GameCard as GameCardType } from "@/types/game";
import {
Sparkles,
Zap,
Expand Down
13 changes: 4 additions & 9 deletions src/components/game/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,22 @@ 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);
const activeSlots = useGameStore((s) => s.activeSlots);
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 (
<header className="flex items-center justify-between px-6 py-4 border-b border-border bg-card/60 backdrop-blur-md">
Expand Down
344 changes: 208 additions & 136 deletions src/components/game/PackOpening.tsx

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions src/config/gameConfig.ts
Original file line number Diff line number Diff line change
@@ -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<number, string>,
PACK_UNLOCKS: {
"standard": 0,
"mega": 10,
"silver-rift": 25,
"alchemists-portal": 50,
"void-breach": 100,
} as Record<string, number>
}
};

/**
* 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<GameState, "activeSlots" | "inventory" | "upgrades">) => {
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;
};
7 changes: 4 additions & 3 deletions src/pages/Collection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useMemo } from 'react';
import { useGameStore, GameCard as CardType } from '@/store/gameStore';
import { useGameStore } from '@/store/gameStore';
import { Header } from '@/components/game/Header';
import { GameCard } from '@/components/game/GameCard';
import { Footer } from '@/components/game/Footer';
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -189,7 +190,7 @@ const Collection = () => {
<div className="text-destructive-foreground">
<p className="font-display font-bold text-lg">{selectedIds.length} Selected</p>
<p className="text-xs opacity-80">
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
</p>
</div>
<div className="flex gap-2">
Expand Down
20 changes: 11 additions & 9 deletions src/pages/Dimension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { Footer } from "@/components/game/Footer";
import { Button } from "@/components/ui/button";
import { Link } from "react-router-dom";
import { Map, ArrowLeft, Sword, History, Zap, Beaker } from "lucide-react";
import { useGameStore, GameCard as GameCardType } from "@/store/gameStore";
import { useGameStore } from "@/store/gameStore";
import { GameCard as GameCardType } from "@/types/game";
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 {
Expand All @@ -31,20 +33,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;
}
Expand All @@ -61,11 +63,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();

Expand All @@ -82,7 +84,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.`,
});
Expand Down Expand Up @@ -119,7 +121,7 @@ const Dimension = () => {
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 text-xs font-bold px-3 py-1 bg-primary/10 text-primary border border-primary/20 rounded-full">
<Beaker className="w-3.5 h-3.5" />
Lab Bonus: +{upgrades.power * 5}% Power
Lab Bonus: +{Math.round(upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL * 100)}% Power
</div>
<div className="flex items-center gap-2">
<History className="w-4 h-4 text-muted-foreground" />
Expand Down
40 changes: 9 additions & 31 deletions src/pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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!`, {
Expand All @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions src/pages/Upgrades.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 = [
Expand Down Expand Up @@ -144,7 +146,7 @@ const Upgrades = () => {
</p>
<div className="flex items-center gap-1">
<p className="text-lg font-bold text-foreground">
+{((level + 1) * 5).toFixed(0)}%
+ {((level + 1) * GAME_CONFIG.UPGRADES[type].BONUS_PER_LEVEL * 100).toFixed(0)}%
</p>
<ChevronUp className="w-4 h-4 text-green-500" />
</div>
Expand Down
Loading
Loading