Skip to content

Commit ffe3e13

Browse files
authored
Merge pull request #7 from DeepVoidGames/4-refactor-centralize-game-logic-and-constants
4 refactor centralize game logic and constants
2 parents 975df95 + b8daa13 commit ffe3e13

11 files changed

Lines changed: 480 additions & 316 deletions

File tree

src/components/game/Footer.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Github, Info } from 'lucide-react';
1+
import { Github, Info } from "lucide-react";
22

33
export function Footer() {
44
const currentYear = new Date().getFullYear();
5-
5+
66
return (
77
<footer className="w-full py-6 px-6 border-t border-border bg-card/20 mt-auto">
88
<div className="max-w-7xl mx-auto flex flex-col items-center gap-4">
@@ -15,8 +15,9 @@ export function Footer() {
1515

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

2223
<div className="flex flex-col md:flex-row items-center justify-center gap-x-4 gap-y-1 opacity-60">

src/components/game/GameCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type GameCard as GameCardType } from "@/store/gameStore";
1+
import { type GameCard as GameCardType } from "@/types/game";
22
import {
33
Sparkles,
44
Zap,

src/components/game/Header.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,22 @@ import { Leaf, TrendingUp, Settings, Beaker, Library } from 'lucide-react';
33
import { Link } from 'react-router-dom';
44
import { Button } from '@/components/ui/button';
55
import { formatNumber, formatCurrency } from '@/lib/utils';
6+
import { GAME_CONFIG, calculateCurrentIncome } from '@/config/gameConfig';
67

78
export function Header() {
89
const seeds = useGameStore((s) => s.seeds);
910
const activeSlots = useGameStore((s) => s.activeSlots);
1011
const inventory = useGameStore((s) => s.inventory);
1112
const upgrades = useGameStore((s) => s.upgrades);
1213

13-
const activeIncome = activeSlots.reduce(
14-
(sum, slot) => sum + (slot?.income ?? 0),
15-
0
16-
);
17-
1814
const inactiveCards = inventory.filter(
1915
(c) => !activeSlots.some((s) => s?.id === c.id)
2016
).length;
2117

22-
const collectionBonus = inactiveCards; // +1% per inactive card
23-
const labBonus = (upgrades.seeds || 0) * 5; // +5% per upgrade level
18+
const collectionBonus = Math.round(inactiveCards * GAME_CONFIG.INCOME.INACTIVE_CARD_BONUS * 100);
19+
const labBonus = Math.round((upgrades.seeds || 0) * GAME_CONFIG.UPGRADES.seeds.BONUS_PER_LEVEL * 100);
2420

25-
const totalMultiplier = (1 + collectionBonus / 100) * (1 + labBonus / 100);
26-
const totalIncome = activeIncome * totalMultiplier;
21+
const totalIncome = calculateCurrentIncome({ activeSlots, inventory, upgrades });
2722

2823
return (
2924
<header className="flex items-center justify-between px-6 py-4 border-b border-border bg-card/60 backdrop-blur-md">

src/components/game/PackOpening.tsx

Lines changed: 208 additions & 136 deletions
Large diffs are not rendered by default.

src/config/gameConfig.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { GameCard, GameState } from "@/types/game";
2+
3+
export const GAME_CONFIG = {
4+
INITIAL_SEEDS: 100,
5+
INITIAL_MAX_INVENTORY: 50,
6+
DIMENSION_ENTRY_COST: 1000,
7+
SELL_PRICE_MULTIPLIER: 100,
8+
MAX_OFFLINE_SECONDS: 24 * 60 * 60, // 24h
9+
10+
UPGRADES: {
11+
seeds: {
12+
BASE_COST: 500,
13+
COST_EXPONENT: 1.6,
14+
JUMP_THRESHOLD: 10,
15+
JUMP_MULTIPLIER: 5,
16+
BONUS_PER_LEVEL: 0.05, // 5% per level
17+
},
18+
power: {
19+
BASE_COST: 800,
20+
COST_EXPONENT: 1.6,
21+
JUMP_THRESHOLD: 10,
22+
JUMP_MULTIPLIER: 5,
23+
BONUS_PER_LEVEL: 0.05, // 5% per level
24+
}
25+
},
26+
27+
INCOME: {
28+
INACTIVE_CARD_BONUS: 0.01, // 1% per inactive card
29+
},
30+
31+
CARD_GENERATION: {
32+
DEFAULT_WEIGHTS: {
33+
COMMON: 0.7,
34+
RARE: 0.2,
35+
HOLO: 0.08,
36+
FULL_ART: 0.02,
37+
},
38+
DEFAULT_COMBINE_CHANCE: 0.1,
39+
ENEMY_WEIGHTS: {
40+
COMMON: 0.6,
41+
RARE: 0.3,
42+
HOLO: 0.08,
43+
FULL_ART: 0.02,
44+
}
45+
},
46+
47+
DIMENSIONS: {
48+
BONUS_STEP: 5,
49+
BONUS_AMOUNT: 200, // (dimensionLevel + 1) * 200
50+
SCALE_FACTOR: 0.25,
51+
MAX_LEVEL: 100,
52+
MAX_LEVEL_REWARD: 1000000,
53+
LEVEL_REWARD_MULTIPLIER: 500,
54+
MILESTONES: {
55+
10: "Mega Portal",
56+
25: "Silver Rift",
57+
50: "Alchemist Portal",
58+
100: "Void Breach",
59+
} as Record<number, string>,
60+
PACK_UNLOCKS: {
61+
"standard": 0,
62+
"mega": 10,
63+
"silver-rift": 25,
64+
"alchemists-portal": 50,
65+
"void-breach": 100,
66+
} as Record<string, number>
67+
}
68+
};
69+
70+
/**
71+
* Calculates the current income per second based on the game state.
72+
* @param state The current game state
73+
* @returns Income per second
74+
*/
75+
export const calculateCurrentIncome = (state: Pick<GameState, "activeSlots" | "inventory" | "upgrades">) => {
76+
const activeIncome = state.activeSlots.reduce(
77+
(sum: number, slot: GameCard | null) => sum + (slot?.income ?? 0),
78+
0,
79+
);
80+
81+
const inactiveCards = state.inventory.filter(
82+
(c: GameCard) => !state.activeSlots.some((s: GameCard | null) => s?.id === c.id),
83+
).length;
84+
85+
const bonus = 1 + inactiveCards * GAME_CONFIG.INCOME.INACTIVE_CARD_BONUS;
86+
87+
const upgradeBonus = 1 + state.upgrades.seeds * GAME_CONFIG.UPGRADES.seeds.BONUS_PER_LEVEL;
88+
89+
return activeIncome * bonus * upgradeBonus;
90+
};

src/pages/Collection.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useMemo } from 'react';
2-
import { useGameStore, GameCard as CardType } from '@/store/gameStore';
2+
import { useGameStore } from '@/store/gameStore';
33
import { Header } from '@/components/game/Header';
44
import { GameCard } from '@/components/game/GameCard';
55
import { Footer } from '@/components/game/Footer';
@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button';
1515
import { Search, Trash2, FilterX, CheckCircle2, Circle, ArrowLeft } from 'lucide-react';
1616
import { toast } from 'sonner';
1717
import { Link } from 'react-router-dom';
18+
import { GAME_CONFIG } from '@/config/gameConfig';
1819

1920
const Collection = () => {
2021
const inventory = useGameStore((s) => s.inventory);
@@ -56,7 +57,7 @@ const Collection = () => {
5657

5758
const totalProfit = inventory
5859
.filter(c => selectedIds.includes(c.id))
59-
.reduce((acc, c) => acc + (c.income * 100), 0);
60+
.reduce((acc, c) => acc + (c.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER), 0);
6061

6162
if (confirm(`Sell ${selectedIds.length} selected cards for ${totalProfit.toLocaleString()} Mega Seeds?`)) {
6263
sellCards(selectedIds);
@@ -189,7 +190,7 @@ const Collection = () => {
189190
<div className="text-destructive-foreground">
190191
<p className="font-display font-bold text-lg">{selectedIds.length} Selected</p>
191192
<p className="text-xs opacity-80">
192-
Total: {inventory.filter(c => selectedIds.includes(c.id)).reduce((acc, c) => acc + (c.income * 100), 0).toLocaleString()} Seeds
193+
Total: {inventory.filter(c => selectedIds.includes(c.id)).reduce((acc, c) => acc + (c.income * GAME_CONFIG.SELL_PRICE_MULTIPLIER), 0).toLocaleString()} Seeds
193194
</p>
194195
</div>
195196
<div className="flex gap-2">

src/pages/Dimension.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { Footer } from "@/components/game/Footer";
44
import { Button } from "@/components/ui/button";
55
import { Link } from "react-router-dom";
66
import { Map, ArrowLeft, Sword, History, Zap, Beaker } from "lucide-react";
7-
import { useGameStore, GameCard as GameCardType } from "@/store/gameStore";
7+
import { useGameStore } from "@/store/gameStore";
8+
import { GameCard as GameCardType } from "@/types/game";
89
import { GameCard } from "@/components/game/GameCard";
910
import { toast } from "sonner";
1011
import { formatNumber, formatCurrency } from "@/lib/utils";
12+
import { GAME_CONFIG } from "@/config/gameConfig";
1113

1214
const Dimension = () => {
1315
const {
@@ -31,20 +33,20 @@ const Dimension = () => {
3133
if (inventory.length === 0) return null;
3234
const strongest = [...inventory].sort((a, b) => b.power - a.power)[0];
3335

34-
// Lab Power Upgrade: +5% per level
35-
const powerMultiplier = 1 + upgrades.power * 0.05;
36+
// Lab Power Upgrade
37+
const powerMultiplier = 1 + upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL;
3638
const totalPower = Math.floor(strongest.power * powerMultiplier);
3739

3840
return {
3941
card: strongest,
4042
basePower: strongest.power,
4143
totalPower,
42-
bonusPercent: upgrades.power * 5,
44+
bonusPercent: Math.round(upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL * 100),
4345
};
4446
}, [inventory, upgrades.power]);
4547

4648
const handleStart = () => {
47-
if (seeds < 1000) {
49+
if (seeds < GAME_CONFIG.DIMENSION_ENTRY_COST) {
4850
toast.error("Not enough Mega Seeds!");
4951
return;
5052
}
@@ -61,11 +63,11 @@ const Dimension = () => {
6163
setIsFighting(true);
6264

6365
setTimeout(() => {
64-
if (dimensionLevel == 100) {
66+
if (dimensionLevel >= GAME_CONFIG.DIMENSIONS.MAX_LEVEL) {
6567
toast.success(`EPIC VICTORY! You conquered the Dimension Rift!`, {
6668
description: `You've reached the maximum level and unlocked all rewards!`,
6769
});
68-
resetDimension(1000000);
70+
resetDimension(GAME_CONFIG.DIMENSIONS.MAX_LEVEL_REWARD);
6971
} else if (playerStats.totalPower >= currentEnemy.power) {
7072
const { bonus, milestoneUnlocked } = nextDimensionLevel();
7173

@@ -82,7 +84,7 @@ const Dimension = () => {
8284
toast.success(`Victory! Level ${dimensionLevel} cleared.`);
8385
}
8486
} else {
85-
const reward = dimensionLevel * 500;
87+
const reward = dimensionLevel * GAME_CONFIG.DIMENSIONS.LEVEL_REWARD_MULTIPLIER;
8688
toast.error(`Defeat! You reached level ${dimensionLevel}.`, {
8789
description: `Final Reward: ${formatCurrency(reward)} Mega Seeds.`,
8890
});
@@ -119,7 +121,7 @@ const Dimension = () => {
119121
<div className="flex items-center gap-6">
120122
<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">
121123
<Beaker className="w-3.5 h-3.5" />
122-
Lab Bonus: +{upgrades.power * 5}% Power
124+
Lab Bonus: +{Math.round(upgrades.power * GAME_CONFIG.UPGRADES.power.BONUS_PER_LEVEL * 100)}% Power
123125
</div>
124126
<div className="flex items-center gap-2">
125127
<History className="w-4 h-4 text-muted-foreground" />

src/pages/Index.tsx

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { Button } from "@/components/ui/button";
99
import { Package, Map, Beaker } from "lucide-react";
1010
import { toast } from "sonner";
1111
import { formatCurrency } from "@/lib/utils";
12-
13-
const MAX_OFFLINE_SECONDS = 24 * 60 * 60; // 24h
12+
import { GAME_CONFIG, calculateCurrentIncome } from "@/config/gameConfig";
1413

1514
const Index = () => {
1615
const offlineProcessed = useRef(false);
@@ -23,23 +22,13 @@ const Index = () => {
2322
const state = useGameStore.getState();
2423
const elapsed = Math.min(
2524
(Date.now() - state.lastSaved) / 1000,
26-
MAX_OFFLINE_SECONDS,
25+
GAME_CONFIG.MAX_OFFLINE_SECONDS,
2726
);
2827

2928
if (elapsed > 10) {
30-
const activeIncome = state.activeSlots.reduce(
31-
(sum, slot) => sum + (slot?.income ?? 0),
32-
0,
33-
);
34-
const inactiveCards = state.inventory.filter(
35-
(c) => !state.activeSlots.some((s) => s?.id === c.id),
36-
).length;
37-
const bonus = 1 + inactiveCards / 100;
38-
39-
// Apply Upgrade Multiplier (5% per level)
40-
const upgradeBonus = 1 + state.upgrades.seeds * 0.05;
41-
42-
const earned = Math.floor(activeIncome * bonus * upgradeBonus * elapsed);
29+
const incomePerSecond = calculateCurrentIncome(state);
30+
const earned = Math.floor(incomePerSecond * elapsed);
31+
4332
if (earned > 0) {
4433
state.updateSeeds(earned);
4534
toast.success(`Welcome back!`, {
@@ -53,21 +42,10 @@ const Index = () => {
5342
useEffect(() => {
5443
const interval = setInterval(() => {
5544
const state = useGameStore.getState();
56-
const activeIncome = state.activeSlots.reduce(
57-
(sum, slot) => sum + (slot?.income ?? 0),
58-
0,
59-
);
60-
const inactiveCards = state.inventory.filter(
61-
(c) => !state.activeSlots.some((s) => s?.id === c.id),
62-
).length;
63-
const bonus = 1 + inactiveCards / 100;
64-
65-
// Apply Upgrade Multiplier (5% per level)
66-
const upgradeBonus = 1 + state.upgrades.seeds * 0.05;
67-
68-
const earned = activeIncome * bonus * upgradeBonus;
69-
if (earned > 0) {
70-
state.updateSeeds(earned);
45+
const incomePerSecond = calculateCurrentIncome(state);
46+
47+
if (incomePerSecond > 0) {
48+
state.updateSeeds(incomePerSecond);
7149
}
7250
}, 1000);
7351
return () => clearInterval(interval);

src/pages/Upgrades.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { useGameStore } from "@/store/gameStore";
1414
import { toast } from "sonner";
1515
import { formatNumber, formatCurrency } from "@/lib/utils";
16+
import { GAME_CONFIG } from "@/config/gameConfig";
1617

1718
const Upgrades = () => {
1819
const { seeds, upgrades, buyUpgrade, getUpgradeCost } = useGameStore();
@@ -35,7 +36,8 @@ const Upgrades = () => {
3536

3637
const getEffect = (type: "seeds" | "power") => {
3738
const level = upgrades[type];
38-
return `+${(level * 5).toFixed(0)}%`;
39+
const bonus = GAME_CONFIG.UPGRADES[type].BONUS_PER_LEVEL;
40+
return `+${(level * bonus * 100).toFixed(0)}%`;
3941
};
4042

4143
const upgradesList = [
@@ -144,7 +146,7 @@ const Upgrades = () => {
144146
</p>
145147
<div className="flex items-center gap-1">
146148
<p className="text-lg font-bold text-foreground">
147-
+{((level + 1) * 5).toFixed(0)}%
149+
+ {((level + 1) * GAME_CONFIG.UPGRADES[type].BONUS_PER_LEVEL * 100).toFixed(0)}%
148150
</p>
149151
<ChevronUp className="w-4 h-4 text-green-500" />
150152
</div>

0 commit comments

Comments
 (0)