Spaces:
Running
Running
| <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> |