from django.conf import settings from django.shortcuts import render, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required from django.db.models import F, Count, Sum, Avg from decimal import Decimal, InvalidOperation import random from .models import GameSession, HighScore, PlayerStats # Game configuration LOCATIONS = ['Downtown', 'Uptown', 'Airport', 'University', 'Suburbs', 'Docks'] PRODUCTS = { 'Espresso': {'base': 10, 'variance': 5, 'event_mult': 3}, 'Latte': {'base': 25, 'variance': 10, 'event_mult': 2}, 'Cold Brew': {'base': 40, 'variance': 20, 'event_mult': 2.5}, 'Jamaican': {'base': 100, 'variance': 50, 'event_mult': 3}, 'Ethiopian': {'base': 200, 'variance': 100, 'event_mult': 4}, 'Kopi Luwak': {'base': 500, 'variance': 300, 'event_mult': 5}, } MAX_DAYS = getattr(settings, "MAX_DAYS", 30) INTEREST_RATE = getattr(settings, "INTEREST_RATE", Decimal('0.05')) def get_or_create_game(request): """Get or create game session""" if request.user.is_authenticated: # For logged-in users, get their active game or create new one game, created = GameSession.objects.get_or_create( user=request.user, is_active=True, defaults={'session_key': request.session.session_key} ) if created: # Create or update player stats stats, _ = PlayerStats.objects.get_or_create(user=request.user) else: # For anonymous users, use session-based games session_key = request.session.session_key if not session_key: request.session.create() session_key = request.session.session_key game, created = GameSession.objects.get_or_create( session_key=session_key, user=None, is_active=True ) return game def generate_prices(request=None): """Generate random prices for products""" prices = {} for product, config in PRODUCTS.items(): base = config['base'] variance = config['variance'] price = base + random.randint(-variance, variance) prices[product] = max(1, price) # Minimum price of $1 # Random events that affect prices event_message = None if random.random() < 0.1: # 10% chance of price event product = random.choice(list(PRODUCTS.keys())) if random.random() < 0.5: # Price surge prices[product] *= PRODUCTS[product]['event_mult'] event_message = f"☕ {product} prices are SOARING due to shortage!" else: # Price crash prices[product] //= 2 event_message = f"📉 {product} prices CRASHED due to oversupply!" return prices, event_message def home(request): """Main game view""" game = get_or_create_game(request) # Check if game is over if game.day > MAX_DAYS: return redirect('game_over') # Generate current prices prices = request.session.get('current_prices') if not prices or request.session.get('current_day') != game.day: prices, event_message = generate_prices() request.session['current_prices'] = prices request.session['current_day'] = game.day if event_message: messages.info(request, event_message) # Calculate inventory value and total items inventory = game.get_inventory() inventory_value = sum( inventory.get(product, 0) * prices[product] for product in PRODUCTS.keys() ) total_items = sum(inventory.values()) # Random events if random.random() < 0.05 and game.cash > 1000: # 5% chance stolen = random.randint(100, min(int(game.cash), 1000)) game.cash -= stolen game.save() messages.warning(request, f"💸 You got mugged! Lost ${stolen}") # Prepare products with inventory for template products_data = [] for product in PRODUCTS.keys(): products_data.append({ 'name': product, 'price': prices.get(product, 0), 'owned': inventory.get(product, 0) }) context = { 'game': game, 'locations': LOCATIONS, 'products_data': products_data, 'inventory': inventory, 'inventory_value': inventory_value, 'total_items': total_items, 'capacity_left': game.capacity - total_items, 'net_worth': Decimal(game.cash) + Decimal(inventory_value) - Decimal(game.debt), 'interest': INTEREST_RATE * 100, } return render(request, 'game/home.html', context) def buy(request, product): """Handle buying products""" if request.method == 'POST': game = get_or_create_game(request) prices = request.session.get('current_prices', {}) try: quantity = int(request.POST.get('quantity', 0)) if quantity <= 0: raise ValueError("Invalid quantity") price = prices.get(product, 0) total_cost = Decimal(price * quantity) inventory = game.get_inventory() current_items = sum(inventory.values()) if current_items + quantity > game.capacity: messages.error(request, f"Not enough capacity! Space for {game.capacity - current_items} items.") elif total_cost > game.cash: messages.error(request, f"Not enough cash! You need ${total_cost:.2f}") else: game.cash -= total_cost inventory[product] = inventory.get(product, 0) + quantity game.set_inventory(inventory) game.save() messages.success(request, f"Bought {quantity} {product} for ${total_cost:.2f}") except (ValueError, TypeError): messages.error(request, "Invalid quantity!") return redirect('home') def sell(request, product): """Handle selling products""" if request.method == 'POST': game = get_or_create_game(request) prices = request.session.get('current_prices', {}) try: quantity = int(request.POST.get('quantity', 0)) if quantity <= 0: raise ValueError("Invalid quantity") inventory = game.get_inventory() if inventory.get(product, 0) < quantity: messages.error(request, f"You don't have {quantity} {product}!") else: price = prices.get(product, 0) total_sale = Decimal(price * quantity) game.cash += total_sale inventory[product] -= quantity if inventory[product] == 0: del inventory[product] game.set_inventory(inventory) game.save() messages.success(request, f"Sold {quantity} {product} for ${total_sale:.2f}") except (ValueError, TypeError): messages.error(request, "Invalid quantity!") return redirect('home') def travel(request): """Travel to new location (advances day)""" if request.method == 'POST': game = get_or_create_game(request) new_location = request.POST.get('location') if new_location in LOCATIONS: game.location = new_location game.day += 1 game.debt = game.debt * Decimal(1 + INTEREST_RATE) # Apply interest game.save() # Clear prices for new day request.session['current_prices'] = None messages.info(request, f"📍 Traveled to {new_location}. Day {game.day}/{MAX_DAYS}") # Random events during travel if random.random() < 0.1: bonus = random.randint(50, 500) game.cash += bonus game.save() messages.success(request, f"🎁 Found ${bonus} on the subway!") return redirect('home') def pay_debt(request): """Pay off debt""" if request.method == 'POST': game = get_or_create_game(request) try: amount = Decimal(request.POST.get('amount', 0)) if amount <= 0: raise ValueError("Invalid amount") if amount > game.cash: messages.error(request, "You don't have that much cash!") elif amount > game.debt: messages.error(request, "You can't pay more than you owe!") else: game.cash -= amount game.debt -= amount game.save() messages.success(request, f"Paid ${amount:.2f} toward debt") except (ValueError, TypeError, InvalidOperation): messages.error(request, "Invalid amount!") return redirect('home') def new_game(request): """Start a new game""" game = get_or_create_game(request) # Save high score and mark game as completed if applicable if game.day > 1: # Only if a game was actually played inventory = game.get_inventory() prices = request.session.get('current_prices', {}) if prices: inventory_value = sum( inventory.get(product, 0) * prices.get(product, 0) for product in PRODUCTS.keys() ) else: inventory_value = 0 net_worth = game.cash + inventory_value - game.debt game.final_score = net_worth game.completed = True game.is_active = False game.save() # Update player stats if logged in if request.user.is_authenticated: stats, _ = PlayerStats.objects.get_or_create(user=request.user) stats.games_played += 1 if net_worth > 0: stats.games_won += 1 if net_worth > stats.best_score: stats.best_score = net_worth stats.total_profit += net_worth stats.save() # Create new game if request.user.is_authenticated: new_game = GameSession.objects.create( user=request.user, session_key=request.session.session_key, is_active=True ) else: new_game = GameSession.objects.create( session_key=request.session.session_key, is_active=True ) # Clear session data request.session['current_prices'] = None request.session['current_day'] = 1 messages.info(request, "🆕 New game started!") return redirect('home') def game_over(request): """Game over view""" game = get_or_create_game(request) inventory = game.get_inventory() prices, _ = generate_prices() # Don't need event message here if request.session.get('current_prices'): prices = request.session.get('current_prices') inventory_value = sum( inventory.get(product, 0) * prices[product] for product in PRODUCTS.keys() ) final_score = game.cash + inventory_value - game.debt if request.method == 'POST': name = request.POST.get('name', 'Anonymous') # Save high score high_score = HighScore.objects.create( player_name=name, user=request.user if request.user.is_authenticated else None, score=final_score, days_played=min(game.day, MAX_DAYS) ) # Mark game as completed game.final_score = final_score game.completed = True game.is_active = False game.save() # Update player stats if logged in if request.user.is_authenticated: stats, _ = PlayerStats.objects.get_or_create(user=request.user) stats.games_played += 1 if final_score > 0: stats.games_won += 1 if final_score > stats.best_score: stats.best_score = final_score stats.total_profit += final_score stats.save() return redirect('high_scores') context = { 'game': game, 'final_score': final_score, 'inventory_value': inventory_value, } return render(request, 'game/game_over.html', context) def high_scores(request): """Display high scores""" scores = HighScore.objects.all()[:10] user_best = None if request.user.is_authenticated: user_scores = HighScore.objects.filter(user=request.user) if user_scores.exists(): user_best = user_scores.first() return render(request, 'game/high_scores.html', { 'scores': scores, 'user_best': user_best }) @login_required def profile(request): """Display user profile and stats""" stats, created = PlayerStats.objects.get_or_create(user=request.user) # Get user's game history games = GameSession.objects.filter( user=request.user, completed=True ).order_by('-created_at')[:10] # Get user's high scores high_scores = HighScore.objects.filter(user=request.user).order_by('-score')[:5] # Calculate additional stats win_rate = (stats.games_won / stats.games_played * 100) if stats.games_played > 0 else 0 avg_score = stats.total_profit / stats.games_played if stats.games_played > 0 else 0 context = { 'stats': stats, 'games': games, 'high_scores': high_scores, 'win_rate': win_rate, 'avg_score': avg_score, } return render(request, 'game/profile.html', context) @login_required def my_games(request): """Display all user's games""" games = GameSession.objects.filter(user=request.user).order_by('-created_at') active_games = games.filter(is_active=True) completed_games = games.filter(completed=True) return render(request, 'game/my_games.html', { 'active_games': active_games, 'completed_games': completed_games, })