mirror of
https://git.astronand.dev/minecartchris/Stock-Game.git
synced 2026-06-05 04:15:22 -04:00
322 lines
10 KiB
Python
322 lines
10 KiB
Python
from flask import Flask, render_template, request, jsonify, session
|
|
import random
|
|
import json
|
|
import os
|
|
from datetime import datetime
|
|
|
|
app = Flask(__name__)
|
|
|
|
# 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
|
|
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(-100, 100) 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(1, 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 'current' not in game_state:
|
|
return None
|
|
|
|
state = game_state['current']
|
|
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)
|
|
game_state['current'] = 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 'current' not in game_state:
|
|
return jsonify({'error': 'Game not started'}), 400
|
|
|
|
state = game_state['current']
|
|
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}"
|
|
|
|
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 'current' not in game_state:
|
|
return jsonify({'error': 'Game not started'}), 400
|
|
|
|
state = game_state['current']
|
|
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}"
|
|
|
|
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 'current' not in game_state:
|
|
return jsonify({'error': 'Game not started'}), 400
|
|
|
|
state = game_state['current']
|
|
|
|
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']}"
|
|
|
|
return jsonify(get_game_state_data())
|
|
|
|
@app.route('/api/end-game', methods=['GET'])
|
|
def end_game():
|
|
"""Get end game statistics"""
|
|
if 'current' not in game_state:
|
|
return jsonify({'error': 'Game not started'}), 400
|
|
|
|
state = game_state['current']
|
|
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 'current' not in game_state:
|
|
return jsonify({'error': 'Game not started'}), 400
|
|
|
|
data = request.json
|
|
player_name = data.get('name', 'Anonymous')
|
|
|
|
state = game_state['current']
|
|
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)
|