diff --git a/index.html b/index.html index e4b78ea..5ecbe64 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + 🎰 Glückspiel fetzt 🎲💵
diff --git a/src/App.tsx b/src/App.tsx index a28c7e2..f0ba223 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,8 @@ import React, { useState, useCallback, useMemo } from 'react'; import Card from './components/Card'; import TierCard from './components/TierCard'; import HistoryList from './components/HistoryList'; +import type { Upgrades } from './components/GubbleStore'; +import GubbleStore from './components/GubbleStore'; // --- Scratch Card Game Types and State --- export type Tier = { @@ -19,15 +21,18 @@ const TIERS: Tier[] = [ { name: 'Platinum', unlockPrice: 30, buyPrice: 20, gubblePointChance: 0.1 }, { name: 'Diamond', unlockPrice: 75, buyPrice: 50, gubblePointChance: 0.2 }, { name: 'Master', unlockPrice: 750, buyPrice: 500, gubblePointChance: 0.2 }, - { name: 'Legend', unlockPrice: 3000, buyPrice: 2000, gubblePointChance: 0.3 }, - { name: 'Ultimate', unlockPrice: 7500, buyPrice: 5000, gubblePointChance: 0.3 }, - { name: 'Champion', unlockPrice: 15000, buyPrice: 10000, gubblePointChance: 0.4 }, + { name: 'Legend', unlockPrice: 10000, buyPrice: 8000, gubblePointChance: 0.3 }, + { name: 'Ultimate', unlockPrice: 100000, buyPrice: 80000, gubblePointChance: 0.3 }, + { name: 'Champion', unlockPrice: 5000000, buyPrice: 4000000, gubblePointChance: 0.4 }, ]; +const START_MONEY = 6; +const START_UNLOCKED: string[] = []; + function App() { // --- Game State --- - const [money, setMoney] = useState(5); // User starts with $20 - const [unlockedTiers, setUnlockedTiers] = useState([]); // Unlocked tier names + const [money, setMoney] = useState(START_MONEY); + const [unlockedTiers, setUnlockedTiers] = useState(START_UNLOCKED); const [gubblePoints, setGubblePoints] = useState(0); const [card, setCard] = useState<{ winningNumbers: number[]; @@ -40,6 +45,10 @@ function App() { winningNumbers: number[]; fields: number[]; }[]>([]); + // --- Gubble Store State --- + const [isStoreOpen, setIsStoreOpen] = useState(false); + const [upgrades, setUpgrades] = useState({ evenDouble: false, oddDouble: false, allTripple: false, gubbleDouble: false }); + const [upgradeBought, setUpgradeBought] = useState(false); // --- Tier Unlock Logic --- const handleUnlockTier = useCallback((tier: Tier) => { @@ -72,9 +81,28 @@ function App() { }); }, [unlockedTiers]); + // --- Gubble Store Logic --- + const handleBuyUpgrade = useCallback((upgrade: keyof Upgrades, cost: number) => { + setGubblePoints(gp => { + if (gp < cost) return gp; + setUpgrades(u => ({ ...u, [upgrade]: true })); + setUpgradeBought(true); + return gp - cost; + }); + }, []); + + const handleStoreClose = useCallback(() => { + setIsStoreOpen(false); + if (upgradeBought) { + setMoney(START_MONEY); + setUnlockedTiers(START_UNLOCKED); + setCard(null); + setHistory([]); + setUpgradeBought(false); + } + }, [upgradeBought]); + // --- Update history winnings after all fields scratched --- - // This effect only runs when card changes - // Use useCallback for setHistory logic const updateHistoryWinnings = useCallback(() => { if (!card) return; const allScratched = card.fields.every(f => f.scratched); @@ -90,8 +118,6 @@ function App() { } }, [card]); - // Only run when card changes - // eslint-disable-next-line react-hooks/exhaustive-deps React.useEffect(updateHistoryWinnings, [card]); // --- Memoized winnings for current card --- @@ -103,6 +129,20 @@ function App() {

Scratch Card Game

Money: ${money}
Gubble Points: {gubblePoints}
+ +
{TIERS.map((tier) => ( ))}
@@ -121,7 +162,7 @@ function App() { Winnings this card: ${currentWinnings} - + )} diff --git a/src/components/Card.tsx b/src/components/Card.tsx index e8a323a..5b660d0 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { Tier } from '../App'; +import type { Upgrades } from './GubbleStore'; type CardProps = { card: { @@ -14,9 +15,10 @@ type CardProps = { } | null>>; setMoney: React.Dispatch>; setGubblePoints: React.Dispatch>; + upgrades?: Upgrades; }; -const Card: React.FC = ({ card, setCard, setMoney, setGubblePoints }) => { +const Card: React.FC = ({ card, setCard, setMoney, setGubblePoints, upgrades }) => { return (
{card.tier?.name} Scratch Card
@@ -36,16 +38,28 @@ const Card: React.FC = ({ card, setCard, setMoney, setGubblePoints }) disabled={field.scratched} onMouseMove={() => { if (field.scratched) return; + // Gubble point gain (with upgrade) if (Math.random() < (card.tier?.gubblePointChance ?? 0)) { - setGubblePoints((g) => g + 1); + setGubblePoints((g) => g + (upgrades?.gubbleDouble ? 2 : 1)); return; } // Determine win amount let won = null; if (card.winningNumbers.includes(field.value)) { + won = 0; if (idx < 2) won = Math.ceil((card.tier?.buyPrice ?? 0) * 0.5); else if (idx < 4) won = Math.ceil((card.tier?.buyPrice ?? 0) * 0.8); else won = Math.ceil((card.tier?.buyPrice ?? 0) * 1.2); + // Apply upgrades + if (upgrades?.allTripple) { + won = won * 2; + } + if (upgrades?.evenDouble && field.value % 2 === 0) { + won = won * 2; + } + if (upgrades?.oddDouble && field.value % 2 === 1) { + won = won * 2; + } } setCard((prev) => { if (!prev) return prev; diff --git a/src/components/GubbleStore.tsx b/src/components/GubbleStore.tsx new file mode 100644 index 0000000..867b4ec --- /dev/null +++ b/src/components/GubbleStore.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +type Upgrades = { + evenDouble: boolean; + oddDouble: boolean; + allTripple: boolean; + gubbleDouble: boolean; +}; + +type GubbleStoreProps = { + isOpen: boolean; + onClose: () => void; + gubblePoints: number; + upgrades: Upgrades; + onBuyUpgrade: (upgrade: keyof Upgrades, cost: number) => void; +}; + +const UPGRADE_LIST: { key: keyof Upgrades; label: string; cost: number; description: string }[] = [ + { key: 'evenDouble', label: 'Double Even Winnings', cost: 30, description: 'Double winnings from even numbers.' }, + { key: 'oddDouble', label: 'Double Odd Winnings', cost: 60, description: 'Double winnings from odd numbers.' }, + { key: 'allTripple', label: 'Tripple All Winnings', cost: 150, description: 'Tripple all winnings.' }, + { key: 'gubbleDouble', label: 'Double Gubble Point Gains', cost: 50, description: 'Double gubble point gains.' }, +]; + +const GubbleStore: React.FC = ({ isOpen, onClose, gubblePoints, upgrades, onBuyUpgrade }) => { + if (!isOpen) return null; + return ( +
+
+ +

Gubble Point Store

+
Gubble Points: {gubblePoints}
+
    + {UPGRADE_LIST.map(upg => ( +
  • +
    +
    {upg.label}
    +
    {upg.description}
    +
    Cost: {upg.cost}
    +
    + +
  • + ))} +
+
+
+ ); +}; + +export type { Upgrades }; +export default GubbleStore; \ No newline at end of file diff --git a/src/components/HistoryList.tsx b/src/components/HistoryList.tsx index c09932a..e10e83e 100644 --- a/src/components/HistoryList.tsx +++ b/src/components/HistoryList.tsx @@ -30,7 +30,16 @@ const HistoryList: React.FC = React.memo(({ history }) => {
{h.fields.map((n, j) => ( - {n} + + {n} + ))}
diff --git a/src/components/TierCard.tsx b/src/components/TierCard.tsx index cdd9dcd..a6c2dbc 100644 --- a/src/components/TierCard.tsx +++ b/src/components/TierCard.tsx @@ -5,34 +5,30 @@ type TierCardProps = { tier: Tier; unlocked: boolean; money: number; + gubblePointChance: number; onUnlock: (tier: Tier) => void; onBuy: (tier: Tier) => void; }; const TierCard: React.FC = React.memo(({ tier, unlocked, money, onUnlock, onBuy }) => { return ( -
-
{tier.name}
-
Unlock: ${tier.unlockPrice}
-
Buy: ${tier.buyPrice}
- {!unlocked ? ( - - ) : ( - - )} -
+ ); });