views.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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. 'max_days': MAX_DAYS,
  112. }
  113. return render(request, 'game/home.html', context)
  114. def buy(request, product):
  115. """Handle buying products"""
  116. if request.method == 'POST':
  117. game = get_or_create_game(request)
  118. prices = request.session.get('current_prices', {})
  119. try:
  120. quantity = int(request.POST.get('quantity', 0))
  121. if quantity <= 0:
  122. raise ValueError("Invalid quantity")
  123. price = prices.get(product, 0)
  124. total_cost = Decimal(price * quantity)
  125. inventory = game.get_inventory()
  126. current_items = sum(inventory.values())
  127. if current_items + quantity > game.capacity:
  128. messages.error(request, f"Not enough capacity! Space for {game.capacity - current_items} items.")
  129. elif total_cost > game.cash:
  130. messages.error(request, f"Not enough cash! You need ${total_cost:.2f}")
  131. else:
  132. game.cash -= total_cost
  133. inventory[product] = inventory.get(product, 0) + quantity
  134. game.set_inventory(inventory)
  135. game.save()
  136. messages.success(request, f"Bought {quantity} {product} for ${total_cost:.2f}")
  137. except (ValueError, TypeError):
  138. messages.error(request, "Invalid quantity!")
  139. return redirect('home')
  140. def sell(request, product):
  141. """Handle selling products"""
  142. if request.method == 'POST':
  143. game = get_or_create_game(request)
  144. prices = request.session.get('current_prices', {})
  145. try:
  146. quantity = int(request.POST.get('quantity', 0))
  147. if quantity <= 0:
  148. raise ValueError("Invalid quantity")
  149. inventory = game.get_inventory()
  150. if inventory.get(product, 0) < quantity:
  151. messages.error(request, f"You don't have {quantity} {product}!")
  152. else:
  153. price = prices.get(product, 0)
  154. total_sale = Decimal(price * quantity)
  155. game.cash += total_sale
  156. inventory[product] -= quantity
  157. if inventory[product] == 0:
  158. del inventory[product]
  159. game.set_inventory(inventory)
  160. game.save()
  161. messages.success(request, f"Sold {quantity} {product} for ${total_sale:.2f}")
  162. except (ValueError, TypeError):
  163. messages.error(request, "Invalid quantity!")
  164. return redirect('home')
  165. def travel(request):
  166. """Travel to new location (advances day)"""
  167. if request.method == 'POST':
  168. game = get_or_create_game(request)
  169. new_location = request.POST.get('location')
  170. if new_location in LOCATIONS:
  171. game.location = new_location
  172. game.day += 1
  173. game.debt = game.debt * Decimal(1 + INTEREST_RATE) # Apply interest
  174. game.save()
  175. # Clear prices for new day
  176. request.session['current_prices'] = None
  177. messages.info(request, f"📍 Traveled to {new_location}. Day {game.day}/{MAX_DAYS}")
  178. # Random events during travel
  179. if random.random() < 0.1:
  180. bonus = random.randint(50, 500)
  181. game.cash += bonus
  182. game.save()
  183. messages.success(request, f"🎁 Found ${bonus} on the subway!")
  184. return redirect('home')
  185. def pay_debt(request):
  186. """Pay off debt"""
  187. if request.method == 'POST':
  188. game = get_or_create_game(request)
  189. try:
  190. amount = Decimal(request.POST.get('amount', 0))
  191. if amount <= 0:
  192. raise ValueError("Invalid amount")
  193. if amount > game.cash:
  194. messages.error(request, "You don't have that much cash!")
  195. elif amount > game.debt:
  196. messages.error(request, "You can't pay more than you owe!")
  197. else:
  198. game.cash -= amount
  199. game.debt -= amount
  200. game.save()
  201. messages.success(request, f"Paid ${amount:.2f} toward debt")
  202. except (ValueError, TypeError, InvalidOperation):
  203. messages.error(request, "Invalid amount!")
  204. return redirect('home')
  205. def new_game(request):
  206. """Start a new game"""
  207. game = get_or_create_game(request)
  208. # Save high score and mark game as completed if applicable
  209. if game.day > 1: # Only if a game was actually played
  210. inventory = game.get_inventory()
  211. prices = request.session.get('current_prices', {})
  212. if prices:
  213. inventory_value = sum(
  214. inventory.get(product, 0) * prices.get(product, 0)
  215. for product in PRODUCTS.keys()
  216. )
  217. else:
  218. inventory_value = 0
  219. net_worth = game.cash + inventory_value - game.debt
  220. game.final_score = net_worth
  221. game.completed = True
  222. game.is_active = False
  223. game.save()
  224. # Update player stats if logged in
  225. if request.user.is_authenticated:
  226. stats, _ = PlayerStats.objects.get_or_create(user=request.user)
  227. stats.games_played += 1
  228. if net_worth > 0:
  229. stats.games_won += 1
  230. if net_worth > stats.best_score:
  231. stats.best_score = net_worth
  232. stats.total_profit += net_worth
  233. stats.save()
  234. # Create new game
  235. if request.user.is_authenticated:
  236. new_game = GameSession.objects.create(
  237. user=request.user,
  238. session_key=request.session.session_key,
  239. is_active=True
  240. )
  241. else:
  242. new_game = GameSession.objects.create(
  243. session_key=request.session.session_key,
  244. is_active=True
  245. )
  246. # Clear session data
  247. request.session['current_prices'] = None
  248. request.session['current_day'] = 1
  249. messages.info(request, "🆕 New game started!")
  250. return redirect('home')
  251. def game_over(request):
  252. """Game over view"""
  253. game = get_or_create_game(request)
  254. inventory = game.get_inventory()
  255. prices, _ = generate_prices() # Don't need event message here
  256. if request.session.get('current_prices'):
  257. prices = request.session.get('current_prices')
  258. inventory_value = sum(
  259. inventory.get(product, 0) * prices[product]
  260. for product in PRODUCTS.keys()
  261. )
  262. final_score = game.cash + inventory_value - game.debt
  263. if request.method == 'POST':
  264. name = request.POST.get('name', 'Anonymous')
  265. # Save high score
  266. high_score = HighScore.objects.create(
  267. player_name=name,
  268. user=request.user if request.user.is_authenticated else None,
  269. score=final_score,
  270. days_played=min(game.day, MAX_DAYS)
  271. )
  272. # Mark game as completed
  273. game.final_score = final_score
  274. game.completed = True
  275. game.is_active = False
  276. game.save()
  277. # Update player stats if logged in
  278. if request.user.is_authenticated:
  279. stats, _ = PlayerStats.objects.get_or_create(user=request.user)
  280. stats.games_played += 1
  281. if final_score > 0:
  282. stats.games_won += 1
  283. if final_score > stats.best_score:
  284. stats.best_score = final_score
  285. stats.total_profit += final_score
  286. stats.save()
  287. return redirect('high_scores')
  288. context = {
  289. 'game': game,
  290. 'final_score': final_score,
  291. 'inventory_value': inventory_value,
  292. }
  293. return render(request, 'game/game_over.html', context)
  294. def high_scores(request):
  295. """Display high scores"""
  296. scores = HighScore.objects.all()[:10]
  297. user_best = None
  298. if request.user.is_authenticated:
  299. user_scores = HighScore.objects.filter(user=request.user)
  300. if user_scores.exists():
  301. user_best = user_scores.first()
  302. return render(request, 'game/high_scores.html', {
  303. 'scores': scores,
  304. 'user_best': user_best
  305. })
  306. @login_required
  307. def profile(request):
  308. """Display user profile and stats"""
  309. stats, created = PlayerStats.objects.get_or_create(user=request.user)
  310. # Get user's game history
  311. games = GameSession.objects.filter(
  312. user=request.user,
  313. completed=True
  314. ).order_by('-created_at')[:10]
  315. # Get user's high scores
  316. high_scores = HighScore.objects.filter(user=request.user).order_by('-score')[:5]
  317. # Calculate additional stats
  318. win_rate = (stats.games_won / stats.games_played * 100) if stats.games_played > 0 else 0
  319. avg_score = stats.total_profit / stats.games_played if stats.games_played > 0 else 0
  320. context = {
  321. 'stats': stats,
  322. 'games': games,
  323. 'high_scores': high_scores,
  324. 'win_rate': win_rate,
  325. 'avg_score': avg_score,
  326. }
  327. return render(request, 'game/profile.html', context)
  328. @login_required
  329. def my_games(request):
  330. """Display all user's games"""
  331. games = GameSession.objects.filter(user=request.user).order_by('-created_at')
  332. active_games = games.filter(is_active=True)
  333. completed_games = games.filter(completed=True)
  334. return render(request, 'game/my_games.html', {
  335. 'active_games': active_games,
  336. 'completed_games': completed_games,
  337. 'max_days': MAX_DAYS,
  338. })