refactor mines multiplier logic to shared utility

This commit is contained in:
Face 2025-06-24 18:08:30 +03:00
parent 05b44be32e
commit fda3643881
3 changed files with 41 additions and 61 deletions

View file

@ -16,6 +16,7 @@
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ModeWatcher } from 'mode-watcher'; import { ModeWatcher } from 'mode-watcher';
import { fetchPortfolioSummary } from '$lib/stores/portfolio-data'; import { fetchPortfolioSummary } from '$lib/stores/portfolio-data';
import { calculateMinesMultiplier } from '$lib/utils';
const GRID_SIZE = 5; const GRID_SIZE = 5;
const TOTAL_TILES = GRID_SIZE * GRID_SIZE; const TOTAL_TILES = GRID_SIZE * GRID_SIZE;
@ -59,14 +60,6 @@
return (probability * 100).toFixed(2); 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) { function setBetAmount(amount: number) {
const clamped = Math.min(amount, Math.min(balance, MAX_BET_AMOUNT)); const clamped = Math.min(amount, Math.min(balance, MAX_BET_AMOUNT));
if (clamped >= 0) { if (clamped >= 0) {
@ -323,9 +316,7 @@
<p class="text-muted-foreground mt-1 text-xs"> <p class="text-muted-foreground mt-1 text-xs">
You will get You will get
<span class="text-success font-semibold"> <span class="text-success font-semibold">
{calculateRawMultiplier(isPlaying ? revealedTiles.length + 1 : 1, mineCount).toFixed( {calculateMinesMultiplier(isPlaying ? revealedTiles.length + 1 : 1, mineCount, betAmount).toFixed(2)}x
2
)}x
</span> </span>
per tile, probability of winning: per tile, probability of winning:
<span class="text-success font-semibold"> <span class="text-success font-semibold">
@ -417,7 +408,7 @@
<span>Next Tile:</span> <span>Next Tile:</span>
<span> <span>
+{formatValue( +{formatValue(
betAmount * (calculateRawMultiplier(revealedTiles.length + 1, mineCount) - 1) betAmount * (calculateMinesMultiplier(revealedTiles.length + 1, mineCount, betAmount) - 1)
)} )}
</span> </span>
</div> </div>

View file

@ -2,6 +2,7 @@ import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema'; import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { redis } from '$lib/server/redis'; import { redis } from '$lib/server/redis';
import { calculateMinesMultiplier } from '$lib/utils';
interface MinesSession { interface MinesSession {
sessionToken: string; 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 { export function calculateMultiplier(picks: number, mines: number, betAmount: number): number {
const TOTAL_TILES = 25; return calculateMinesMultiplier(picks, mines, betAmount);
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)));
}

View file

@ -382,3 +382,38 @@ export function getPrestigeColor(level: number): string {
export function getMaxPrestigeLevel(): number { export function getMaxPrestigeLevel(): number {
return 5; 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)));
}