tst12 / index.html
widichandra's picture
use RNG for every game result - Follow Up Deployment
b84463f verified
Raw
History Blame Contribute Delete
64.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stake.com Game Simulator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
<style>
.sidebar {
scrollbar-width: thin;
scrollbar-color: #4b5563 #1f2937;
}
.sidebar::-webkit-scrollbar {
width: 6px;
}
.sidebar::-webkit-scrollbar-track {
background: #1f2937;
}
.sidebar::-webkit-scrollbar-thumb {
background-color: #4b5563;
border-radius: 6px;
}
.progress-ring__circle {
transition: stroke-dashoffset 0.1s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #111827;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div class="flex flex-col lg:flex-row h-screen">
<!-- Sidebar -->
<div class="sidebar w-full lg:w-80 bg-gray-800 overflow-y-auto p-4">
<h1 class="text-2xl font-bold mb-6 text-center">Stake Simulator</h1>
<!-- Game Selection -->
<div class="mb-6">
<h2 class="text-lg font-semibold mb-2">Game</h2>
<div class="grid grid-cols-2 gap-2">
<button id="game-dice" class="game-btn bg-blue-600 hover:bg-blue-700 py-2 px-4 rounded">Dice</button>
<button id="game-limbo" class="game-btn bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded">Limbo</button>
<button id="game-keno" class="game-btn bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded">Keno</button>
<button id="game-wheel" class="game-btn bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded">Wheel</button>
</div>
</div>
<!-- Game Settings -->
<div id="game-settings" class="mb-6">
<!-- Dice/Limbo Settings -->
<div id="dice-limbo-settings">
<div class="mb-4">
<label class="block mb-1">Target Multiplier</label>
<input type="range" id="target-multiplier" min="1.01" max="990" step="0.01" value="2" class="w-full">
<div class="flex justify-between">
<span>1.01x</span>
<span id="target-multiplier-value">2x</span>
<span>990x</span>
</div>
</div>
<div class="mb-4">
<label class="block mb-1">Win Chance</label>
<input type="range" id="win-chance" min="0.1" max="99" step="0.1" value="49.5" class="w-full">
<div class="flex justify-between">
<span>0.1%</span>
<span id="win-chance-value">49.5%</span>
<span>99%</span>
</div>
</div>
</div>
<!-- Keno Settings -->
<div id="keno-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">Tiles Picked</label>
<input type="range" id="keno-tiles" min="1" max="10" step="1" value="6" class="w-full">
<div class="flex justify-between">
<span>1</span>
<span id="keno-tiles-value">6</span>
<span>10</span>
</div>
</div>
</div>
<!-- Wheel Settings -->
<div id="wheel-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">Segment</label>
<select id="wheel-segment" class="w-full bg-gray-700 rounded p-2">
<option value="1">1 (1.12x)</option>
<option value="2">2 (1.25x)</option>
<option value="3">3 (1.5x)</option>
<option value="4">4 (1.75x)</option>
<option value="5">5 (2x)</option>
<option value="6">6 (2.5x)</option>
<option value="7">7 (3x)</option>
<option value="8">8 (5x)</option>
<option value="9">9 (10x)</option>
<option value="10">10 (25x)</option>
<option value="11">11 (100x)</option>
</select>
</div>
</div>
</div>
<!-- Bankroll Settings -->
<div class="mb-6">
<h2 class="text-lg font-semibold mb-2">Bankroll</h2>
<div class="mb-4">
<label class="block mb-1">Starting Amount</label>
<input type="number" id="starting-amount" min="1" value="1000" class="w-full bg-gray-700 rounded p-2">
</div>
<div class="mb-4">
<label class="block mb-1">Base Bet Amount</label>
<input type="number" id="base-bet" min="0.00000001" step="0.00000001" value="1" class="w-full bg-gray-700 rounded p-2">
</div>
</div>
<!-- Strategy Selection -->
<div class="mb-6">
<h2 class="text-lg font-semibold mb-2">Strategy</h2>
<select id="strategy" class="w-full bg-gray-700 rounded p-2 mb-4">
<option value="flat">Flat Betting</option>
<option value="martingale">Martingale</option>
<option value="reverse-martingale">Reverse Martingale</option>
<option value="fibonacci">Fibonacci</option>
<option value="dalembert">D'Alembert</option>
<option value="custom">Custom Strategy</option>
</select>
<!-- Strategy Settings -->
<div id="strategy-settings">
<!-- Martingale/Reverse Martingale -->
<div id="martingale-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">Multiplier on Loss</label>
<input type="number" id="martingale-multiplier" min="1.01" step="0.01" value="2" class="w-full bg-gray-700 rounded p-2">
</div>
<div class="mb-4">
<label class="block mb-1">Reset to Base on Win</label>
<select id="martingale-reset" class="w-full bg-gray-700 rounded p-2">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div>
<!-- Fibonacci -->
<div id="fibonacci-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">Sequence Step on Loss</label>
<select id="fibonacci-step" class="w-full bg-gray-700 rounded p-2">
<option value="next">Next in Sequence</option>
<option value="reset">Reset to Start</option>
</select>
</div>
</div>
<!-- D'Alembert -->
<div id="dalembert-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">Increment Amount</label>
<input type="number" id="dalembert-increment" min="0.01" step="0.01" value="1" class="w-full bg-gray-700 rounded p-2">
</div>
</div>
<!-- Custom Strategy -->
<div id="custom-settings" class="hidden">
<div class="mb-4">
<label class="block mb-1">On Win</label>
<select id="custom-on-win" class="w-full bg-gray-700 rounded p-2">
<option value="reset">Reset to Base</option>
<option value="increase">Increase Bet</option>
<option value="decrease">Decrease Bet</option>
<option value="same">Keep Same</option>
</select>
</div>
<div class="mb-4">
<label class="block mb-1">Win Multiplier</label>
<input type="number" id="custom-win-multiplier" min="0.01" step="0.01" value="1" class="w-full bg-gray-700 rounded p-2">
</div>
<div class="mb-4">
<label class="block mb-1">On Loss</label>
<select id="custom-on-loss" class="w-full bg-gray-700 rounded p-2">
<option value="reset">Reset to Base</option>
<option value="increase">Increase Bet</option>
<option value="decrease">Decrease Bet</option>
<option value="same">Keep Same</option>
</select>
</div>
<div class="mb-4">
<label class="block mb-1">Loss Multiplier</label>
<input type="number" id="custom-loss-multiplier" min="0.01" step="0.01" value="1" class="w-full bg-gray-700 rounded p-2">
</div>
</div>
</div>
</div>
<!-- Simulation Settings -->
<div class="mb-6">
<h2 class="text-lg font-semibold mb-2">Simulation</h2>
<div class="mb-4">
<label class="block mb-1">Rounds</label>
<input type="number" id="rounds" min="1" max="1000000" value="1000" class="w-full bg-gray-700 rounded p-2">
</div>
<div class="mb-4">
<label class="block mb-1">Stop Conditions</label>
<div class="flex items-center mb-2">
<input type="checkbox" id="enable-stop-profit" class="mr-2">
<label>Stop at Profit</label>
<input type="number" id="stop-profit" min="0.01" step="0.01" value="100" class="w-20 bg-gray-700 rounded p-2 ml-2" disabled>
</div>
<div class="flex items-center">
<input type="checkbox" id="enable-stop-loss" class="mr-2">
<label>Stop at Loss</label>
<input type="number" id="stop-loss" min="0.01" step="0.01" value="100" class="w-20 bg-gray-700 rounded p-2 ml-2" disabled>
</div>
</div>
</div>
<!-- Run Button -->
<button id="run-simulation" class="w-full bg-green-600 hover:bg-green-700 py-3 px-4 rounded-lg font-bold">Run Simulation</button>
</div>
<!-- Main Content -->
<div class="flex-1 overflow-y-auto p-4">
<div class="max-w-7xl mx-auto">
<!-- Progress Indicator -->
<div id="progress-container" class="hidden mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-xl font-semibold">Simulation Progress</h2>
<span id="progress-text">0%</span>
</div>
<div class="flex items-center">
<div class="w-16 h-16 mr-4">
<svg class="progress-ring" width="64" height="64">
<circle class="progress-ring__circle" stroke="#3b82f6" stroke-width="4" fill="transparent" r="28" cx="32" cy="32" />
</svg>
</div>
<div class="flex-1 bg-gray-700 rounded-full h-4">
<div id="progress-bar" class="bg-blue-500 h-4 rounded-full" style="width: 0%"></div>
</div>
</div>
<div class="mt-2 text-sm text-gray-400">
<span id="completed-rounds">0</span> / <span id="total-rounds">0</span> rounds completed
</div>
</div>
<!-- Results -->
<div id="results-container" class="hidden">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Simulation Results</h2>
<button id="export-csv" class="bg-blue-600 hover:bg-blue-700 py-2 px-4 rounded">Export to CSV</button>
</div>
<!-- Summary Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Starting Bankroll</div>
<div id="starting-bankroll" class="text-xl font-bold">1,000</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Ending Bankroll</div>
<div id="ending-bankroll" class="text-xl font-bold">1,000</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Profit/Loss</div>
<div id="profit-loss" class="text-xl font-bold">0</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">ROI</div>
<div id="roi" class="text-xl font-bold">0%</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Win Rate</div>
<div id="win-rate" class="text-xl font-bold">0%</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Max Win Streak</div>
<div id="max-win-streak" class="text-xl font-bold">0</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Max Loss Streak</div>
<div id="max-loss-streak" class="text-xl font-bold">0</div>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-gray-400">Max Drawdown</div>
<div id="max-drawdown" class="text-xl font-bold">0</div>
</div>
</div>
<!-- Chart -->
<div class="bg-gray-800 p-4 rounded-lg mb-6">
<h3 class="text-lg font-semibold mb-4">Bankroll Over Time</h3>
<canvas id="bankroll-chart" height="300"></canvas>
</div>
<!-- Detailed Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div class="bg-gray-800 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">Profit Distribution</h3>
<canvas id="profit-distribution-chart" height="200"></canvas>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">Bet Sizes</h3>
<canvas id="bet-sizes-chart" height="200"></canvas>
</div>
</div>
<!-- Recent Rounds Table -->
<div class="bg-gray-800 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">Recent Rounds</h3>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="py-2 px-4 text-left">Round</th>
<th class="py-2 px-4 text-left">Bet</th>
<th class="py-2 px-4 text-left">Result</th>
<th class="py-2 px-4 text-left">Payout</th>
<th class="py-2 px-4 text-left">Bankroll</th>
<th class="py-2 px-4 text-left">Verify</th>
</tr>
</thead>
<tbody id="recent-rounds">
<!-- Will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Initial State -->
<div id="initial-state" class="flex flex-col items-center justify-center h-96">
<div class="text-center max-w-md">
<h2 class="text-2xl font-bold mb-4">Stake.com Game Simulator</h2>
<p class="text-gray-400 mb-6">Configure your simulation settings in the sidebar and click "Run Simulation" to start analyzing different betting strategies.</p>
<div class="grid grid-cols-2 gap-4">
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-blue-400 font-semibold mb-2">Features</div>
<ul class="text-sm text-gray-400">
<li class="mb-1">• Dice, Limbo, Keno, Wheel</li>
<li class="mb-1">• 5 Betting Strategies</li>
<li class="mb-1">• Custom Strategy Builder</li>
</ul>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-blue-400 font-semibold mb-2">Stats</div>
<ul class="text-sm text-gray-400">
<li class="mb-1">• Bankroll Graph</li>
<li class="mb-1">• Win/Loss Analysis</li>
<li class="mb-1">• CSV Export</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const gameButtons = document.querySelectorAll('.game-btn');
const gameSettings = document.getElementById('game-settings');
const diceLimboSettings = document.getElementById('dice-limbo-settings');
const kenoSettings = document.getElementById('keno-settings');
const wheelSettings = document.getElementById('wheel-settings');
const targetMultiplier = document.getElementById('target-multiplier');
const targetMultiplierValue = document.getElementById('target-multiplier-value');
const winChance = document.getElementById('win-chance');
const winChanceValue = document.getElementById('win-chance-value');
const kenoTiles = document.getElementById('keno-tiles');
const kenoTilesValue = document.getElementById('keno-tiles-value');
const strategySelect = document.getElementById('strategy');
const strategySettings = document.getElementById('strategy-settings');
const martingaleSettings = document.getElementById('martingale-settings');
const fibonacciSettings = document.getElementById('fibonacci-settings');
const dalembertSettings = document.getElementById('dalembert-settings');
const customSettings = document.getElementById('custom-settings');
const runSimulationBtn = document.getElementById('run-simulation');
const progressContainer = document.getElementById('progress-container');
const progressText = document.getElementById('progress-text');
const progressBar = document.getElementById('progress-bar');
const completedRounds = document.getElementById('completed-rounds');
const totalRounds = document.getElementById('total-rounds');
const resultsContainer = document.getElementById('results-container');
const initialState = document.getElementById('initial-state');
const exportCsvBtn = document.getElementById('export-csv');
// Game variables
let currentGame = 'dice';
let simulationRunning = false;
let simulationResults = [];
let bankrollChart = null;
let profitDistributionChart = null;
let betSizesChart = null;
// Initialize event listeners
function init() {
// Game selection
gameButtons.forEach(button => {
button.addEventListener('click', () => {
gameButtons.forEach(btn => btn.classList.remove('bg-blue-600', 'hover:bg-blue-700'));
gameButtons.forEach(btn => btn.classList.add('bg-gray-700', 'hover:bg-gray-600'));
button.classList.remove('bg-gray-700', 'hover:bg-gray-600');
button.classList.add('bg-blue-600', 'hover:bg-blue-700');
currentGame = button.id.split('-')[1];
updateGameSettings();
});
});
// Target multiplier and win chance sync
targetMultiplier.addEventListener('input', () => {
const value = parseFloat(targetMultiplier.value);
targetMultiplierValue.textContent = value.toFixed(2) + 'x';
// Calculate win chance for dice/limbo
const newWinChance = (99 / value) * 0.99; // 0.1% house edge
winChance.value = newWinChance.toFixed(1);
winChanceValue.textContent = newWinChance.toFixed(1) + '%';
});
winChance.addEventListener('input', () => {
const value = parseFloat(winChance.value);
winChanceValue.textContent = value.toFixed(1) + '%';
// Calculate target multiplier for dice/limbo
const newMultiplier = (99 / value) * 0.99; // 0.1% house edge
targetMultiplier.value = newMultiplier.toFixed(2);
targetMultiplierValue.textContent = newMultiplier.toFixed(2) + 'x';
});
// Keno tiles sync
kenoTiles.addEventListener('input', () => {
kenoTilesValue.textContent = kenoTiles.value;
});
// Strategy selection
strategySelect.addEventListener('change', updateStrategySettings);
// Stop conditions
document.getElementById('enable-stop-profit').addEventListener('change', (e) => {
document.getElementById('stop-profit').disabled = !e.target.checked;
});
document.getElementById('enable-stop-loss').addEventListener('change', (e) => {
document.getElementById('stop-loss').disabled = !e.target.checked;
});
// Run simulation
runSimulationBtn.addEventListener('click', runSimulation);
// Export CSV
exportCsvBtn.addEventListener('click', exportToCSV);
// Initial setup
updateGameSettings();
updateStrategySettings();
}
// Update game settings based on selected game
function updateGameSettings() {
diceLimboSettings.classList.add('hidden');
kenoSettings.classList.add('hidden');
wheelSettings.classList.add('hidden');
if (currentGame === 'dice' || currentGame === 'limbo') {
diceLimboSettings.classList.remove('hidden');
} else if (currentGame === 'keno') {
kenoSettings.classList.remove('hidden');
} else if (currentGame === 'wheel') {
wheelSettings.classList.remove('hidden');
}
}
// Update strategy settings based on selected strategy
function updateStrategySettings() {
martingaleSettings.classList.add('hidden');
fibonacciSettings.classList.add('hidden');
dalembertSettings.classList.add('hidden');
customSettings.classList.add('hidden');
const strategy = strategySelect.value;
if (strategy === 'martingale' || strategy === 'reverse-martingale') {
martingaleSettings.classList.remove('hidden');
} else if (strategy === 'fibonacci') {
fibonacciSettings.classList.remove('hidden');
} else if (strategy === 'dalembert') {
dalembertSettings.classList.remove('hidden');
} else if (strategy === 'custom') {
customSettings.classList.remove('hidden');
}
}
// Run the simulation
function runSimulation() {
if (simulationRunning) return;
// Get simulation parameters
const rounds = parseInt(document.getElementById('rounds').value);
const startingAmount = parseFloat(document.getElementById('starting-amount').value);
const baseBet = parseFloat(document.getElementById('base-bet').value);
const strategy = strategySelect.value;
// Get game-specific parameters
let gameParams = {};
if (currentGame === 'dice' || currentGame === 'limbo') {
gameParams = {
targetMultiplier: parseFloat(targetMultiplier.value),
winChance: parseFloat(winChance.value) / 100
};
} else if (currentGame === 'keno') {
gameParams = {
tilesPicked: parseInt(kenoTiles.value)
};
} else if (currentGame === 'wheel') {
gameParams = {
segment: parseInt(document.getElementById('wheel-segment').value)
};
}
// Get strategy parameters
let strategyParams = {};
if (strategy === 'martingale' || strategy === 'reverse-martingale') {
strategyParams = {
multiplier: parseFloat(document.getElementById('martingale-multiplier').value),
resetOnWin: document.getElementById('martingale-reset').value === 'true'
};
} else if (strategy === 'fibonacci') {
strategyParams = {
stepOnLoss: document.getElementById('fibonacci-step').value
};
} else if (strategy === 'dalembert') {
strategyParams = {
increment: parseFloat(document.getElementById('dalembert-increment').value)
};
} else if (strategy === 'custom') {
strategyParams = {
onWin: document.getElementById('custom-on-win').value,
winMultiplier: parseFloat(document.getElementById('custom-win-multiplier').value),
onLoss: document.getElementById('custom-on-loss').value,
lossMultiplier: parseFloat(document.getElementById('custom-loss-multiplier').value)
};
}
// Get stop conditions
const stopConditions = {
stopProfit: document.getElementById('enable-stop-profit').checked ?
parseFloat(document.getElementById('stop-profit').value) : null,
stopLoss: document.getElementById('enable-stop-loss').checked ?
parseFloat(document.getElementById('stop-loss').value) : null
};
// Validate inputs
if (isNaN(rounds) || rounds < 1) {
alert('Please enter a valid number of rounds (minimum 1)');
return;
}
if (isNaN(startingAmount) || startingAmount <= 0) {
alert('Please enter a valid starting amount (greater than 0)');
return;
}
if (isNaN(baseBet) || baseBet <= 0) {
alert('Please enter a valid base bet amount (greater than 0)');
return;
}
// Prepare UI for simulation
simulationRunning = true;
runSimulationBtn.disabled = true;
runSimulationBtn.textContent = 'Running...';
progressContainer.classList.remove('hidden');
resultsContainer.classList.add('hidden');
initialState.classList.add('hidden');
completedRounds.textContent = '0';
totalRounds.textContent = rounds.toLocaleString();
progressBar.style.width = '0%';
progressText.textContent = '0%';
// Run simulation in chunks to avoid freezing the UI
setTimeout(async () => {
const results = await simulate(
currentGame,
rounds,
startingAmount,
baseBet,
strategy,
gameParams,
strategyParams,
stopConditions,
updateProgress
);
// Simulation complete
simulationRunning = false;
runSimulationBtn.disabled = false;
runSimulationBtn.textContent = 'Run Simulation';
// Store results and display them
simulationResults = results;
displayResults(results);
}, 100);
}
// Update progress during simulation
function updateProgress(completed, total) {
const percent = Math.round((completed / total) * 100);
progressBar.style.width = percent + '%';
progressText.textContent = percent + '%';
completedRounds.textContent = completed.toLocaleString();
// Update progress ring
const circle = document.querySelector('.progress-ring__circle');
const radius = circle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (percent / 100) * circumference;
circle.style.strokeDasharray = `${circumference} ${circumference}`;
circle.style.strokeDashoffset = offset;
}
// Main simulation function
async function simulate(game, rounds, startingAmount, baseBet, strategy, gameParams, strategyParams, stopConditions, progressCallback) {
const results = [];
let bankroll = startingAmount;
let currentBet = baseBet;
let betSequence = [1, 1]; // For Fibonacci
let betIndex = 0; // For Fibonacci
let consecutiveWins = 0;
let consecutiveLosses = 0;
let maxWinStreak = 0;
let maxLossStreak = 0;
let maxBankroll = startingAmount;
let minBankroll = startingAmount;
let maxDrawdown = 0;
let stopConditionMet = false;
// Initialize provably fair variables
const serverSeed = generateRandomSeed();
const clientSeed = generateRandomSeed();
let nonce = 0;
for (let i = 0; i < rounds; i++) {
if (stopConditionMet) break;
// Check if we can afford the bet
if (bankroll < currentBet) {
results.push({
round: i + 1,
bet: currentBet,
result: 'BUST',
payout: 0,
bankroll: 0,
serverSeed,
clientSeed,
nonce
});
break;
}
// Place the bet
bankroll -= currentBet;
// Play the game
let win = false;
let payoutMultiplier = 1;
// Generate provably fair result
let randomValue;
await generateHash(serverSeed, clientSeed, nonce).then(hash => {
const seed = parseInt(hash.substring(0, 8), 16);
const rng = createRNG(seed);
randomValue = rng();
nonce++;
});
if (game === 'dice' || game === 'limbo') {
win = randomValue < gameParams.winChance;
payoutMultiplier = gameParams.targetMultiplier * 0.999; // 0.1% house edge
} else if (game === 'keno') {
// Simplified Keno logic
const hits = simulateKeno(randomValue, gameParams.tilesPicked);
payoutMultiplier = getKenoMultiplier(gameParams.tilesPicked, hits) * 0.999;
win = hits > 0;
} else if (game === 'wheel') {
// Simplified Wheel logic
const segment = getWheelSegment(randomValue);
win = segment === gameParams.segment;
payoutMultiplier = getWheelMultiplier(segment) * 0.999;
}
// Calculate payout
const payout = win ? currentBet * payoutMultiplier : 0;
bankroll += payout;
// Update streaks
if (win) {
consecutiveWins++;
consecutiveLosses = 0;
maxWinStreak = Math.max(maxWinStreak, consecutiveWins);
} else {
consecutiveLosses++;
consecutiveWins = 0;
maxLossStreak = Math.max(maxLossStreak, consecutiveLosses);
}
// Update bankroll stats
maxBankroll = Math.max(maxBankroll, bankroll);
minBankroll = Math.min(minBankroll, bankroll);
const drawdown = (maxBankroll - bankroll) / maxBankroll * 100;
maxDrawdown = Math.max(maxDrawdown, drawdown);
// Store result
results.push({
round: i + 1,
bet: currentBet,
result: win ? 'WIN' : 'LOSS',
payout: payout,
bankroll: bankroll,
serverSeed,
clientSeed,
nonce: nonce - 1
});
// Update bet amount based on strategy
if (strategy === 'flat') {
// No change, keep betting the same amount
} else if (strategy === 'martingale') {
if (win && strategyParams.resetOnWin) {
currentBet = baseBet;
} else if (!win) {
currentBet *= strategyParams.multiplier;
}
} else if (strategy === 'reverse-martingale') {
if (win) {
currentBet *= strategyParams.multiplier;
} else if (strategyParams.resetOnWin) {
currentBet = baseBet;
}
} else if (strategy === 'fibonacci') {
if (win) {
if (betIndex <= 1) {
currentBet = baseBet;
betIndex = 0;
} else {
betIndex -= 2;
currentBet = betSequence[betIndex];
}
} else {
if (strategyParams.stepOnLoss === 'next') {
betIndex = Math.min(betIndex + 1, betSequence.length - 1);
currentBet = betSequence[betIndex];
// Extend sequence if needed
if (betIndex === betSequence.length - 1) {
const nextValue = betSequence[betIndex] + betSequence[betIndex - 1];
betSequence.push(nextValue);
}
} else {
currentBet = baseBet;
betIndex = 0;
}
}
} else if (strategy === 'dalembert') {
if (win) {
currentBet = Math.max(baseBet, currentBet - strategyParams.increment);
} else {
currentBet += strategyParams.increment;
}
} else if (strategy === 'custom') {
if (win) {
if (strategyParams.onWin === 'reset') {
currentBet = baseBet;
} else if (strategyParams.onWin === 'increase') {
currentBet *= strategyParams.winMultiplier;
} else if (strategyParams.onWin === 'decrease') {
currentBet /= strategyParams.winMultiplier;
}
// 'same' does nothing
} else {
if (strategyParams.onLoss === 'reset') {
currentBet = baseBet;
} else if (strategyParams.onLoss === 'increase') {
currentBet *= strategyParams.lossMultiplier;
} else if (strategyParams.onLoss === 'decrease') {
currentBet /= strategyParams.lossMultiplier;
}
// 'same' does nothing
}
}
// Check stop conditions
if (stopConditions.stopProfit !== null && bankroll >= startingAmount + stopConditions.stopProfit) {
stopConditionMet = true;
}
if (stopConditions.stopLoss !== null && bankroll <= startingAmount - stopConditions.stopLoss) {
stopConditionMet = true;
}
// Update progress every 100 rounds or at the end
if (i % 100 === 0 || i === rounds - 1) {
progressCallback(i + 1, rounds);
}
}
// Calculate summary statistics
const wins = results.filter(r => r.result === 'WIN').length;
const losses = results.filter(r => r.result === 'LOSS').length;
const busts = results.filter(r => r.result === 'BUST').length;
const winRate = (wins / (wins + losses)) * 100 || 0;
const profit = bankroll - startingAmount;
const roi = (profit / startingAmount) * 100 || 0;
return {
results,
summary: {
startingAmount,
endingAmount: bankroll,
profit,
roi,
winRate,
maxWinStreak,
maxLossStreak,
maxDrawdown,
roundsCompleted: results.length,
roundsRequested: rounds,
stopConditionMet
}
};
}
// Game-specific helper functions
function simulateKeno(randomValue, tilesPicked) {
// Simplified Keno - probability based on tiles picked
const hitProbabilities = {
1: [0.25, 0.75],
2: [0.06, 0.38, 0.56],
3: [0.01, 0.15, 0.45, 0.39],
4: [0.003, 0.05, 0.25, 0.45, 0.25],
5: [0.001, 0.02, 0.10, 0.30, 0.40, 0.18],
6: [0.0004, 0.01, 0.05, 0.17, 0.35, 0.32, 0.10],
7: [0.0001, 0.004, 0.02, 0.09, 0.22, 0.35, 0.25, 0.07],
8: [0.00003, 0.002, 0.01, 0.05, 0.13, 0.26, 0.32, 0.18, 0.04],
9: [0.00001, 0.001, 0.004, 0.02, 0.07, 0.16, 0.28, 0.29, 0.13, 0.02],
10: [0.000003, 0.0003, 0.002, 0.01, 0.04, 0.10, 0.20, 0.28, 0.22, 0.08, 0.01]
};
const probs = hitProbabilities[tilesPicked];
let cumulativeProb = 0;
let hits = 0;
for (let i = 0; i < probs.length; i++) {
cumulativeProb += probs[i];
if (randomValue < cumulativeProb) {
hits = i;
break;
}
}
return hits;
}
function getKenoMultiplier(tilesPicked, hits) {
const multipliers = {
1: [0, 3.3],
2: [0, 1, 9],
3: [0, 0, 2, 42],
4: [0, 0, 1, 4, 88],
5: [0, 0, 0, 1, 16, 150],
6: [0, 0, 0, 1, 2, 26, 600],
7: [0, 0, 0, 0, 1, 4, 50, 1000],
8: [0, 0, 0, 0, 0, 2, 10, 100, 4200],
9: [0, 0, 0, 0, 0, 1, 3, 25, 250, 10000],
10: [0, 0, 0, 0, 0, 0, 2, 10, 60, 750, 100000]
};
return multipliers[tilesPicked][hits] || 0;
}
function getWheelSegment(randomValue) {
const segments = [
{ min: 0.0000, max: 0.8000, segment: 1 }, // 80%
{ min: 0.8000, max: 0.8800, segment: 2 }, // 8%
{ min: 0.8800, max: 0.9280, segment: 3 }, // 4.8%
{ min: 0.9280, max: 0.9568, segment: 4 }, // 2.88%
{ min: 0.9568, max: 0.9741, segment: 5 }, // 1.728%
{ min: 0.9741, max: 0.9845, segment: 6 }, // 1.0368%
{ min: 0.9845, max: 0.9907, segment: 7 }, // 0.6221%
{ min: 0.9907, max: 0.9944, segment: 8 }, // 0.3733%
{ min: 0.9944, max: 0.9966, segment: 9 }, // 0.2240%
{ min: 0.9966, max: 0.9980, segment: 10 }, // 0.1344%
{ min: 0.9980, max: 1.0000, segment: 11 } // 0.0806%
];
for (const segment of segments) {
if (randomValue >= segment.min && randomValue < segment.max) {
return segment.segment;
}
}
return 1; // Default to first segment
}
function getWheelMultiplier(segment) {
const multipliers = {
1: 1.12,
2: 1.25,
3: 1.5,
4: 1.75,
5: 2,
6: 2.5,
7: 3,
8: 5,
9: 10,
10: 25,
11: 100
};
return multipliers[segment] || 1;
}
// Seeded RNG functions
function generateRandomSeed() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 32; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function generateHash(serverSeed, clientSeed, nonce) {
// Use crypto-grade hash function
const str = serverSeed + clientSeed + nonce;
const encoder = new TextEncoder();
const data = encoder.encode(str);
return crypto.subtle.digest('SHA-256', data).then(hashBuffer => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
});
}
// Mulberry32 PRNG - fast and good quality
function createRNG(seed) {
return function() {
seed |= 0;
seed = seed + 0x6D2B79F5 | 0;
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// Display results
function displayResults(simulation) {
const { results, summary } = simulation;
// Update summary stats
document.getElementById('starting-bankroll').textContent = formatCurrency(summary.startingAmount);
document.getElementById('ending-bankroll').textContent = formatCurrency(summary.endingAmount);
const profitElement = document.getElementById('profit-loss');
profitElement.textContent = formatCurrency(summary.profit);
if (summary.profit > 0) {
profitElement.classList.add('text-green-500');
profitElement.classList.remove('text-red-500');
} else {
profitElement.classList.add('text-red-500');
profitElement.classList.remove('text-green-500');
}
const roiElement = document.getElementById('roi');
roiElement.textContent = summary.roi.toFixed(2) + '%';
if (summary.roi > 0) {
roiElement.classList.add('text-green-500');
roiElement.classList.remove('text-red-500');
} else {
roiElement.classList.add('text-red-500');
roiElement.classList.remove('text-green-500');
}
document.getElementById('win-rate').textContent = summary.winRate.toFixed(2) + '%';
document.getElementById('max-win-streak').textContent = summary.maxWinStreak;
document.getElementById('max-loss-streak').textContent = summary.maxLossStreak;
document.getElementById('max-drawdown').textContent = summary.maxDrawdown.toFixed(2) + '%';
// Create bankroll chart
createBankrollChart(results);
// Create distribution charts
createDistributionCharts(results);
// Show recent rounds
showRecentRounds(results.slice(-10));
// Show results container
resultsContainer.classList.remove('hidden');
}
function createBankrollChart(results) {
const ctx = document.getElementById('bankroll-chart').getContext('2d');
// Destroy previous chart if it exists
if (bankrollChart) {
bankrollChart.destroy();
}
// Prepare data
const labels = results.map((_, i) => i + 1);
const data = results.map(r => r.bankroll);
bankrollChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Bankroll',
data: data,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return 'Bankroll: ' + formatCurrency(context.raw);
}
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Round'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
y: {
title: {
display: true,
text: 'Amount'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
callback: function(value) {
return formatCurrency(value);
}
}
}
}
}
});
}
function createDistributionCharts(results) {
// Profit distribution chart
const profitCtx = document.getElementById('profit-distribution-chart').getContext('2d');
// Calculate profit per round
const profits = [];
let previousBankroll = results[0].startingAmount || results[0].bankroll - (results[0].payout - results[0].bet);
for (let i = 0; i < results.length; i++) {
const profit = results[i].bankroll - previousBankroll;
profits.push(profit);
previousBankroll = results[i].bankroll;
}
// Categorize profits
const profitCategories = {
'Big Win (> 5x bet)': 0,
'Win (1-5x bet)': 0,
'Small Win (< 1x bet)': 0,
'Small Loss (< 1x bet)': 0,
'Loss (1-5x bet)': 0,
'Big Loss (> 5x bet)': 0
};
for (const profit of profits) {
if (profit > 0) {
if (profit > 5 * results[0].bet) {
profitCategories['Big Win (> 5x bet)']++;
} else if (profit > results[0].bet) {
profitCategories['Win (1-5x bet)']++;
} else {
profitCategories['Small Win (< 1x bet)']++;
}
} else if (profit < 0) {
const loss = Math.abs(profit);
if (loss > 5 * results[0].bet) {
profitCategories['Big Loss (> 5x bet)']++;
} else if (loss > results[0].bet) {
profitCategories['Loss (1-5x bet)']++;
} else {
profitCategories['Small Loss (< 1x bet)']++;
}
}
}
// Destroy previous chart if it exists
if (profitDistributionChart) {
profitDistributionChart.destroy();
}
profitDistributionChart = new Chart(profitCtx, {
type: 'doughnut',
data: {
labels: Object.keys(profitCategories),
datasets: [{
data: Object.values(profitCategories),
backgroundColor: [
'#10b981',
'#34d399',
'#6ee7b7',
'#fca5a5',
'#f87171',
'#ef4444'
],
borderColor: '#1f2937',
borderWidth: 2
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
labels: {
color: '#f3f4f6'
}
}
}
}
});
// Bet sizes chart
const betCtx = document.getElementById('bet-sizes-chart').getContext('2d');
const betSizes = results.map(r => r.bet);
// Destroy previous chart if it exists
if (betSizesChart) {
betSizesChart.destroy();
}
betSizesChart = new Chart(betCtx, {
type: 'bar',
data: {
labels: results.map((_, i) => i + 1),
datasets: [{
label: 'Bet Size',
data: betSizes,
backgroundColor: 'rgba(99, 102, 241, 0.7)',
borderColor: 'rgba(99, 102, 241, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return 'Bet: ' + formatCurrency(context.raw);
}
}
}
},
scales: {
x: {
display: false,
title: {
display: true,
text: 'Round'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
y: {
title: {
display: true,
text: 'Bet Amount'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
callback: function(value) {
return formatCurrency(value);
}
}
}
}
}
});
}
function showRecentRounds(rounds) {
const container = document.getElementById('recent-rounds');
container.innerHTML = '';
for (const round of rounds) {
const row = document.createElement('tr');
row.className = 'border-b border-gray-700';
const roundCell = document.createElement('td');
roundCell.className = 'py-2 px-4';
roundCell.textContent = round.round;
const betCell = document.createElement('td');
betCell.className = 'py-2 px-4';
betCell.textContent = formatCurrency(round.bet);
const resultCell = document.createElement('td');
resultCell.className = 'py-2 px-4';
resultCell.textContent = round.result;
if (round.result === 'WIN') {
resultCell.classList.add('text-green-500');
} else if (round.result === 'LOSS') {
resultCell.classList.add('text-red-500');
} else {
resultCell.classList.add('text-yellow-500');
}
const payoutCell = document.createElement('td');
payoutCell.className = 'py-2 px-4';
payoutCell.textContent = round.result === 'WIN' ? formatCurrency(round.payout) : '-';
const bankrollCell = document.createElement('td');
bankrollCell.className = 'py-2 px-4';
bankrollCell.textContent = formatCurrency(round.bankroll);
const verifyCell = document.createElement('td');
verifyCell.className = 'py-2 px-4';
const verifyBtn = document.createElement('button');
verifyBtn.className = 'text-blue-400 hover:text-blue-300 text-sm';
verifyBtn.textContent = 'Verify';
verifyBtn.addEventListener('click', () => verifyRoll(round));
verifyCell.appendChild(verifyBtn);
row.appendChild(roundCell);
row.appendChild(betCell);
row.appendChild(resultCell);
row.appendChild(payoutCell);
row.appendChild(bankrollCell);
row.appendChild(verifyCell);
container.appendChild(row);
}
}
function verifyRoll(round) {
alert(`Verification for round ${round.round}:\n\n` +
`Server Seed: ${round.serverSeed}\n` +
`Client Seed: ${round.clientSeed}\n` +
`Nonce: ${round.nonce}\n\n` +
`Hash: ${generateHash(round.serverSeed, round.clientSeed, round.nonce)}\n` +
`Result: ${round.result}\n` +
`Payout: ${round.payout}`);
}
// Export to CSV
function exportToCSV() {
if (!simulationResults.results || simulationResults.results.length === 0) {
alert('No simulation results to export');
return;
}
// Prepare data
const data = simulationResults.results.map(round => ({
Round: round.round,
Bet: round.bet,
Result: round.result,
Payout: round.payout,
Bankroll: round.bankroll,
'Server Seed': round.serverSeed,
'Client Seed': round.clientSeed,
Nonce: round.nonce
}));
// Add summary info
data.unshift({
Round: 'Summary',
Bet: '',
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
data.unshift({
Round: 'Starting Bankroll',
Bet: simulationResults.summary.startingAmount,
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
data.unshift({
Round: 'Ending Bankroll',
Bet: simulationResults.summary.endingAmount,
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
data.unshift({
Round: 'Profit/Loss',
Bet: simulationResults.summary.profit,
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
data.unshift({
Round: 'ROI',
Bet: simulationResults.summary.roi + '%',
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
data.unshift({
Round: 'Win Rate',
Bet: simulationResults.summary.winRate + '%',
Result: '',
Payout: '',
Bankroll: '',
'Server Seed': '',
'Client Seed': '',
Nonce: ''
});
// Convert to CSV
const csv = Papa.unparse(data);
// Download
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', `stake_simulation_${new Date().toISOString().slice(0, 10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Helper functions
function formatCurrency(value) {
return parseFloat(value).toFixed(8).replace(/\.?0+$/, '');
}
// Initialize the app
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=widichandra/tst12" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>