// Game state let gameActive = false; let stockCharts = {}; // Format large numbers: add commas for thousands function formatNumber(value) { const num = Number(value); if (isNaN(num)) return value; if (Number.isInteger(num)) return num.toLocaleString(); return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } async function startGame() { const daysInput = document.getElementById('daysInput'); const days = parseInt(daysInput.value); if (!days || days < 1) { alert('Please enter a valid number of days'); return; } try { const response = await fetch('/api/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ days: days }) }); if (!response.ok) throw new Error('Failed to start game'); gameActive = true; document.getElementById('startScreen').classList.add('hidden'); document.getElementById('gameScreen').classList.remove('hidden'); document.getElementById('totalDays').textContent = days; await updateGameDisplay(); populateStockSelect(); } catch (error) { alert('Error starting game: ' + error.message); } } async function updateGameDisplay() { try { const response = await fetch('/api/game-state'); if (!response.ok) throw new Error('Failed to fetch game state'); const data = await response.json(); document.getElementById('balance').textContent = formatNumber(data.balance); document.getElementById('day').textContent = data.day; document.getElementById('index').textContent = formatNumber(data.index); displayStocks(data.stocks); displayHoldings(data.holdings); displayMessage(data.message); updateCharts(data.priceHistory, data.stocks, data.day); } catch (error) { console.error('Error updating game display:', error); } } function displayStocks(stocks) { const stocksList = document.getElementById('stocksList'); stocksList.innerHTML = ''; stocks.forEach(stock => { const div = document.createElement('div'); div.className = 'stock-item'; div.innerHTML = `${stock.name}$${formatNumber(stock.price)}`; stocksList.appendChild(div); }); } function displayHoldings(holdings) { const holdingsList = document.getElementById('holdingsList'); holdingsList.innerHTML = ''; holdings.forEach(holding => { const div = document.createElement('div'); div.className = 'holding-item'; div.innerHTML = `${holding.name}${holding.amount} shares`; holdingsList.appendChild(div); }); } function displayMessage(message) { const messageDiv = document.getElementById('message'); if (message) { messageDiv.textContent = message; messageDiv.classList.add('show'); messageDiv.classList.remove('error'); } else { messageDiv.classList.remove('show'); } } function populateStockSelect() { // Fetch the current game state to get the actual stocks fetch('/api/game-state') .then(response => response.json()) .then(data => { const stocks = data.stocks; const select = document.getElementById('stockSelect'); select.innerHTML = ''; stocks.forEach(stock => { const option = document.createElement('option'); option.value = stock.id; option.textContent = `${stock.name}`; select.appendChild(option); }); }) .catch(error => console.error('Error populating stock select:', error)); } async function buyStock() { if (!gameActive) return; const stockId = parseInt(document.getElementById('stockSelect').value); const amount = parseInt(document.getElementById('amountInput').value); if (!amount || amount < 1) { showError('Please enter a valid amount'); return; } try { const response = await fetch('/api/buy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stock_id: stockId, amount: amount }) }); const data = await response.json(); if (!response.ok) { showError(data.error); return; } document.getElementById('amountInput').value = '1'; await updateGameDisplay(); } catch (error) { showError('Error buying stock: ' + error.message); } } async function sellStock() { if (!gameActive) return; const stockId = parseInt(document.getElementById('stockSelect').value); const amount = parseInt(document.getElementById('amountInput').value); if (!amount || amount < 1) { showError('Please enter a valid amount'); return; } try { const response = await fetch('/api/sell', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stock_id: stockId, amount: amount }) }); const data = await response.json(); if (!response.ok) { showError(data.error); return; } document.getElementById('amountInput').value = '1'; await updateGameDisplay(); } catch (error) { showError('Error selling stock: ' + error.message); } } async function buyAll() { if (!gameActive) return; const stockId = parseInt(document.getElementById('stockSelect').value); try { const gameStateResponse = await fetch('/api/game-state'); const gameStateData = await gameStateResponse.json(); const stocks = gameStateData.stocks; const balance = gameStateData.balance; const stockPrice = stocks[stockId - 1]['price']; // Calculate maximum amount we can buy const maxAmount = Math.min(Math.floor(balance / stockPrice), 999999999999999999); if (maxAmount <= 0) { showError('Insufficient funds to buy any shares of this stock'); return; } try { const response = await fetch('/api/buy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stock_id: stockId, amount: maxAmount }) }); const data = await response.json(); if (!response.ok) { showError(data.error); return; } document.getElementById('amountInput').value = '1'; await updateGameDisplay(); } catch (error) { showError('Error buying stock: ' + error.message); } } catch (error) { showError('Error fetching game state: ' + error.message); } } async function sellAll() { if (!gameActive) return; const stockId = parseInt(document.getElementById('stockSelect').value); try { const gameStateResponse = await fetch('/api/game-state'); const gameStateData = await gameStateResponse.json(); const holdings = gameStateData.holdings; const amountToSell = Math.min(holdings[stockId - 1]['amount'], 999999999999999999); if (amountToSell <= 0) { showError('You do not own any shares of this stock'); return; } try { const response = await fetch('/api/sell', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stock_id: stockId, amount: amountToSell }) }); const data = await response.json(); if (!response.ok) { showError(data.error); return; } document.getElementById('amountInput').value = '1'; await updateGameDisplay(); } catch (error) { showError('Error selling stock: ' + error.message); } } catch (error) { showError('Error fetching game state: ' + error.message); } } async function nextDay() { if (!gameActive) return; try { const response = await fetch('/api/next-day', { method: 'POST' }); const data = await response.json(); if (data.game_over) { await endGame(); return; } await updateGameDisplay(); } catch (error) { showError('Error advancing to next day: ' + error.message); } } async function endGame() { try { const response = await fetch('/api/end-game'); if (!response.ok) throw new Error('Failed to fetch end game stats'); const data = await response.json(); document.getElementById('startBalance').textContent = formatNumber(data.starting_balance); document.getElementById('endBalance').textContent = formatNumber(data.ending_balance); document.getElementById('netWorth').textContent = formatNumber(data.total_net_worth); document.getElementById('profit').textContent = formatNumber(data.profit); // Color profit red or green const profitText = document.getElementById('profitText'); if (data.profit >= 0) { profitText.style.color = '#4caf50'; } else { profitText.style.color = '#f44336'; } // Display final holdings const finalHoldings = document.getElementById('finalHoldings'); finalHoldings.innerHTML = ''; data.holdings.forEach((holding, index) => { if (holding.amount > 0) { const div = document.createElement('div'); div.className = 'holding-final'; const stockPrice = Number(data.stocks[index].price); const value = holding.amount * stockPrice; div.innerHTML = ` ${holding.name}: ${holding.amount} shares Value: $${formatNumber(value)} `; finalHoldings.appendChild(div); } }); // Fetch current game state to get price history for the chart const gameStateResponse = await fetch('/api/game-state'); const gameStateData = await gameStateResponse.json(); // Display the final price chart if (gameStateData.priceHistory) { createEndPriceChart(gameStateData.priceHistory); } gameActive = false; document.getElementById('gameScreen').classList.add('hidden'); document.getElementById('endScreen').classList.remove('hidden'); } catch (error) { showError('Error getting game stats: ' + error.message); } } function showError(message) { const messageDiv = document.getElementById('message'); messageDiv.textContent = message; messageDiv.classList.add('show', 'error'); } async function saveScore() { const playerName = document.getElementById('playerName').value.trim(); if (!playerName) { alert('Please enter your name'); return; } try { const response = await fetch('/api/save-score', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: playerName }) }); const data = await response.json(); if (data.success) { alert(`Score saved! You ranked #${data.rank} on the leaderboard!`); // Clear the name input document.getElementById('playerName').value = ''; } } catch (error) { showError('Error saving score: ' + error.message); } } async function viewLeaderboard() { try { const response = await fetch('/api/leaderboard'); const data = await response.json(); document.getElementById('startScreen').classList.add('hidden'); document.getElementById('leaderboardScreen').classList.remove('hidden'); displayLeaderboard(data.scores); } catch (error) { showError('Error loading leaderboard: ' + error.message); } } function displayLeaderboard(scores) { const leaderboardList = document.getElementById('leaderboardList'); const emptyMessage = document.getElementById('emptyLeaderboard'); if (scores.length === 0) { leaderboardList.innerHTML = ''; emptyMessage.style.display = 'block'; return; } emptyMessage.style.display = 'none'; leaderboardList.innerHTML = ''; scores.forEach((score, index) => { const rank = index + 1; const div = document.createElement('div'); div.className = `leaderboard-entry rank-${rank}`; const profitClass = score.profit >= 0 ? 'positive' : 'negative'; const profitSign = score.profit >= 0 ? '+' : ''; div.innerHTML = `