views.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. from django.conf import settings
  2. from django.shortcuts import render, redirect
  3. from django.contrib import messages
  4. from django.contrib.auth.decorators import login_required
  5. from django.db.models import F, Count, Sum, Avg
  6. from decimal import Decimal, InvalidOperation
  7. import random
  8. from .models import GameSession, HighScore, PlayerStats
  9. # Game configuration
  10. LOCATIONS = ['Downtown', 'Uptown', 'Airport', 'University', 'Suburbs', 'Docks']
  11. PRODUCTS = {
  12. 'Espresso': {'base': 10, 'variance': 5, 'event_mult': 3},
  13. 'Latte': {'base': 25, 'variance': 10, 'event_mult': 2},
  14. 'Cold Brew': {'base': 40, 'variance': 20, 'event_mult': 2.5},
  15. 'Jamaican': {'base': 100, 'variance': 50, 'event_mult': 3},
  16. 'Ethiopian': {'base': 200, 'variance': 100, 'event_mult': 4},
  17. 'Kopi Luwak': {'base': 500, 'variance': 300, 'event_mult': 5},
  18. }
  19. MAX_DAYS = getattr(settings, "MAX_DAYS", 30)
  20. INTEREST_RATE = getattr(settings, "INTEREST_RATE", Decimal('0.05'))
  21. def get_or_create_game(request):
  22. """Get or create game session"""
  23. if request.user.is_authenticated:
  24. # For logged-in users, get their active game or create new one
  25. game, created = GameSession.objects.get_or_create(
  26. user=request.user,
  27. is_active=True,
  28. defaults={'session_key': request.session.session_key}
  29. )
  30. if created:
  31. # Create or update player stats
  32. stats, _ = PlayerStats.objects.get_or_create(user=request.user)
  33. else:
  34. # For anonymous users, use session-based games
  35. session_key = request.session.session_key
  36. if not session_key:
  37. request.session.create()
  38. session_key = request.session.session_key
  39. game, created = GameSession.objects.get_or_create(
  40. session_key=session_key,
  41. user=None,
  42. is_active=True
  43. )
  44. return game
  45. def generate_prices(request=None):
  46. """Generate random prices for products"""
  47. prices = {}
  48. for product, config in PRODUCTS.items():
  49. base = config['base']
  50. variance = config['variance']
  51. price = base + random.randint(-variance, variance)
  52. prices[product] = max(1, price) # Minimum price of $1
  53. # Random events that affect prices
  54. event_message = None
  55. if random.random() < 0.1: # 10% chance of price event
  56. product = random.choice(list(PRODUCTS.keys()))
  57. if random.random() < 0.5:
  58. # Price surge
  59. prices[product] *= PRODUCTS[product]['event_mult']
  60. event_message = f"☕ {product} prices are SOARING due to shortage!"
  61. else:
  62. # Price crash
  63. prices[product] //= 2
  64. event_message = f"📉 {product} prices CRASHED due to oversupply!"
  65. return prices, event_message
  66. def home(request):
  67. """Main game view"""
  68. game = get_or_create_game(request)
  69. # Check if game is over
  70. if game.day > MAX_DAYS:
  71. return redirect('game_over')
  72. # Generate current prices
  73. prices = request.session.get('current_prices')
  74. if not prices or request.session.get('current_day') != game.day:
  75. prices, event_message = generate_prices()
  76. request.session['current_prices'] = prices
  77. request.session['current_day'] = game.day
  78. if event_message:
  79. messages.info(request, event_message)
  80. # Calculate inventory value and total items
  81. inventory = game.get_inventory()
  82. inventory_value = sum(
  83. inventory.get(product, 0) * prices[product]
  84. for product in PRODUCTS.keys()
  85. )
  86. total_items = sum(inventory.values())
  87. # Random events
  88. if random.random() < 0.05 and game.cash > 1000: # 5% chance
  89. stolen = random.randint(100, min(int(game.cash), 1000))
  90. game.cash -= stolen
  91. game.save()
  92. messages.warning(request, f"💸 You got mugged! Lost ${stolen}")
  93. # Prepare products with inventory for template
  94. products_data = []
  95. for product in PRODUCTS.keys():
  96. products_data.append({
  97. 'name': product,
  98. 'price': prices.get(product, 0),
  99. 'owned': inventory.get(product, 0)
  100. })
  101. context = {
  102. 'game': game,
  103. 'locations': LOCATIONS,
  104. 'products_data': products_data,
  105. 'inventory': inventory,
  106. 'inventory_value': inventory_value,
  107. 'total_items': total_items,
  108. 'capacity_left': game.capacity - total_items,
  109. 'net_worth': Decimal(game.cash) + Decimal(inventory_value) - Decimal(game.debt),
  110. 'interest': INTEREST_RATE * 100,
  111. }
  112. return render(request, 'game/home.html', context)
  113. def buy(request, product):
  114. """Handle buying products"""
  115. if request.method == 'POST':
  116. game = get_or_create_game(request)
  117. prices = request.session.get('current_prices', {})
  118. try:
  119. quantity = int(request.POST.get('quantity', 0))
  120. if quantity <= 0:
  121. raise ValueError("Invalid quantity")
  122. price = prices.get(product, 0)
  123. total_cost = Decimal(price * quantity)
  124. inventory = game.get_inventory()
  125. current_items = sum(inventory.values())
  126. if current_items + quantity > game.capacity:
  127. messages.error(request, f"Not enough capacity! Space for {game.capacity - current_items} items.")
  128. elif total_cost > game.cash:
  129. messages.error(request, f"Not enough cash! You need ${total_cost:.2f}")
  130. else:
  131. game.cash -= total_cost
  132. inventory[product] = inventory.get(product, 0) + quantity
  133. game.set_inventory(inventory)
  134. game.save()
  135. messages.success(request, f"Bought {quantity} {product} for ${total_cost:.2f}")
  136. except (ValueError, TypeError):
  137. messages.error(request, "Invalid quantity!")
  138. return redirect('home')
  139. def sell(request, product):
  140. """Handle selling products"""
  141. if request.method == 'POST':
  142. game = get_or_create_game(request)
  143. prices = request.session.get('current_prices', {})
  144. try:
  145. quantity = int(request.POST.get('quantity', 0))
  146. if quantity <= 0:
  147. raise ValueError("Invalid quantity")
  148. inventory = game.get_inventory()
  149. if inventory.get(product, 0) < quantity:
  150. messages.error(request, f"You don't have {quantity} {product}!")
  151. else:
  152. price = prices.get(product, 0)
  153. total_sale = Decimal(price * quantity)
  154. game.cash += total_sale
  155. inventory[product] -= quantity
  156. if inventory[product] == 0:
  157. del inventory[product]
  158. game.set_inventory(inventory)
  159. game.save()
  160. messages.success(request, f"Sold {quantity} {product} for ${total_sale:.2f}")
  161. except (ValueError, TypeError):
  162. messages.error(request, "Invalid quantity!")
  163. return redirect('home')
  164. def travel(request):
  165. """Travel to new location (advances day)"""
  166. if request.method == 'POST':
  167. game = get_or_create_game(request)
  168. new_location = request.POST.get('location')
  169. if new_location in LOCATIONS:
  170. game.location = new_location
  171. game.day += 1
  172. game.debt = game.debt * Decimal(1 + INTEREST_RATE) # Apply interest
  173. game.save()
  174. # Clear prices for new day
  175. request.session['current_prices'] = None
  176. messages.info(request, f"📍 Traveled to {new_location}. Day {game.day}/{MAX_DAYS}")
  177. # Random events during travel
  178. if random.random() < 0.1:
  179. bonus = random.randint(50, 500)
  180. game.cash += bonus
  181. game.save()
  182. messages.success(request, f"🎁 Found ${bonus} on the subway!")
  183. return redirect('home')
  184. def pay_debt(request):
  185. """Pay off debt"""
  186. if request.method == 'POST':
  187. game = get_or_create_game(request)
  188. try:
  189. amount = Decimal(request.POST.get('amount', 0))
  190. if amount <= 0:
  191. raise ValueError("Invalid amount")
  192. if amount > game.cash:
  193. messages.error(request, "You don't have that much cash!")
  194. elif amount > game.debt:
  195. messages.error(request, "You can't pay more than you owe!")
  196. else:
  197. game.cash -= amount
  198. game.debt -= amount
  199. game.save()
  200. messages.success(request, f"Paid ${amount:.2f} toward debt")
  201. except (ValueError, TypeError, InvalidOperation):
  202. messages.error(request, "Invalid amount!")
  203. return redirect('home')
  204. def new_game(request):
  205. """Start a new game"""
  206. game = get_or_create_game(request)
  207. # Save high score and mark game as completed if applicable
  208. if game.day > 1: # Only if a game was actually played
  209. inventory = game.get_inventory()
  210. prices = request.session.get('current_prices', {})
  211. if prices:
  212. inventory_value = sum(
  213. inventory.get(product, 0) * prices.get(product, 0)
  214. for product in PRODUCTS.keys()
  215. )
  216. else:
  217. inventory_value = 0
  218. net_worth = game.cash + inventory_value - game.debt
  219. game.final_score = net_worth
  220. game.completed = True
  221. game.is_active = False
  222. game.save()
  223. # Update player stats if logged in
  224. if request.user.is_authenticated:
  225. stats, _ = PlayerStats.objects.get_or_create(user=request.user)
  226. stats.games_played += 1
  227. if net_worth > 0:
  228. stats.games_won += 1
  229. if net_worth > stats.best_score:
  230. stats.best_score = net_worth
  231. stats.total_profit += net_worth
  232. stats.save()
  233. # Create new game
  234. if request.user.is_authenticated:
  235. new_game = GameSession.objects.create(
  236. user=request.user,
  237. session_key=request.session.session_key,
  238. is_active=True
  239. )
  240. else:
  241. new_game = GameSession.objects.create(
  242. session_key=request.session.session_key,
  243. is_active=True
  244. )
  245. # Clear session data
  246. request.session['current_prices'] = None
  247. request.session['current_day'] = 1
  248. messages.info(request, "🆕 New game started!")
  249. return redirect('home')
  250. def game_over(request):
  251. """Game over view"""
  252. game = get_or_create_game(request)
  253. inventory = game.get_inventory()
  254. prices, _ = generate_prices() # Don't need event message here
  255. if request.session.get('current_prices'):
  256. prices = request.session.get('current_prices')
  257. inventory_value = sum(
  258. inventory.get(product, 0) * prices[product]
  259. for product in PRODUCTS.keys()
  260. )
  261. final_score = game.cash + inventory_value - game.debt
  262. if request.method == 'POST':
  263. name = request.POST.get('name', 'Anonymous')
  264. # Save high score
  265. high_score = HighScore.objects.create(
  266. player_name=name,
  267. user=request.user if request.user.is_authenticated else None,
  268. score=final_score,
  269. days_played=min(game.day, MAX_DAYS)
  270. )
  271. # Mark game as completed
  272. game.final_score = final_score
  273. game.completed = True
  274. game.is_active = False
  275. game.save()
  276. # Update player stats if logged in
  277. if request.user.is_authenticated:
  278. stats, _ = PlayerStats.objects.get_or_create(user=request.user)
  279. stats.games_played += 1
  280. if final_score > 0:
  281. stats.games_won += 1
  282. if final_score > stats.best_score:
  283. stats.best_score = final_score
  284. stats.total_profit += final_score
  285. stats.save()
  286. return redirect('high_scores')
  287. context = {
  288. 'game': game,
  289. 'final_score': final_score,
  290. 'inventory_value': inventory_value,
  291. }
  292. return render(request, 'game/game_over.html', context)
  293. def high_scores(request):
  294. """Display high scores"""
  295. scores = HighScore.objects.all()[:10]
  296. user_best = None
  297. if request.user.is_authenticated:
  298. user_scores = HighScore.objects.filter(user=request.user)
  299. if user_scores.exists():
  300. user_best = user_scores.first()
  301. return render(request, 'game/high_scores.html', {
  302. 'scores': scores,
  303. 'user_best': user_best
  304. })
  305. @login_required
  306. def profile(request):
  307. """Display user profile and stats"""
  308. stats, created = PlayerStats.objects.get_or_create(user=request.user)
  309. # Get user's game history
  310. games = GameSession.objects.filter(
  311. user=request.user,
  312. completed=True
  313. ).order_by('-created_at')[:10]
  314. # Get user's high scores
  315. high_scores = HighScore.objects.filter(user=request.user).order_by('-score')[:5]
  316. # Calculate additional stats
  317. win_rate = (stats.games_won / stats.games_played * 100) if stats.games_played > 0 else 0
  318. avg_score = stats.total_profit / stats.games_played if stats.games_played > 0 else 0
  319. context = {
  320. 'stats': stats,
  321. 'games': games,
  322. 'high_scores': high_scores,
  323. 'win_rate': win_rate,
  324. 'avg_score': avg_score,
  325. }
  326. return render(request, 'game/profile.html', context)
  327. @login_required
  328. def my_games(request):
  329. """Display all user's games"""
  330. games = GameSession.objects.filter(user=request.user).order_by('-created_at')
  331. active_games = games.filter(is_active=True)
  332. completed_games = games.filter(completed=True)
  333. return render(request, 'game/my_games.html', {
  334. 'active_games': active_games,
  335. 'completed_games': completed_games,
  336. })