commit 860987938648a4f48f59ad230e11514498c62982 Author: minecartchris Date: Mon Dec 15 18:24:44 2025 -0600 add all files diff --git a/httpTest.py b/httpTest.py new file mode 100644 index 0000000..d9709dd --- /dev/null +++ b/httpTest.py @@ -0,0 +1,164 @@ +import requests +import time +import webbrowser + + +BASE_URL = 'https://stock.minecartchris.cc' +log_file = "tradeLog.txt" +dayRun = 100 + +webbrowser.get('firefox').open(BASE_URL) + +# Create a session object to maintain cookies/session across requests +session = requests.Session() + +def log(message): + """Print to console and write to file""" + print(message) + if log_file: + log_file.write(message + '\n') + log_file.flush() + + +def find_cheapest_stock(stocks): + cheapest = min(stocks, key=lambda s: s['price']) + return cheapest + + +def get_holding_amount(holdings, stock_id): + for h in holdings: + if h['id'] == stock_id: + return h['amount'] + return 0 + + +def wait_for_server(): + for i in range(30): + try: + session.get(f'{BASE_URL}/api/game-state', timeout=1) + return True + except: + log(f"Waiting for server... ({i+1}/10)") + return False + + +def main(): + global log_file + log_file = open('tradeLog.txt', 'w') + + if not wait_for_server(): + log("Server not available!") + log_file.close() + return + + response = session.post(f'{BASE_URL}/api/start', json={'days': dayRun}) + if response.status_code != 200: + log(f"Error starting game: {response.status_code} - {response.text}") + log_file.close() + return + + data = response.json() + log("Game started!") + log(f"Balance: ${data['balance']}") + + cheapest = find_cheapest_stock(data['stocks']) + log(f"\nCheapest stock: {cheapest['name']} at ${cheapest['price']}") + + shares_to_buy = data['balance'] // cheapest['price'] + if shares_to_buy > 0: + response = session.post(f'{BASE_URL}/api/buy', + json={ + 'stock_id': cheapest['id'], + 'amount': shares_to_buy + }) + if response.status_code != 200: + log(f"Error buying stock: {response.status_code} - {response.text}") + log_file.close() + return + + data = response.json() + log(f"Bought {shares_to_buy} shares of {cheapest['name']}") + #log(f"Balance: ${data['balance']}") + + current_stock_id = cheapest['id'] + + while data['daysleft'] > 0: + response = session.post(f'{BASE_URL}/api/next-day') + if response.status_code != 200: + log(f"Error on next day: {response.status_code} - {response.text}") + break + + data = response.json() + log(f"\n--- Day {data['day']} ---") + + new_cheapest = find_cheapest_stock(data['stocks']) + log(f"Cheapest stock now: {new_cheapest['name']} at ${new_cheapest['price']}") + + if new_cheapest['id'] != current_stock_id: + log(f"Different stock is cheaper! Switching...") + + current_holdings = get_holding_amount(data['holdings'], current_stock_id) + if current_holdings > 0: + response = session.post(f'{BASE_URL}/api/sell', + json={ + 'stock_id': current_stock_id, + 'amount': current_holdings + }) + if response.status_code != 200: + log(f"Error selling stock: {response.status_code} - {response.text}") + break + + data = response.json() + log(f"Sold {current_holdings} shares") + log(f"Balance: ${data['balance']}") + + shares_to_buy = data['balance'] // new_cheapest['price'] + if shares_to_buy > 0: + response = session.post(f'{BASE_URL}/api/buy', + json={ + 'stock_id': new_cheapest['id'], + 'amount': shares_to_buy + }) + if response.status_code != 200: + log(f"Error buying stock: {response.status_code} - {response.text}") + break + + data = response.json() + log(f"Bought {shares_to_buy} shares of {new_cheapest['name']}") + + current_stock_id = new_cheapest['id'] + else: + log("Same stock is still cheapest, holding position.") + + log(f"\n=== GAME OVER - SELLING ALL ===") + for h in data['holdings']: + if h['amount'] > 0: + response = session.post(f'{BASE_URL}/api/sell', + json={ + 'stock_id': h['id'], + 'amount': h['amount'] + }) + if response.status_code != 200: + log(f"Error selling: {response.status_code} - {response.text}") + continue + + data = response.json() + log(f"Sold {h['amount']} shares of {h['name']}") + + log(f"\n=== FINAL RESULTS ===") + log(f"Starting balance: $1000") + log(f"Ending balance: ${data['balance']}") + log(f"Profit: ${data['balance'] - 1000}") + response = session.post(f'{BASE_URL}/api/save-score', + json={ + 'name': 'AutoTrader', + }) + if response.status_code == 200: + log("Score saved successfully!") + else: + log(f"Error saving score: {response.status_code}") + + log_file.close() + + +main() diff --git a/leaderboard.json b/leaderboard.json new file mode 100644 index 0000000..f6193f4 --- /dev/null +++ b/leaderboard.json @@ -0,0 +1,42 @@ +[ + { + "name": "test", + "profit": 29242282, + "net_worth": 29243282, + "final_balance": 29243282, + "leaderboardDays": 100, + "timestamp": "2025-12-11 22:53:08" + }, + { + "name": "Nvidia", + "profit": 20420, + "net_worth": 21420, + "final_balance": 21420, + "leaderboardDays": 10, + "timestamp": "2025-12-13 15:30:26" + }, + { + "name": "test", + "profit": 1680, + "net_worth": 2680, + "final_balance": 2680, + "leaderboardDays": 5, + "timestamp": "2025-12-13 15:25:16" + }, + { + "name": "test", + "profit": 0, + "net_worth": 1000, + "final_balance": 1000, + "leaderboardDays": 5, + "timestamp": "2025-12-13 15:24:32" + }, + { + "name": "easports", + "profit": 0, + "net_worth": 1000, + "final_balance": 1000, + "leaderboardDays": 5, + "timestamp": "2025-12-13 15:27:17" + } +] \ No newline at end of file diff --git a/mainweb.py b/mainweb.py new file mode 100644 index 0000000..36ceb72 --- /dev/null +++ b/mainweb.py @@ -0,0 +1,325 @@ +from flask import Flask, render_template, request, jsonify, session +import random +import json +import os +from datetime import datetime + +app = Flask(__name__) +app.secret_key = 'your-secret-key-change-this-in-production' + +# Leaderboard file path +LEADERBOARD_FILE = 'leaderboard.json' + +# List of all companies - EASY TO ADD/REMOVE COMPANIES HERE +COMPANIES = [ + {'id': 1, 'name': 'Kwik trip', 'key': 'kwiktrip'}, + {'id': 2, 'name': 'Apple computers', 'key': 'apple'}, + {'id': 3, 'name': 'Microsoft', 'key': 'microsoft'}, + {'id': 4, 'name': 'Walmart Super Store', 'key': 'walmart'}, + {'id': 5, 'name': 'Car company', 'key': 'car'}, + {'id': 6, 'name': 'Tesla', 'key': 'tesla'}, + {'id': 7, 'name': 'Amazon', 'key': 'amazon'}, + {'id': 8, 'name': 'Google', 'key': 'google'}, + {'id': 9, 'name': 'Netflix', 'key': 'netflix'}, + {'id': 10, 'name': 'Steam', 'key': 'steam',}, + {'id': 11, 'name': 'Nvidia', 'key': 'nvidia'} +] + +# Game state management - now per-client via session +# Removed: game_state = {} +leaderboard = [] + +def load_leaderboard(): + """Load leaderboard from file""" + global leaderboard + if os.path.exists(LEADERBOARD_FILE): + try: + with open(LEADERBOARD_FILE, 'r') as f: + leaderboard = json.load(f) + except (json.JSONDecodeError, IOError): + leaderboard = [] + else: + leaderboard = [] + +def save_leaderboard(): + """Save leaderboard to file""" + try: + with open(LEADERBOARD_FILE, 'w') as f: + json.dump(leaderboard, f, indent=4) + except IOError as e: + print(f"Error saving leaderboard: {e}") + +def init_game(days): + """Initialize a new game session""" + # Generate random offsets for initial prices for each company + price_offsets = {company['key']: random.randint(-1000, 1000) for company in COMPANIES} + + # Build state with dynamic company prices and history + state = { + 'balance': 1000, + 'day': 1, + 'daysleft': days, + 'leaderboardDays': days, + 'message': '', + 'priceHistory': {}, + } + + # Add stock prices and holdings for each company + for company in COMPANIES: + key = company['key'] + offset = price_offsets[key] + # Ensure initial price is at least $1 + initial_price = max(50, 100 + offset) + state[f'{key}StockPrice'] = initial_price + state[key] = 0 + state['priceHistory'][key] = [initial_price] + + return state + +def calculate_index(state): + """Calculate market index""" + prices = [state[f"{company['key']}StockPrice"] for company in COMPANIES] + return sum(prices) / len(prices) if prices else 0 + +def get_stock_list(state): + """Return list of stocks with prices""" + return [ + {'id': company['id'], 'name': company['name'], 'price': state[f"{company['key']}StockPrice"]} + for company in COMPANIES + ] + +def get_holdings(state): + """Return user's stock holdings""" + return [ + {'id': company['id'], 'name': company['name'], 'amount': state[company['key']]} + for company in COMPANIES + ] + +def get_game_state_data(): + """Get current game state data as dictionary""" + if 'game_state' not in session: + return None + + state = session['game_state'] + stocks = get_stock_list(state) + holdings = get_holdings(state) + + return { + 'balance': state['balance'], + 'day': state['day'], + 'daysleft': state['daysleft'], + 'index': round(calculate_index(state), 2), + 'stocks': stocks, + 'holdings': holdings, + 'message': state.get('message', ''), + 'priceHistory': state.get('priceHistory', {}) + } + +@app.route('/') +def home(): + """Home page - ask for number of days""" + return render_template('index.html') + +@app.route('/api/start', methods=['POST']) +def start_game(): + """Initialize a new game""" + data = request.json + days = int(data.get('days', 5)) + + state = init_game(days) + session['game_state'] = state + + return jsonify(get_game_state_data()) + +@app.route('/api/game-state', methods=['GET']) +def get_game_state(): + """Get current game state""" + state_data = get_game_state_data() + if state_data is None: + return jsonify({'error': 'Game not started'}), 400 + + return jsonify(state_data) + +@app.route('/api/buy', methods=['POST']) +def buy_stock(): + """Buy stocks""" + if 'game_state' not in session: + return jsonify({'error': 'Game not started'}), 400 + + state = session['game_state'] + data = request.json + + try: + stock_id = int(data['stock_id']) + amount = int(data['amount']) + + stocks = get_stock_list(state) + stock_price = stocks[stock_id - 1]['price'] + total_cost = amount * stock_price + + if state['balance'] < total_cost: + state['message'] = f"Insufficient funds. Cost: ${total_cost}, Balance: ${state['balance']}" + return jsonify({'error': state['message']}), 400 + + state['balance'] -= total_cost + + # Use dynamic COMPANIES list instead of hardcoded array + stock_key = COMPANIES[stock_id - 1]['key'] + state[stock_key] += amount + + state['message'] = f"Successfully bought {amount} shares of {stocks[stock_id - 1]['name']} for ${total_cost}" + + session['game_state'] = state + return jsonify(get_game_state_data()) + except (ValueError, IndexError, KeyError) as e: + return jsonify({'error': str(e)}), 400 + +@app.route('/api/sell', methods=['POST']) +def sell_stock(): + """Sell stocks""" + if 'game_state' not in session: + return jsonify({'error': 'Game not started'}), 400 + + state = session['game_state'] + data = request.json + + try: + stock_id = int(data['stock_id']) + amount = int(data['amount']) + + stocks = get_stock_list(state) + holdings = get_holdings(state) + + user_holdings = holdings[stock_id - 1]['amount'] + + if user_holdings < amount: + state['message'] = f"You don't own enough shares. You own: {user_holdings}" + return jsonify({'error': state['message']}), 400 + + stock_price = stocks[stock_id - 1]['price'] + total_payout = amount * stock_price + + state['balance'] += total_payout + + # Use dynamic COMPANIES list instead of hardcoded array + stock_key = COMPANIES[stock_id - 1]['key'] + state[stock_key] -= amount + + state['message'] = f"Successfully sold {amount} shares of {stocks[stock_id - 1]['name']} for ${total_payout}" + + session['game_state'] = state + return jsonify(get_game_state_data()) + except (ValueError, IndexError, KeyError) as e: + return jsonify({'error': str(e)}), 400 + +@app.route('/api/next-day', methods=['POST']) +def next_day(): + """Progress to next day""" + if 'game_state' not in session: + return jsonify({'error': 'Game not started'}), 400 + + state = session['game_state'] + + if state['daysleft'] <= 0: + return jsonify({'error': 'Game over!', 'game_over': True}), 400 + + state['day'] += 1 + state['daysleft'] -= 1 + + # Update stock prices dynamically for all companies + for company in COMPANIES: + key = company['key'] + price_key = f"{key}StockPrice" + new_price = state[price_key] + random.randint(-100, 100) + # Ensure price is at least $1 + state[price_key] = max(1, new_price) + state['priceHistory'][key].append(state[price_key]) + + # Market crash chance + if random.randint(0, 1000) == 555: + # Market crash - set all prices to $1 minimum + for company in COMPANIES: + key = company['key'] + crash_price = max(1, int(state[f"{key}StockPrice"] * 0.2)) # Drop to 20% of current price, but no lower than $1 + state[f"{key}StockPrice"] = crash_price + state['priceHistory'][key][-1] = crash_price + state['message'] = "Market crash! All stocks dropped significantly!" + else: + state['message'] = f"Moved to day {state['day']}" + + session['game_state'] = state + return jsonify(get_game_state_data()) + +@app.route('/api/end-game', methods=['GET']) +def end_game(): + """Get end game statistics""" + if 'game_state' not in session: + return jsonify({'error': 'Game not started'}), 400 + + state = session['game_state'] + stocks = get_stock_list(state) + + total_net_worth = state['balance'] + holdings = get_holdings(state) + + for i, holding in enumerate(holdings): + total_net_worth += holding['amount'] * stocks[i]['price'] + + profit = state['balance'] - 1000 + + return jsonify({ + 'starting_balance': 1000, + 'ending_balance': state['balance'], + 'total_net_worth': round(total_net_worth, 2), + 'profit': profit, + 'holdings': holdings, + 'stocks': stocks + }) + +@app.route('/api/save-score', methods=['POST']) +def save_score(): + """Save game score to leaderboard""" + if 'game_state' not in session: + return jsonify({'error': 'Game not started'}), 400 + + data = request.json + player_name = data.get('name', 'Anonymous') + + state = session['game_state'] + stocks = get_stock_list(state) + + total_net_worth = state['balance'] + holdings = get_holdings(state) + + for i, holding in enumerate(holdings): + total_net_worth += holding['amount'] * stocks[i]['price'] + + profit = state['balance'] - 1000 + + score_entry = { + 'name': player_name, + 'profit': profit, + 'net_worth': round(total_net_worth, 2), + 'final_balance': state['balance'], + 'leaderboardDays': state['leaderboardDays'], + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + leaderboard.append(score_entry) + leaderboard.sort(key=lambda x: x['profit'], reverse=True) + + # Save leaderboard to file + save_leaderboard() + + return jsonify({'success': True, 'rank': leaderboard.index(score_entry) + 1}) + +@app.route('/api/leaderboard', methods=['GET']) +def get_leaderboard(): + """Get leaderboard scores""" + return jsonify({'scores': leaderboard[:10]}) + +# Load leaderboard from file at startup +load_leaderboard() + +if __name__ == '__main__': + app.run(debug=False) diff --git a/static/game.js b/static/game.js new file mode 100644 index 0000000..1a48bd7 --- /dev/null +++ b/static/game.js @@ -0,0 +1,607 @@ +// 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 = ` +
#${rank}
+
+
${score.name}
+
Days: ${score.leaderboardDays} | Balance: $${formatNumber(score.final_balance)} | Net Worth: $${formatNumber(score.net_worth)} | ${score.timestamp}
+
+
${profitSign}$${formatNumber(score.profit)}
+ `; + + leaderboardList.appendChild(div); + }); +} + +function backToStart() { + document.getElementById('leaderboardScreen').classList.add('hidden'); + document.getElementById('startScreen').classList.remove('hidden'); +} + +function createEndPriceChart(priceHistory) { + // Define colors for stocks + const colors = ['#667eea', '#764ba2', '#4caf50', '#ff9800', '#f44336', '#e91e63', '#9c27b0']; + + // Get the history for all stocks + let history = []; + const stockKeys = Object.keys(priceHistory); + if (stockKeys.length > 0) { + history = priceHistory[stockKeys[0]] || []; + } + const days = Array.from({ length: history.length }, (_, i) => i + 1); + + // Create datasets for each stock dynamically + const datasets = stockKeys.map((key, index) => ({ + label: key.charAt(0).toUpperCase() + key.slice(1), + data: priceHistory[key] || [], + borderColor: colors[index % colors.length], + backgroundColor: colors[index % colors.length] + '15', + fill: false, + tension: 0.4, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + pointBackgroundColor: colors[index % colors.length], + pointBorderColor: '#fff', + pointBorderWidth: 1 + })); + + const ctx = document.getElementById('endPriceChart'); + if (!ctx) return; + + new Chart(ctx, { + type: 'line', + data: { + labels: days, + datasets: datasets + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + font: { size: 12 }, + padding: 15, + usePointStyle: true + } + } + }, + scales: { + y: { + beginAtZero: false, + ticks: { + callback: function(value) { + return '$' + formatNumber(value); + } + } + }, + x: { + title: { + display: true, + text: 'Day' + } + } + } + } + }); +} + +function updateCharts(priceHistory, stocks, currentDay) { + // Define colors for stocks + const colors = ['#667eea', '#764ba2', '#4caf50', '#ff9800', '#f44336', '#e91e63', '#9c27b0']; + + // Get the history for all stocks - use first stock to get day count + let history = []; + const priceHistoryKeys = Object.keys(priceHistory); + if (priceHistoryKeys.length > 0) { + history = priceHistory[priceHistoryKeys[0]] || []; + } + const days = Array.from({ length: history.length }, (_, i) => i + 1); + + // Create datasets for each stock dynamically + const datasets = priceHistoryKeys.map((key, index) => { + // Find the stock name from the stocks array that matches this key + const matchingStock = stocks.find(s => s.name.toLowerCase().replace(/\s+/g, '').replace(/[^a-z0-9]/g, '') === key.toLowerCase()); + const label = matchingStock ? matchingStock.name : key.charAt(0).toUpperCase() + key.slice(1); + + return { + label: label, + data: priceHistory[key] || [], + borderColor: colors[index % colors.length], + backgroundColor: colors[index % colors.length] + '15', + fill: false, + tension: 0.4, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + pointBackgroundColor: colors[index % colors.length], + pointBorderColor: '#fff', + pointBorderWidth: 1 + }; + }); + + const ctx = document.getElementById('priceChart'); + if (!ctx) return; + + if (stockCharts['combined']) { + stockCharts['combined'].data.labels = days; + stockCharts['combined'].data.datasets = datasets; + stockCharts['combined'].update(); + } else { + stockCharts['combined'] = new Chart(ctx, { + type: 'line', + data: { + labels: days, + datasets: datasets + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + font: { size: 12 }, + padding: 15, + usePointStyle: true + } + } + }, + scales: { + y: { + beginAtZero: false, + ticks: { + callback: function(value) { + return '$' + formatNumber(value); + } + } + }, + x: { + title: { + display: true, + text: 'Day' + } + } + } + } + }); + } +} + +// Initialize when page loads +document.addEventListener('DOMContentLoaded', () => { + populateStockSelect(); +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..d161c6e --- /dev/null +++ b/static/style.css @@ -0,0 +1,533 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.container { + background: white; + border-radius: 15px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + max-width: 1400px; + width: 100%; + padding: 40px; +} + +h1 { + text-align: center; + color: #333; + margin-bottom: 30px; + font-size: 2.5em; +} + +h2 { + color: #667eea; + margin-bottom: 20px; + text-align: center; +} + +h3 { + color: #555; + margin-bottom: 15px; + font-size: 1.1em; +} + +.section h3 { + text-align: center; +} + +.screen { + display: block; +} + +.screen.hidden { + display: none; +} + +/* Start Screen */ +#startScreen .form-group { + text-align: center; + margin-bottom: 20px; +} + +#startScreen label { + display: block; + margin-bottom: 10px; + font-size: 1.1em; + color: #333; +} + +#startScreen input { + padding: 10px 15px; + font-size: 1em; + border: 2px solid #667eea; + border-radius: 5px; + margin-right: 10px; + width: 100px; +} + +/* Game Header */ +.game-header { + margin-bottom: 30px; + padding: 20px; + background: #f8f9fa; + border-radius: 10px; +} + +.info-box { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + margin-bottom: 15px; +} + +.info-box h3 { + background: #667eea; + color: white; + padding: 15px; + border-radius: 8px; + margin-bottom: 0; + font-size: 1em; +} + +.section .info-box { + margin-bottom: 20px; +} + +.message { + padding: 15px; + background: #e8f5e9; + border-left: 4px solid #4caf50; + border-radius: 5px; + color: #2e7d32; + display: none; +} + +.message.show { + display: block; +} + +.message.error { + background: #ffebee; + border-left-color: #f44336; + color: #c62828; +} + +/* Game Content */ +.game-content { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 20px; +} + +.section { + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + border: 1px solid #e0e0e0; +} + +.stocks-list, +.holdings-list, +.companies-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.stock-item, +.holding-item, +.company-item { + background: white; + padding: 12px; + border-radius: 6px; + border-left: 4px solid #667eea; + display: flex; + justify-content: space-between; + align-items: center; +} + +.holding-item { + border-left-color: #764ba2; +} + +.company-item { + border-left-color: #2196F3; + padding: 15px; + gap: 20px; +} + +.company-info { + flex: 1; +} + +.company-name { + font-weight: bold; + color: #333; + font-size: 1.05em; + margin-bottom: 5px; +} + +.company-details { + display: flex; + gap: 20px; + font-size: 0.9em; + color: #666; +} + +.company-details span { + font-weight: 500; +} + +.company-value { + font-weight: bold; + color: #2196F3; + font-size: 1.1em; + min-width: 100px; + text-align: right; +} + +.stock-item span, +.holding-item span { + font-weight: bold; + color: #667eea; +} + +/* Form Groups */ +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + color: #333; + font-weight: bold; +} + +.form-group input, +.form-group select { + width: 100%; + padding: 10px; + border: 2px solid #e0e0e0; + border-radius: 5px; + font-size: 1em; + transition: border-color 0.3s; +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: #667eea; +} + +/* Buttons */ +button { + padding: 12px 20px; + border: none; + border-radius: 6px; + font-size: 1em; + cursor: pointer; + transition: all 0.3s; + font-weight: bold; +} + +button:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +#startScreen button { + background: #667eea; + color: white; +} + +#startScreen button:hover { + background: #764ba2; +} + +.button-group { + display: flex; + gap: 10px; +} + +.btn-buy { + background: #4caf50; + color: white; + flex: 1; +} + +.btn-buy:hover { + background: #45a049; +} + +.btn-sell { + background: #ff9800; + color: white; + flex: 1; +} + +.btn-sell:hover { + background: #e68900; +} + +.btn-buy-all { + background: #2196F3; + color: white; + flex: 1; +} + +.btn-buy-all:hover { + background: #0b7dda; +} + +.btn-sell-all { + background: #f57c00; + color: white; + flex: 1; +} + +.btn-sell-all:hover { + background: #e65100; +} + +.btn-next-day { + background: #667eea; + color: white; + width: 100%; + padding: 15px; + font-size: 1.1em; +} + +.btn-next-day:hover { + background: #764ba2; +} + +/* Charts Container */ +.charts-container { + display: grid; + grid-template-columns: 1fr; + gap: 20px; + margin-top: 30px; +} + +.chart-item { + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + border: 1px solid #e0e0e0; + min-height: 400px; + position: relative; +} + +.chart-item canvas { + max-height: 400px; +} + +/* End Screen */ +.end-stats { + text-align: center; + padding: 20px; + background: #f8f9fa; + border-radius: 10px; +} + +.end-stats p { + font-size: 1.1em; + margin: 10px 0; + color: #333; +} + +.end-stats .profit { + font-size: 1.3em; + font-weight: bold; + color: #4caf50; + margin-top: 20px; +} + +#finalHoldings { + text-align: left; + margin: 20px 0; + padding: 15px; + background: white; + border-radius: 6px; +} + +.holding-final { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; +} + +.holding-final:last-child { + border-bottom: none; +} + +#endScreen button { + background: #667eea; + color: white; + margin-top: 20px; + padding: 12px 30px; +} + +#endScreen button:hover { + background: #764ba2; +} + +.btn-leaderboard { + background: #9c27b0; + color: white; + width: 100%; + margin-top: 10px; +} + +.btn-leaderboard:hover { + background: #7b1fa2; +} + +.btn-save-score { + background: #667eea; + color: white; + width: 100%; +} + +.btn-save-score:hover { + background: #764ba2; +} + +.btn-back { + background: #667eea; + color: white; + width: 100%; + margin-top: 20px; + padding: 12px; +} + +.btn-back:hover { + background: #764ba2; +} + +/* Leaderboard Screen */ +.leaderboard-container { + background: #f8f9fa; + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + min-height: 300px; +} + +.leaderboard-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.leaderboard-entry { + background: white; + padding: 15px; + border-radius: 8px; + border-left: 4px solid #667eea; + display: flex; + justify-content: space-between; + align-items: center; +} + +.leaderboard-entry.rank-1 { + border-left-color: #ffd700; + background: #fffbf0; +} + +.leaderboard-entry.rank-2 { + border-left-color: #c0c0c0; + background: #f5f5f5; +} + +.leaderboard-entry.rank-3 { + border-left-color: #cd7f32; + background: #f9f5f0; +} + +.leaderboard-rank { + font-size: 1.5em; + font-weight: bold; + color: #667eea; + min-width: 40px; + text-align: center; +} + +.leaderboard-info { + flex: 1; + margin: 0 15px; +} + +.leaderboard-name { + font-weight: bold; + color: #333; + font-size: 1.1em; +} + +.leaderboard-stats { + font-size: 0.9em; + color: #666; + margin-top: 5px; +} + +.leaderboard-profit { + font-size: 1.2em; + font-weight: bold; + min-width: 120px; + text-align: right; +} + +.leaderboard-profit.positive { + color: #4caf50; +} + +.leaderboard-profit.negative { + color: #f44336; +} + +.empty-message { + text-align: center; + padding: 40px 20px; + color: #999; + font-size: 1.1em; +} + +/* Responsive */ +@media (max-width: 768px) { + .container { + padding: 20px; + } + + .game-content { + grid-template-columns: 1fr; + } + + .info-box { + grid-template-columns: 1fr; + } + + .charts-container { + grid-template-columns: 1fr; + } + + .chart-item { + min-height: 250px; + } + + h1 { + font-size: 1.8em; + } +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d853354 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,120 @@ + + + + + + Stock Trading Game + + + + + +
+

Stock Trading Game

+ + +
+

Welcome to the Stock Game!

+
+ + + +
+
+ +
+
+ + + + + + + + + +
+ + + diff --git a/tradeLog.txt b/tradeLog.txt new file mode 100644 index 0000000..22d14ab --- /dev/null +++ b/tradeLog.txt @@ -0,0 +1,495 @@ +Game started! +Balance: $1000 + +Cheapest stock: Kwik trip at $50 +Bought 20 shares of Kwik trip + +--- Day 2 --- +Cheapest stock now: Apple computers at $1 +Different stock is cheaper! Switching... +Sold 20 shares +Balance: $160 +Bought 160 shares of Apple computers + +--- Day 3 --- +Cheapest stock now: Microsoft at $1 +Different stock is cheaper! Switching... +Sold 160 shares +Balance: $10560 +Bought 10560 shares of Microsoft + +--- Day 4 --- +Cheapest stock now: Apple computers at $1 +Different stock is cheaper! Switching... +Sold 10560 shares +Balance: $348480 +Bought 348480 shares of Apple computers + +--- Day 5 --- +Cheapest stock now: Microsoft at $1 +Different stock is cheaper! Switching... +Sold 348480 shares +Balance: $28226880 +Bought 28226880 shares of Microsoft + +--- Day 6 --- +Cheapest stock now: Netflix at $1 +Different stock is cheaper! Switching... +Sold 28226880 shares +Balance: $649218240 +Bought 649218240 shares of Netflix + +--- Day 7 --- +Cheapest stock now: Microsoft at $1 +Different stock is cheaper! Switching... +Sold 649218240 shares +Balance: $649218240 +Bought 649218240 shares of Microsoft + +--- Day 8 --- +Cheapest stock now: Microsoft at $1 +Same stock is still cheapest, holding position. + +--- Day 9 --- +Cheapest stock now: Google at $1 +Different stock is cheaper! Switching... +Sold 649218240 shares +Balance: $43497622080 +Bought 43497622080 shares of Google + +--- Day 10 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 11 --- +Cheapest stock now: Google at $7 +Same stock is still cheapest, holding position. + +--- Day 12 --- +Cheapest stock now: Car company at $1 +Different stock is cheaper! Switching... +Sold 43497622080 shares +Balance: $3131828789760 +Bought 3131828789760 shares of Car company + +--- Day 13 --- +Cheapest stock now: Car company at $14 +Same stock is still cheapest, holding position. + +--- Day 14 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 15 --- +Cheapest stock now: Car company at $9 +Same stock is still cheapest, holding position. + +--- Day 16 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 17 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 18 --- +Cheapest stock now: Google at $38 +Different stock is cheaper! Switching... +Sold 3131828789760 shares +Balance: $169118754647040 +Bought 4450493543343 shares of Google + +--- Day 19 --- +Cheapest stock now: Google at $30 +Same stock is still cheapest, holding position. + +--- Day 20 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 21 --- +Cheapest stock now: Google at $56 +Same stock is still cheapest, holding position. + +--- Day 22 --- +Cheapest stock now: Microsoft at $12 +Different stock is cheaper! Switching... +Sold 4450493543343 shares +Balance: $431697873704277 +Bought 35974822808689 shares of Microsoft + +--- Day 23 --- +Cheapest stock now: Microsoft at $16 +Same stock is still cheapest, holding position. + +--- Day 24 --- +Cheapest stock now: Nvidia at $41 +Different stock is cheaper! Switching... +Sold 35974822808689 shares +Balance: $4173079445807933 +Bought 101782425507510 shares of Nvidia + +--- Day 25 --- +Cheapest stock now: Nvidia at $104 +Same stock is still cheapest, holding position. + +--- Day 26 --- +Cheapest stock now: Car company at $78 +Different stock is cheaper! Switching... +Sold 101782425507510 shares +Balance: $14453104422066443 +Bought 185296210539313 shares of Car company + +--- Day 27 --- +Cheapest stock now: Tesla at $63 +Different stock is cheaper! Switching... +Sold 185296210539313 shares +Balance: $17973732422313390 +Bought 285297340036720 shares of Tesla + +--- Day 28 --- +Cheapest stock now: Tesla at $35 +Same stock is still cheapest, holding position. + +--- Day 29 --- +Cheapest stock now: Car company at $32 +Different stock is cheaper! Switching... +Sold 285297340036720 shares +Balance: $27388544643525150 +Bought 855892020110160 shares of Car company + +--- Day 30 --- +Cheapest stock now: Car company at $5 +Same stock is still cheapest, holding position. + +--- Day 31 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 32 --- +Cheapest stock now: Tesla at $1 +Different stock is cheaper! Switching... +Sold 855892020110160 shares +Balance: $57344765347380750 +Bought 57344765347380750 shares of Tesla + +--- Day 33 --- +Cheapest stock now: Tesla at $1 +Same stock is still cheapest, holding position. + +--- Day 34 --- +Cheapest stock now: Tesla at $32 +Same stock is still cheapest, holding position. + +--- Day 35 --- +Cheapest stock now: Tesla at $1 +Same stock is still cheapest, holding position. + +--- Day 36 --- +Cheapest stock now: Tesla at $86 +Same stock is still cheapest, holding position. + +--- Day 37 --- +Cheapest stock now: Car company at $15 +Different stock is cheaper! Switching... +Sold 57344765347380750 shares +Balance: $6307924188211882500 +Bought 420528279214125500 shares of Car company + +--- Day 38 --- +Cheapest stock now: Car company at $6 +Same stock is still cheapest, holding position. + +--- Day 39 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 40 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 41 --- +Cheapest stock now: Car company at $26 +Same stock is still cheapest, holding position. + +--- Day 42 --- +Cheapest stock now: Car company at $114 +Same stock is still cheapest, holding position. + +--- Day 43 --- +Cheapest stock now: Netflix at $184 +Different stock is cheaper! Switching... +Sold 420528279214125500 shares +Balance: $85787768959681602000 +Bought 466237874780878271 shares of Netflix + +--- Day 44 --- +Cheapest stock now: Netflix at $100 +Same stock is still cheapest, holding position. + +--- Day 45 --- +Cheapest stock now: Netflix at $81 +Same stock is still cheapest, holding position. + +--- Day 46 --- +Cheapest stock now: Netflix at $1 +Same stock is still cheapest, holding position. + +--- Day 47 --- +Cheapest stock now: Car company at $74 +Different stock is cheaper! Switching... +Sold 466237874780878271 shares +Balance: $45691311728526070694 +Bought 617450158493595549 shares of Car company + +--- Day 48 --- +Cheapest stock now: Car company at $10 +Same stock is still cheapest, holding position. + +--- Day 49 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 50 --- +Cheapest stock now: Netflix at $1 +Different stock is cheaper! Switching... +Sold 617450158493595549 shares +Balance: $61745015849359554968 +Bought 61745015849359554968 shares of Netflix + +--- Day 51 --- +Cheapest stock now: Netflix at $1 +Same stock is still cheapest, holding position. + +--- Day 52 --- +Cheapest stock now: Car company at $1 +Different stock is cheaper! Switching... +Sold 61745015849359554968 shares +Balance: $61745015849359554968 +Bought 61745015849359554968 shares of Car company + +--- Day 53 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 54 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 55 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 56 --- +Cheapest stock now: Car company at $99 +Same stock is still cheapest, holding position. + +--- Day 57 --- +Cheapest stock now: Car company at $33 +Same stock is still cheapest, holding position. + +--- Day 58 --- +Cheapest stock now: Car company at $36 +Same stock is still cheapest, holding position. + +--- Day 59 --- +Cheapest stock now: Car company at $38 +Same stock is still cheapest, holding position. + +--- Day 60 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 61 --- +Cheapest stock now: Car company at $1 +Same stock is still cheapest, holding position. + +--- Day 62 --- +Cheapest stock now: Car company at $17 +Same stock is still cheapest, holding position. + +--- Day 63 --- +Cheapest stock now: Car company at $73 +Same stock is still cheapest, holding position. + +--- Day 64 --- +Cheapest stock now: Car company at $45 +Same stock is still cheapest, holding position. + +--- Day 65 --- +Cheapest stock now: Car company at $98 +Same stock is still cheapest, holding position. + +--- Day 66 --- +Cheapest stock now: Car company at $31 +Same stock is still cheapest, holding position. + +--- Day 67 --- +Cheapest stock now: Car company at $42 +Same stock is still cheapest, holding position. + +--- Day 68 --- +Cheapest stock now: Car company at $71 +Same stock is still cheapest, holding position. + +--- Day 69 --- +Cheapest stock now: Car company at $158 +Same stock is still cheapest, holding position. + +--- Day 70 --- +Cheapest stock now: Steam at $231 +Different stock is cheaper! Switching... +Sold 61745015849359554968 shares +Balance: $15374508946490529187032 +Bought 66556315785673286523 shares of Steam + +--- Day 71 --- +Cheapest stock now: Steam at $154 +Same stock is still cheapest, holding position. + +--- Day 72 --- +Cheapest stock now: Google at $177 +Different stock is cheaper! Switching... +Sold 66556315785673286523 shares +Balance: $15640734209633222333124 +Bought 88365729997927809791 shares of Google + +--- Day 73 --- +Cheapest stock now: Google at $251 +Same stock is still cheapest, holding position. + +--- Day 74 --- +Cheapest stock now: Google at $195 +Same stock is still cheapest, holding position. + +--- Day 75 --- +Cheapest stock now: Google at $217 +Same stock is still cheapest, holding position. + +--- Day 76 --- +Cheapest stock now: Google at $125 +Same stock is still cheapest, holding position. + +--- Day 77 --- +Cheapest stock now: Google at $52 +Same stock is still cheapest, holding position. + +--- Day 78 --- +Cheapest stock now: Google at $37 +Same stock is still cheapest, holding position. + +--- Day 79 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 80 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 81 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 82 --- +Cheapest stock now: Google at $34 +Same stock is still cheapest, holding position. + +--- Day 83 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 84 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 85 --- +Cheapest stock now: Google at $10 +Same stock is still cheapest, holding position. + +--- Day 86 --- +Cheapest stock now: Google at $44 +Same stock is still cheapest, holding position. + +--- Day 87 --- +Cheapest stock now: Google at $131 +Same stock is still cheapest, holding position. + +--- Day 88 --- +Cheapest stock now: Google at $139 +Same stock is still cheapest, holding position. + +--- Day 89 --- +Cheapest stock now: Steam at $180 +Different stock is cheaper! Switching... +Sold 88365729997927809791 shares +Balance: $19617192059539973773719 +Bought 108984400330777632076 shares of Steam + +--- Day 90 --- +Cheapest stock now: Google at $125 +Different stock is cheaper! Switching... +Sold 108984400330777632076 shares +Balance: $29098834888317627764331 +Bought 232790679106541022114 shares of Google + +--- Day 91 --- +Cheapest stock now: Google at $119 +Same stock is still cheapest, holding position. + +--- Day 92 --- +Cheapest stock now: Google at $138 +Same stock is still cheapest, holding position. + +--- Day 93 --- +Cheapest stock now: Tesla at $100 +Different stock is cheaper! Switching... +Sold 232790679106541022114 shares +Balance: $37479299336153104560435 +Bought 374792993361531045604 shares of Tesla + +--- Day 94 --- +Cheapest stock now: Google at $61 +Different stock is cheaper! Switching... +Sold 374792993361531045604 shares +Balance: $71210668738690898664795 +Bought 1167388012109686863357 shares of Google + +--- Day 95 --- +Cheapest stock now: Google at $1 +Same stock is still cheapest, holding position. + +--- Day 96 --- +Cheapest stock now: Google at $39 +Same stock is still cheapest, holding position. + +--- Day 97 --- +Cheapest stock now: Tesla at $78 +Different stock is cheaper! Switching... +Sold 1167388012109686863357 shares +Balance: $150593053562149605373071 +Bought 1930680173873712889398 shares of Tesla + +--- Day 98 --- +Cheapest stock now: Tesla at $49 +Same stock is still cheapest, holding position. + +--- Day 99 --- +Cheapest stock now: Tesla at $112 +Same stock is still cheapest, holding position. + +--- Day 100 --- +Cheapest stock now: Tesla at $13 +Same stock is still cheapest, holding position. + +--- Day 101 --- +Cheapest stock now: Tesla at $60 +Same stock is still cheapest, holding position. + +=== GAME OVER - SELLING ALL === +Sold 1930680173873712889398 shares of Tesla + +=== FINAL RESULTS === +Starting balance: $1000 +Ending balance: $115840810432422773363907 +Profit: $115840810432422773362907 +Score saved successfully!