mirror of
https://git.astronand.dev/minecartchris/Stock-Game.git
synced 2026-06-17 05:49:41 -04:00
Add initial implementation of Stock Trading Game with CSS styling, HTML structure, and trade logging functionality
This commit is contained in:
325
Stock game/mainweb.py
Normal file
325
Stock game/mainweb.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user