diff --git a/website/src/lib/components/self/games/Mines.svelte b/website/src/lib/components/self/games/Mines.svelte index 7348ca8..17b0aaa 100644 --- a/website/src/lib/components/self/games/Mines.svelte +++ b/website/src/lib/components/self/games/Mines.svelte @@ -16,6 +16,7 @@ import { onMount, onDestroy } from 'svelte'; import { ModeWatcher } from 'mode-watcher'; import { fetchPortfolioSummary } from '$lib/stores/portfolio-data'; + import { calculateMinesMultiplier } from '$lib/utils'; const GRID_SIZE = 5; const TOTAL_TILES = GRID_SIZE * GRID_SIZE; @@ -59,14 +60,6 @@ return (probability * 100).toFixed(2); } - function calculateRawMultiplier(picks: number, mines: number): number { - let probability = 1; - for (let i = 0; i < picks; i++) { - probability *= (TOTAL_TILES - mines - i) / (TOTAL_TILES - i); - } - return probability === 0 ? 1.0 : Math.max(1.0, 1 / probability); - } - function setBetAmount(amount: number) { const clamped = Math.min(amount, Math.min(balance, MAX_BET_AMOUNT)); if (clamped >= 0) { @@ -323,9 +316,7 @@

You will get - {calculateRawMultiplier(isPlaying ? revealedTiles.length + 1 : 1, mineCount).toFixed( - 2 - )}x + {calculateMinesMultiplier(isPlaying ? revealedTiles.length + 1 : 1, mineCount, betAmount).toFixed(2)}x per tile, probability of winning: @@ -417,7 +408,7 @@ Next Tile: +{formatValue( - betAmount * (calculateRawMultiplier(revealedTiles.length + 1, mineCount) - 1) + betAmount * (calculateMinesMultiplier(revealedTiles.length + 1, mineCount, betAmount) - 1) )} diff --git a/website/src/lib/server/games/mines.ts b/website/src/lib/server/games/mines.ts index 1b64eda..2acbae4 100644 --- a/website/src/lib/server/games/mines.ts +++ b/website/src/lib/server/games/mines.ts @@ -2,6 +2,7 @@ import { db } from '$lib/server/db'; import { user } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; import { redis } from '$lib/server/redis'; +import { calculateMinesMultiplier } from '$lib/utils'; interface MinesSession { sessionToken: string; @@ -111,53 +112,6 @@ export async function minesAutoCashout() { } } -const getMaxPayout = (bet: number, picks: number, mines: number): number => { - const MAX_PAYOUT = 2_000_000; - const HIGH_BET_THRESHOLD = 50_000; - - const mineFactor = 1 + (mines / 25); - const baseMultiplier = (1.4 + Math.pow(picks, 0.45)) * mineFactor; - - if (bet > HIGH_BET_THRESHOLD) { - const betRatio = Math.pow(Math.min(1, (bet - HIGH_BET_THRESHOLD) / (MAX_PAYOUT - HIGH_BET_THRESHOLD)), 1); - - // Direct cap on multiplier for high bets - const maxAllowedMultiplier = 1.05 + (picks * 0.1); - const highBetMultiplier = Math.min(baseMultiplier, maxAllowedMultiplier) * (1 - (bet / MAX_PAYOUT) * 0.9); - const betSizeFactor = Math.max(0.1, 1 - (bet / MAX_PAYOUT) * 0.9); - const minMultiplier = (1.1 + (picks * 0.15 * betSizeFactor)) * mineFactor; - - const reducedMultiplier = highBetMultiplier - ((highBetMultiplier - minMultiplier) * betRatio); - const payout = Math.min(bet * reducedMultiplier, MAX_PAYOUT); - - return payout; - } - - const payout = Math.min(bet * baseMultiplier, MAX_PAYOUT); - return payout; -}; - export function calculateMultiplier(picks: number, mines: number, betAmount: number): number { - const TOTAL_TILES = 25; - const HOUSE_EDGE = 0.05; - - // Calculate probability of winning based on picks and mines - let probability = 1; - for (let i = 0; i < picks; i++) { - probability *= (TOTAL_TILES - mines - i) / (TOTAL_TILES - i); - } - - if (probability <= 0) return 1.0; - - // Calculate fair multiplier based on probability and house edge - const fairMultiplier = (1 / probability) * (1 - HOUSE_EDGE); - - const rawPayout = fairMultiplier * betAmount; - const maxPayout = getMaxPayout(betAmount, picks, mines); - const cappedPayout = Math.min(rawPayout, maxPayout); - const effectiveMultiplier = cappedPayout / betAmount; - - return Math.max(1.0, Number(effectiveMultiplier.toFixed(2))); -} - - + return calculateMinesMultiplier(picks, mines, betAmount); +} \ No newline at end of file diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 2f131b6..b5cee9c 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -382,3 +382,38 @@ export function getPrestigeColor(level: number): string { export function getMaxPrestigeLevel(): number { return 5; } + +export function calculateMinesMultiplier(picks: number, mines: number, betAmount: number): number { + const TOTAL_TILES = 25; + const HOUSE_EDGE = 0.05; + + let probability = 1; + for (let i = 0; i < picks; i++) { + probability *= (TOTAL_TILES - mines - i) / (TOTAL_TILES - i); + } + if (probability <= 0) return 1.0; + + const fairMultiplier = (1 / probability) * (1 - HOUSE_EDGE); + + // Backend payout cap logic + const MAX_PAYOUT = 2_000_000; + const HIGH_BET_THRESHOLD = 50_000; + const mineFactor = 1 + (mines / 25); + const baseMultiplier = (1.4 + Math.pow(picks, 0.45)) * mineFactor; + let maxPayout: number; + if (betAmount > HIGH_BET_THRESHOLD) { + const betRatio = Math.pow(Math.min(1, (betAmount - HIGH_BET_THRESHOLD) / (MAX_PAYOUT - HIGH_BET_THRESHOLD)), 1); + const maxAllowedMultiplier = 1.05 + (picks * 0.1); + const highBetMultiplier = Math.min(baseMultiplier, maxAllowedMultiplier) * (1 - (betAmount / MAX_PAYOUT) * 0.9); + const betSizeFactor = Math.max(0.1, 1 - (betAmount / MAX_PAYOUT) * 0.9); + const minMultiplier = (1.1 + (picks * 0.15 * betSizeFactor)) * mineFactor; + const reducedMultiplier = highBetMultiplier - ((highBetMultiplier - minMultiplier) * betRatio); + maxPayout = Math.min(betAmount * reducedMultiplier, MAX_PAYOUT); + } else { + maxPayout = Math.min(betAmount * baseMultiplier, MAX_PAYOUT); + } + const rawPayout = fairMultiplier * betAmount; + const cappedPayout = Math.min(rawPayout, maxPayout); + const effectiveMultiplier = cappedPayout / betAmount; + return Math.max(1.0, Number(effectiveMultiplier.toFixed(2))); +}