diff --git a/.idea/PythonProject.iml b/.idea/PythonProject.iml index fb3ed39..a0a6873 100644 --- a/.idea/PythonProject.iml +++ b/.idea/PythonProject.iml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f092b8d..bb81cf8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 5f43230..bdacb6f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/.idea/pyProjectModel.xml b/.idea/pyProjectModel.xml new file mode 100644 index 0000000..4670f7b --- /dev/null +++ b/.idea/pyProjectModel.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..9b58030 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,7 @@ - - - - - + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1e57c3e..a02eed2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,108 +1,161 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + { + "lastFilter": { + "state": "OPEN", + "assignee": "Navamanidhassan" } -}]]> - + + { + "selectedUrlAndAccountId": { + "url": "https://github.com/Navamanidhassan/PythonProjects.git", + "accountId": "f5bfe0a0-b38f-495e-a15e-eaa598fce04d" } -}]]> - - - - +} + { + "associatedIndex": 8 +} + + + - - - - - - - - - - 1780992343813 - - - - - - - - - - +}]]> + + + + + + + + + + + + + 1780992343813 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Slot machine python project/main.py b/Slot machine python project/main.py index 2bbdf9f..f5d95ce 100644 --- a/Slot machine python project/main.py +++ b/Slot machine python project/main.py @@ -1,143 +1,384 @@ -import random - -MAX_LINES=3 -MAX_BET=100 -MIN_BET=1 - -ROWS=3 -COLS=3 - -symbol_count={ - 'A':2, - 'B':4, - 'C':6, - 'D':8 -} -symbol_value={ - 'A':5, - 'B':4, - 'C':3, - 'D':2 -} - -def check_winnings(columns,lines,bet,values): - winnings=0 - winning_lines=[] - for line in range(lines): - symbol=columns[0][line] - - for column in columns: - symbol_to_check=column[line] - - if symbol_to_check!=symbol: - break - else: - winnings+=values[symbol]*bet - winning_lines.append(line+1) - - return winnings, winning_lines - -def get_slot_machine_spin(rows, cols, symbols): #creating columns=[] - all_symbols=[] - - for symbol,sym_count in symbols.items(): - for _ in range(sym_count): - all_symbols.append(symbol) - columns=[] - - for _ in range(cols): - current_symbols=all_symbols[:] - column=[] - - for _ in range(rows): - value=random.choice(current_symbols) - current_symbols.remove(value) - column.append(value) - columns.append(column) - return columns - -def print_slot_machine(columns)-> None: #tranposing columns=[] - num_rows=len(columns[0]) - - for row in range(num_rows): - for i,column in enumerate(columns): - value=column[row] - - if i!=len(columns)-1: - print(value,end=" | ") - else: - print(value,end="") - print() - -def deposit()->int: - while True: - amount=input("How much is the amount you wanna deposit? $") - if amount.isdigit(): - amount=int(amount) - if amount>0: - break - else: - print("Amount must be greater than 0") - else: - print("Enter a positive number") - return amount - -def get_number_of_lines()->int: - while True: - lines=input("How many lines u wanna bet from (1 to " + str(MAX_LINES) + ")? ") - if lines.isdigit(): - lines=int(lines) - if 1<=lines<=MAX_LINES: - break - else: - print("Enter a valid number of lines") - else: - print("Enter a number") - return lines - -def get_bet()->int: - while True: - bet=input("How much u wanna bet? ") - if bet.isdigit(): - bet=int(bet) - if MIN_BET<=bet<=MAX_BET: - break - else: - print("Enter a valid bet amount") - else: - print("Enter a number") - return bet - -def spin(balance)->int: - lines = get_number_of_lines() - while True: - bet = get_bet() - total_bet = bet * lines - if balance < total_bet: - print(f"You do not have enough to bet, Your current balance is: ${balance}") - else: - break - print(f"You are betting ${bet} in {lines} lines. Total bet is ${total_bet}") - - slots = get_slot_machine_spin(ROWS, COLS, symbol_count) - print_slot_machine(slots) - - winnings, winning_lines = check_winnings(slots, lines, bet, symbol_value) - print(f"You won ${winnings}.") - print("You won on lines:", *winning_lines) - - return winnings-total_bet - - -def main()->None: - balance=deposit() - while True: - print(f"Current balance is ${balance}") - ans=input("Press enter to play.(q to quit).") - if ans=='q': - break - balance+=spin(balance) - print(f"You left with ${balance}") - -if __name__=='__main__': +import random +import time +import os +import json +import sys + +# Reconfigure stdout and stderr to use UTF-8 to support emojis on Windows terminals +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding='utf-8') +if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding='utf-8') + + +# Configuration Constants +MAX_LINES = 3 +MAX_BET = 100 +MIN_BET = 1 +ROWS = 3 +COLS = 3 + +# File path for storing profiles +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +PROFILES_FILE = os.path.join(SCRIPT_DIR, "player_profiles.json") + +# Emojis for s ymbols +symbol_count = { + '💎': 2, # Diamond (Jackpot / Rarest) + '🔔': 4, # Bell + '🍋': 6, # Lemon + '🍒': 8 # Cherry (Common) +} + +symbol_value = { + '💎': 5, # Payout multiplier + '🔔': 4, + '🍋': 3, + '🍒': 2 +} + +# ANSI Escape Codes for CLI Colors +RESET = "\033[0m" +BOLD = "\033[1m" +RED = "\033[31m" +GREEN = "\033[32m" +YELLOW = "\033[33m" +BLUE = "\033[34m" +MAGENTA = "\033[35m" +CYAN = "\033[36m" +WHITE = "\033[37m" +CLEAR_LINE = "\033[K" + +def load_profiles(): + if not os.path.exists(PROFILES_FILE): + return {} + try: + with open(PROFILES_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception: + return {} + +def save_profiles(profiles): + try: + with open(PROFILES_FILE, 'w', encoding='utf-8') as f: + json.dump(profiles, f, indent=4, ensure_ascii=False) + except Exception as e: + print(f"{RED}Error saving profile data: {e}{RESET}") + +def check_winnings(columns, lines, bet, values): + winnings = 0 + winning_lines = [] + for line in range(lines): + symbol = columns[0][line] + for column in columns: + symbol_to_check = column[line] + if symbol_to_check != symbol: + break + else: + winnings += values[symbol] * bet + winning_lines.append(line + 1) + + return winnings, winning_lines + +def get_slot_machine_spin(rows, cols, symbols): + all_symbols = [] + for symbol, sym_count in symbols.items(): + for _ in range(sym_count): + all_symbols.append(symbol) + + columns = [] + for _ in range(cols): + current_symbols = all_symbols[:] + column = [] + for _ in range(rows): + value = random.choice(current_symbols) + current_symbols.remove(value) + column.append(value) + columns.append(column) + + return columns + +def print_slot_machine(columns) -> None: + num_rows = len(columns[0]) + for row in range(num_rows): + print(CLEAR_LINE, end="") + for i, column in enumerate(columns): + value = column[row] + if i != len(columns) - 1: + print(f" {value} ", end="|") + else: + print(f" {value} ", end="") + print() + +def spin_animation(rows, cols, symbols): + print(f"\n{BOLD}{YELLOW}🎰 SPINNING THE REELS... 🎰{RESET}") + for _ in range(rows): + print() + + for frame in range(12): + print(f"\033[{rows}A", end="") + temp_slots = get_slot_machine_spin(rows, cols, symbols) + print_slot_machine(temp_slots) + time.sleep(0.04 + (frame * 0.02)) + +def deposit() -> int: + while True: + amount = input(f"{BOLD}{CYAN}How much is the amount you wanna deposit? ${RESET}") + if amount.isdigit(): + amount = int(amount) + if amount > 0: + print(f"{GREEN}Successfully deposited ${amount}!{RESET}") + break + else: + print(f"{RED}Amount must be greater than 0.{RESET}") + else: + print(f"{RED}Please enter a positive number.{RESET}") + return amount + +def get_number_of_lines() -> int: + while True: + lines = input(f"How many lines do you want to bet on (1 to {MAX_LINES})? ") + if lines.isdigit(): + lines = int(lines) + if 1 <= lines <= MAX_LINES: + break + else: + print(f"{RED}Enter a valid number of lines (1-{MAX_LINES}){RESET}") + else: + print(f"{RED}Enter a valid integer.{RESET}") + return lines + +def get_bet() -> int: + while True: + bet = input(f"How much do you want to bet per line (${MIN_BET} - ${MAX_BET})? $") + if bet.isdigit(): + bet = int(bet) + if MIN_BET <= bet <= MAX_BET: + break + else: + print(f"{RED}Bet must be between ${MIN_BET} and ${MAX_BET}.{RESET}") + else: + print(f"{RED}Please enter a valid number.{RESET}") + return bet + +def display_rules(): + print(f"\n{BOLD}{YELLOW}📜 SLOT MACHINE RULES & PAYOUTS 📜{RESET}") + print("┌──────────────────────────────────────────────┐") + print(f"│ {CYAN}Symbol{RESET} │ {CYAN}Occurrence Count{RESET} │ {CYAN}Multiplier Value{RESET} │") + print("├──────────────────────────────────────────────┤") + for symbol in symbol_count: + count = symbol_count[symbol] + value = symbol_value[symbol] + print(f"│ {symbol} │ {count} │ {value}x │") + print("└──────────────────────────────────────────────┘") + print("• You win if all symbols in a betted row match.") + print("• Winnings are calculated as: multiplier * bet per line.") + print("• Betting on multiple lines checks horizontal rows from top to bottom.") + input(f"\n{BLUE}Press Enter to return to the menu...{RESET}") + +def display_stats(stats): + print(f"\n{BOLD}{CYAN}📊 YOUR SESSION STATISTICS 📊{RESET}") + print("┌──────────────────────────────────────────┐") + print(f"│ Total Spins: {stats['total_spins']:<25}│") + + win_rate = 0.0 + if stats['total_spins'] > 0: + win_rate = (stats['rounds_won'] / stats['total_spins']) * 100 + + print(f"│ Win Rate: {f'{win_rate:.1f}%':<25}│") + print(f"│ Total Bet: {f'${stats['total_bet']}':<25}│") + print(f"│ Total Won: {f'${stats['total_won']}':<25}│") + + net_profit = stats['total_won'] - stats['total_bet'] + profit_color = GREEN if net_profit >= 0 else RED + sign = "+" if net_profit >= 0 else "" + profit_str = f"{sign}${net_profit}" + + print(f"│ Net Profit: {profit_color}{profit_str:<25}{RESET}│") + print(f"│ Biggest Win: {f'${stats['biggest_win']}':<25}│") + print("└──────────────────────────────────────────┘") + input(f"\n{BLUE}Press Enter to return to the menu...{RESET}") + +def play_round(balance, stats) -> int: + lines = get_number_of_lines() + while True: + bet = get_bet() + total_bet = bet * lines + if balance < total_bet: + print(f"{RED}You do not have enough funds. Current balance: ${balance} (Required: ${total_bet}){RESET}") + choice = input("Would you like to deposit more funds? (y/n): ").lower() + if choice == 'y': + balance += deposit() + else: + return balance + else: + break + + print(f"\nBetting {GREEN}${bet}{RESET} on {YELLOW}{lines}{RESET} line(s). Total bet: {RED}${total_bet}{RESET}") + + spin_animation(ROWS, COLS, symbol_count) + + slots = get_slot_machine_spin(ROWS, COLS, symbol_count) + + print(f"\033[{ROWS}A", end="") + print_slot_machine(slots) + + winnings, winning_lines = check_winnings(slots, lines, bet, symbol_value) + + stats['total_spins'] += 1 + stats['total_bet'] += total_bet + stats['total_won'] += winnings + if winnings > stats['biggest_win']: + stats['biggest_win'] = winnings + + if winnings > 0: + stats['rounds_won'] += 1 + print(f"\n{BOLD}{GREEN}🎉 YOU WON ${winnings}! 🎉{RESET}") + print(f"Winning line(s): {', '.join(map(str, winning_lines))}") + else: + print(f"\n{RED}No winning combinations on your lines.{RESET}") + + new_balance = balance - total_bet + winnings + print(f"New Balance: {GREEN}${new_balance}{RESET}") + return new_balance + +def select_profile(profiles): + while True: + print(f"\n{BOLD}{CYAN}👥 SELECT PLAYER PROFILE 👥{RESET}") + print("1. Select existing profile") + print("2. Create new profile") + print("3. Quit") + + choice = input(f"{BOLD}Choose an option (1-3): {RESET}").strip() + + if choice == '1': + if not profiles: + print(f"{RED}No profiles found. Please create one first.{RESET}") + continue + + print(f"\n{BOLD}{CYAN}Existing Profiles:{RESET}") + profile_list = list(profiles.keys()) + for idx, name in enumerate(profile_list, 1): + balance = profiles[name].get("balance", 0) + print(f"{idx}. {name} (Balance: ${balance})") + + p_choice = input(f"Select profile (1-{len(profile_list)}) or 'b' to go back: ").strip() + if p_choice.lower() == 'b': + continue + if p_choice.isdigit(): + p_idx = int(p_choice) - 1 + if 0 <= p_idx < len(profile_list): + profile_name = profile_list[p_idx] + profile_data = profiles[profile_name] + # Ensure stats exist + stats = profile_data.get("stats", { + "total_spins": 0, + "total_won": 0, + "total_bet": 0, + "biggest_win": 0, + "rounds_won": 0 + }) + # Make sure all required keys are in stats (migration safety) + for key in ["total_spins", "total_won", "total_bet", "biggest_win", "rounds_won"]: + if key not in stats: + stats[key] = 0 + return profile_name, profile_data.get("balance", 0), stats + print(f"{RED}Invalid selection.{RESET}") + + elif choice == '2': + name = input("Enter new profile name: ").strip() + if not name: + print(f"{RED}Name cannot be empty.{RESET}") + continue + if name in profiles: + print(f"{RED}Profile name already exists.{RESET}") + continue + + print(f"{GREEN}Creating profile for '{name}'...{RESET}") + initial_deposit = deposit() + profiles[name] = { + "balance": initial_deposit, + "stats": { + "total_spins": 0, + "total_won": 0, + "total_bet": 0, + "biggest_win": 0, + "rounds_won": 0 + } + } + save_profiles(profiles) + return name, initial_deposit, profiles[name]["stats"] + + elif choice == '3': + print(f"\n{BOLD}{YELLOW}Goodbye!{RESET}\n") + exit(0) + else: + print(f"{RED}Invalid choice.{RESET}") + +def main() -> None: + os.system("") + + print(f"{BOLD}{YELLOW}🎰 WELCOME TO THE PREMIUM SLOT MACHINE GAME! 🎰{RESET}") + + profiles = load_profiles() + profile_name, balance, stats = select_profile(profiles) + + while True: + print(f"\n{BOLD}{BLUE}======================================={RESET}") + print(f" Player: {YELLOW}{profile_name}{RESET} | Balance: {GREEN}${balance}{RESET}") + print(f"{BOLD}{BLUE}======================================={RESET}") + print(f"1. 🎰 {BOLD}Spin the Slots{RESET}") + print(f"2. 📜 View Rules & Payouts") + print(f"3. 📊 View Session Statistics") + print(f"4. 💵 Deposit More Funds") + print(f"5. 👥 Change Profile / Log Out") + print(f"6. 🚪 Save & Quit") + print(f"{BOLD}{BLUE}======================================={RESET}") + + choice = input(f"{BOLD}Choose an option (1-6): {RESET}").strip() + + if choice == '1': + if balance <= 0: + print(f"{RED}Your balance is $0! Please deposit more funds to continue playing.{RESET}") + continue + balance = play_round(balance, stats) + + # Save progress + profiles[profile_name]["balance"] = balance + profiles[profile_name]["stats"] = stats + save_profiles(profiles) + + elif choice == '2': + display_rules() + elif choice == '3': + display_stats(stats) + elif choice == '4': + added_deposit = deposit() + balance += added_deposit + + # Save progress + profiles[profile_name]["balance"] = balance + save_profiles(profiles) + + elif choice == '5': + # Save current profile progress first + profiles[profile_name]["balance"] = balance + profiles[profile_name]["stats"] = stats + save_profiles(profiles) + + # Switch profile + profiles = load_profiles() + profile_name, balance, stats = select_profile(profiles) + + elif choice == '6': + profiles[profile_name]["balance"] = balance + profiles[profile_name]["stats"] = stats + save_profiles(profiles) + break + else: + print(f"{RED}Invalid option. Please choose between 1 and 6.{RESET}") + + print(f"\n{BOLD}{YELLOW}💰 Cashing out... You left with ${balance}. Thank you for playing! 💰{RESET}\n") + +if __name__ == '__main__': main() \ No newline at end of file diff --git a/Slot machine python project/player_profiles.json b/Slot machine python project/player_profiles.json new file mode 100644 index 0000000..c45ae07 --- /dev/null +++ b/Slot machine python project/player_profiles.json @@ -0,0 +1,32 @@ +{ + "Navu": { + "balance": 660, + "stats": { + "total_spins": 8, + "total_won": 82, + "total_bet": 522, + "biggest_win": 60, + "rounds_won": 2 + } + }, + "TestUser": { + "balance": 497120, + "stats": { + "total_spins": 19, + "total_won": 1300, + "total_bet": 4780, + "biggest_win": 500, + "rounds_won": 4 + } + }, + "das": { + "balance": 0, + "stats": { + "total_spins": 12, + "total_won": 700, + "total_bet": 1200, + "biggest_win": 200, + "rounds_won": 5 + } + } +} \ No newline at end of file diff --git a/Slot machine python project/server.py b/Slot machine python project/server.py new file mode 100644 index 0000000..419564d --- /dev/null +++ b/Slot machine python project/server.py @@ -0,0 +1,89 @@ +import http.server +import socketserver +import json +import os +import urllib.parse + +PORT = 8000 +DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web") +PROFILES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "player_profiles.json") + +class SlotMachineRequestHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + # Serve from the "web" subdirectory + super().__init__(*args, directory=DIRECTORY, **kwargs) + + def do_GET(self): + parsed_path = urllib.parse.urlparse(self.path) + if parsed_path.path == '/api/profiles': + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + + if os.path.exists(PROFILES_FILE): + try: + with open(PROFILES_FILE, 'r', encoding='utf-8') as f: + data = json.load(f) + except Exception: + data = {} + else: + data = {} + + self.wfile.write(json.dumps(data).encode('utf-8')) + else: + # Fallback to serving files + super().do_GET() + + def do_POST(self): + parsed_path = urllib.parse.urlparse(self.path) + if parsed_path.path == '/api/profiles': + content_length = int(self.headers.get('Content-Length', 0)) + post_data = self.rfile.read(content_length) + + try: + profiles = json.loads(post_data.decode('utf-8')) + with open(PROFILES_FILE, 'w', encoding='utf-8') as f: + json.dump(profiles, f, indent=4, ensure_ascii=False) + + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps({"status": "success"}).encode('utf-8')) + except Exception as e: + self.send_response(500) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps({"status": "error", "message": str(e)}).encode('utf-8')) + else: + self.send_response(404) + self.end_headers() + + def do_OPTIONS(self): + # Handle preflight CORS request + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + +def run(): + # Ensure the web directory exists + os.makedirs(DIRECTORY, exist_ok=True) + + server_address = ('', PORT) + # Allow port reuse to prevent address-already-in-use errors during quick restarts + socketserver.TCPServer.allow_reuse_address = True + with socketserver.TCPServer(server_address, SlotMachineRequestHandler) as httpd: + print(f"Server running at http://localhost:{PORT}") + print(f"Serving web directory: {DIRECTORY}") + print("Press Ctrl+C to stop.") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nShutting down server.") + +if __name__ == '__main__': + run() diff --git a/Slot machine python project/web/app.js b/Slot machine python project/web/app.js new file mode 100644 index 0000000..8125119 --- /dev/null +++ b/Slot machine python project/web/app.js @@ -0,0 +1,628 @@ +// GAME SYSTEM - SLOT MACHINE FRONTEND LOGIC + +// Game Constants (matching main.py exactly) +const ROWS = 3; +const COLS = 3; +const MIN_BET = 1; +const MAX_BET = 100; + +const symbolCount = { + '💎': 2, // Jackpot (rarest) + '🔔': 4, // Bell + '🍋': 6, // Lemon + '🍒': 8 // Cherry (common) +}; + +const symbolValue = { + '💎': 5, // Multiplier + '🔔': 4, + '🍋': 3, + '🍒': 2 +}; + +const defaultStats = { + total_spins: 0, + total_won: 0, + total_bet: 0, + biggest_win: 0, + rounds_won: 0 +}; + +// State Variables +let allProfiles = {}; +let currentProfileName = ''; +let currentBalance = 0; +let currentStats = { ...defaultStats }; +let activeLines = 1; +let betPerLine = 10; +let isSpinning = false; +let soundEnabled = false; + +// Audio System (Web Audio API) +let audioCtx = null; + +function initAudio() { + if (!audioCtx) { + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + } + if (audioCtx.state === 'suspended') { + audioCtx.resume(); + } +} + +function playSound(type) { + if (!soundEnabled) return; + try { + initAudio(); + const now = audioCtx.currentTime; + + if (type === 'spin') { + // Rising pitch sci-fi hum + const osc = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(80, now); + osc.frequency.exponentialRampToValueAtTime(240, now + 1.2); + gainNode.gain.setValueAtTime(0.04, now); + gainNode.gain.exponentialRampToValueAtTime(0.005, now + 1.2); + + // Subtle vibrato + const lfo = audioCtx.createOscillator(); + const lfoGain = audioCtx.createGain(); + lfo.frequency.setValueAtTime(20, now); + lfoGain.gain.setValueAtTime(10, now); + lfo.connect(lfoGain); + lfoGain.connect(osc.frequency); + + osc.connect(gainNode); + gainNode.connect(audioCtx.destination); + + lfo.start(now); + osc.start(now); + lfo.stop(now + 1.2); + osc.stop(now + 1.2); + + } else if (type === 'stop') { + // Mechanical clunk/thump + const osc = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + osc.type = 'triangle'; + osc.frequency.setValueAtTime(160, now); + osc.frequency.exponentialRampToValueAtTime(30, now + 0.1); + gainNode.gain.setValueAtTime(0.12, now); + gainNode.gain.exponentialRampToValueAtTime(0.005, now + 0.12); + + osc.connect(gainNode); + gainNode.connect(audioCtx.destination); + osc.start(now); + osc.stop(now + 0.12); + + } else if (type === 'win') { + // Retro 8-bit win fanfare (C5 -> E5 -> G5 -> C6) + const notes = [523.25, 659.25, 783.99, 1046.50]; + notes.forEach((freq, idx) => { + const osc = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + osc.type = 'square'; + osc.frequency.setValueAtTime(freq, now + idx * 0.08); + gainNode.gain.setValueAtTime(0.04, now + idx * 0.08); + gainNode.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.08 + 0.2); + + osc.connect(gainNode); + gainNode.connect(audioCtx.destination); + osc.start(now + idx * 0.08); + osc.stop(now + idx * 0.08 + 0.2); + }); + + } else if (type === 'lose') { + // Descending minor chord buzz + const osc = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(130, now); + osc.frequency.linearRampToValueAtTime(60, now + 0.45); + gainNode.gain.setValueAtTime(0.06, now); + gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.45); + + osc.connect(gainNode); + gainNode.connect(audioCtx.destination); + osc.start(now); + osc.stop(now + 0.45); + + } else if (type === 'deposit') { + // Double coin ding (retro) + const osc = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + osc.type = 'sine'; + osc.frequency.setValueAtTime(987.77, now); // B5 + osc.frequency.setValueAtTime(1318.51, now + 0.06); // E6 + gainNode.gain.setValueAtTime(0.08, now); + gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.3); + + osc.connect(gainNode); + gainNode.connect(audioCtx.destination); + osc.start(now); + osc.stop(now + 0.3); + } + } catch (e) { + console.warn('Web Audio synthesis failed or blocked:', e); + } +} + +// REST API Handlers +async function loadProfilesFromServer() { + try { + const response = await fetch('/api/profiles'); + if (response.ok) { + allProfiles = await response.json(); + populateProfilesDropdown(); + } else { + console.error('Failed to load profiles'); + } + } catch (error) { + console.error('Error contacting server for profiles:', error); + } +} + +async function saveProfilesToServer() { + try { + const response = await fetch('/api/profiles', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(allProfiles) + }); + if (!response.ok) { + console.error('Failed to save profiles to server'); + } + } catch (error) { + console.error('Error saving profiles to server:', error); + } +} + +// UI Population +function populateProfilesDropdown() { + const dropdown = document.getElementById('select-profile-dropdown'); + // Clear existing options, save placeholder + dropdown.innerHTML = ''; + + Object.keys(allProfiles).forEach(name => { + const option = document.createElement('option'); + option.value = name; + option.textContent = `${name} (Balance: $${allProfiles[name].balance})`; + dropdown.appendChild(option); + }); +} + +function updateGameDashboard() { + document.getElementById('display-player-name').textContent = currentProfileName; + document.getElementById('display-balance').textContent = `$${currentBalance}`; + + // Update stats text + document.getElementById('stat-spins').textContent = currentStats.total_spins; + + const winRate = currentStats.total_spins > 0 + ? ((currentStats.rounds_won / currentStats.total_spins) * 100).toFixed(1) + : '0.0'; + document.getElementById('stat-winrate').textContent = `${winRate}%`; + document.getElementById('stat-bet').textContent = `$${currentStats.total_bet}`; + document.getElementById('stat-won').textContent = `$${currentStats.total_won}`; + + const netProfit = currentStats.total_won - currentStats.total_bet; + const netBox = document.getElementById('stat-net-box'); + const netEl = document.getElementById('stat-net'); + + const sign = netProfit >= 0 ? '+' : '-'; + netEl.textContent = sign + `$${Math.abs(netProfit)}`; + if (netProfit >= 0) { + netBox.className = 'stat-box col-span-2 highlight-box text-green'; + } else { + netBox.className = 'stat-box col-span-2 highlight-box text-red'; + } + + document.getElementById('stat-biggest').textContent = `$${currentStats.biggest_win}`; + + // Update active lines button highlights + for (let l = 1; l <= 3; l++) { + const btn = document.getElementById(`btn-line-${l}`); + if (l === activeLines) { + btn.classList.add('active'); + document.getElementById(`payline-${l}`).classList.add('active'); + } else { + btn.classList.remove('active'); + document.getElementById(`payline-${l}`).classList.remove('active'); + } + } + + // Update total bet display + const totalBet = betPerLine * activeLines; + document.getElementById('display-total-bet').textContent = `$${totalBet}`; + + // Disable/Enable spin button if balance is too low + const btnSpin = document.getElementById('btn-spin'); + if (currentBalance < totalBet && !isSpinning) { + btnSpin.title = 'Insufficient funds!'; + } else { + btnSpin.title = 'Spin the slots!'; + } +} + +// Core Game Logic (identical to python check_winnings & get_slot_machine_spin) +function getSlotMachineSpin() { + const allSymbols = []; + for (const [symbol, count] of Object.entries(symbolCount)) { + for (let i = 0; i < count; i++) { + allSymbols.push(symbol); + } + } + + const columns = []; + for (let col = 0; col < COLS; col++) { + const currentSymbols = [...allSymbols]; + const column = []; + for (let row = 0; row < ROWS; row++) { + const idx = Math.floor(Math.random() * currentSymbols.length); + const value = currentSymbols[idx]; + currentSymbols.splice(idx, 1); + column.push(value); + } + columns.push(column); + } + return columns; +} + +function checkWinnings(columns, lines, bet) { + let winnings = 0; + const winningLines = []; + for (let line = 0; line < lines; line++) { + const symbol = columns[0][line]; + let allMatch = true; + for (let col = 0; col < columns.length; col++) { + if (columns[col][line] !== symbol) { + allMatch = false; + break; + } + } + if (allMatch) { + winnings += symbolValue[symbol] * bet; + winningLines.push(line + 1); + } + } + return { winnings, winningLines }; +} + +// Spin Routine +function executeSpin() { + if (isSpinning) return; + + const totalBet = betPerLine * activeLines; + if (currentBalance < totalBet) { + showError('You do not have enough funds! Click 💵 Deposit to add more.'); + openDepositModal(); + return; + } + + isSpinning = true; + document.getElementById('btn-spin').disabled = true; + document.getElementById('btn-logout').disabled = true; + + // Clear active payline pulses + for (let l = 1; l <= 3; l++) { + document.getElementById(`payline-${l}`).classList.remove('active'); + } + + // Deduct balance + currentBalance -= totalBet; + updateGameDashboard(); + + // Update announcement board + const announcement = document.getElementById('announcement-text'); + announcement.textContent = 'SPINNING THE REELS...'; + announcement.className = 'announcement-spin'; + + playSound('spin'); + + // Generate results + const spinResults = getSlotMachineSpin(); + + // Staggered reels stopping + const reels = [ + document.getElementById('reel-0'), + document.getElementById('reel-1'), + document.getElementById('reel-2') + ]; + + reels.forEach((reel, idx) => { + reel.classList.add('spinning'); + + // Staggered stop delays: Reel 0 @ 1000ms, Reel 1 @ 1500ms, Reel 2 @ 2000ms + const stopDelay = 800 + (idx * 500); + + setTimeout(() => { + reel.classList.remove('spinning'); + + // Lock results into visual elements + const inner = reel.querySelector('.reel-inner'); + inner.innerHTML = ''; + for (let r = 0; r < ROWS; r++) { + const cell = document.createElement('div'); + cell.className = 'slot-cell'; + cell.textContent = spinResults[idx][r]; + inner.appendChild(cell); + } + + // Add a locking impact shake effect + reel.classList.add('shaking'); + playSound('stop'); + setTimeout(() => { + reel.classList.remove('shaking'); + }, 150); + + // Once the last reel stops, calculate and display outcome + if (idx === reels.length - 1) { + concludeSpin(spinResults, totalBet); + } + }, stopDelay); + }); +} + +function concludeSpin(spinResults, totalBet) { + const { winnings, winningLines } = checkWinnings(spinResults, activeLines, betPerLine); + + // Update balance and stats + currentBalance += winnings; + + currentStats.total_spins += 1; + currentStats.total_bet += totalBet; + currentStats.total_won += winnings; + if (winnings > currentStats.biggest_win) { + currentStats.biggest_win = winnings; + } + + const announcement = document.getElementById('announcement-text'); + + if (winnings > 0) { + currentStats.rounds_won += 1; + + announcement.textContent = `🎉 YOU WON $${winnings}! 🎉`; + announcement.className = 'announcement-win'; + playSound('win'); + + // Pulse only the winning lines + winningLines.forEach(lineNum => { + document.getElementById(`payline-${lineNum}`).classList.add('active'); + }); + } else { + announcement.textContent = 'NO WINNING COMBINATIONS'; + announcement.className = 'announcement-lose'; + playSound('lose'); + } + + // Save state back to global structure + allProfiles[currentProfileName].balance = currentBalance; + allProfiles[currentProfileName].stats = { ...currentStats }; + + // Save to file + saveProfilesToServer(); + + // Re-enable controls + isSpinning = false; + document.getElementById('btn-spin').disabled = false; + document.getElementById('btn-logout').disabled = false; + + updateGameDashboard(); +} + +// Event Listeners Registration +document.addEventListener('DOMContentLoaded', () => { + // Load initial profiles list from server + loadProfilesFromServer(); + + // Screen transitions - Load Profile + document.getElementById('btn-load-profile').addEventListener('click', () => { + const select = document.getElementById('select-profile-dropdown'); + const selectedName = select.value; + if (!selectedName) { + showError('Please choose a profile first.'); + return; + } + + currentProfileName = selectedName; + currentBalance = allProfiles[selectedName].balance; + currentStats = allProfiles[selectedName].stats || { ...defaultStats }; + + transitionToGame(); + }); + + // Screen transitions - Create Profile + document.getElementById('btn-create-profile').addEventListener('click', () => { + const inputName = document.getElementById('input-new-profile-name').value.trim(); + const inputDeposit = parseInt(document.getElementById('input-initial-deposit').value); + + if (!inputName) { + showError('Profile name cannot be empty.'); + return; + } + if (allProfiles[inputName]) { + showError('A profile with that name already exists.'); + return; + } + if (isNaN(inputDeposit) || inputDeposit <= 0) { + showError('Initial deposit must be a valid positive number.'); + return; + } + + // Initialize locally + allProfiles[inputName] = { + balance: inputDeposit, + stats: { ...defaultStats } + }; + + currentProfileName = inputName; + currentBalance = inputDeposit; + currentStats = { ...defaultStats }; + + // Save to backend file, then switch screen + saveProfilesToServer().then(() => { + transitionToGame(); + // Reload dropdown behind the scenes + loadProfilesFromServer(); + }); + }); + + // Logout + document.getElementById('btn-logout').addEventListener('click', () => { + if (isSpinning) return; + currentProfileName = ''; + currentBalance = 0; + currentStats = { ...defaultStats }; + + document.getElementById('input-new-profile-name').value = ''; + document.getElementById('screen-game').classList.remove('active'); + document.getElementById('screen-landing').classList.add('active'); + loadProfilesFromServer(); + }); + + // Spin trigger + document.getElementById('btn-spin').addEventListener('click', executeSpin); + + // Line selector buttons + [1, 2, 3].forEach(linesCount => { + document.getElementById(`btn-line-${linesCount}`).addEventListener('click', () => { + if (isSpinning) return; + activeLines = linesCount; + updateGameDashboard(); + }); + }); + + // Bet increment / decrement + document.getElementById('btn-bet-minus').addEventListener('click', () => { + if (isSpinning) return; + if (betPerLine > MIN_BET) { + betPerLine--; + document.getElementById('input-bet-amount').value = betPerLine; + updateGameDashboard(); + } + }); + + document.getElementById('btn-bet-plus').addEventListener('click', () => { + if (isSpinning) return; + if (betPerLine < MAX_BET) { + betPerLine++; + document.getElementById('input-bet-amount').value = betPerLine; + updateGameDashboard(); + } + }); + + document.getElementById('input-bet-amount').addEventListener('change', (e) => { + if (isSpinning) return; + let val = parseInt(e.target.value); + if (isNaN(val) || val < MIN_BET) val = MIN_BET; + if (val > MAX_BET) val = MAX_BET; + betPerLine = val; + e.target.value = val; + updateGameDashboard(); + }); + + // Sound toggle + document.getElementById('btn-toggle-sound').addEventListener('click', () => { + soundEnabled = !soundEnabled; + const btn = document.getElementById('btn-toggle-sound'); + const text = btn.querySelector('.sound-text'); + + if (soundEnabled) { + btn.classList.remove('muted'); + text.textContent = 'Sound: ON'; + // Play a soft test chime to confirm audio is active and unblocked + playSound('deposit'); + } else { + btn.classList.add('muted'); + text.textContent = 'Sound: OFF'; + } + }); + + // Modals handling (Deposit) + document.getElementById('btn-open-deposit').addEventListener('click', openDepositModal); + document.getElementById('btn-close-deposit').addEventListener('click', closeDepositModal); + document.getElementById('btn-submit-deposit').addEventListener('click', submitDeposit); + + // Preset deposit buttons + document.querySelectorAll('.btn-preset').forEach(btn => { + btn.addEventListener('click', (e) => { + const amount = e.target.dataset.amount; + document.getElementById('input-deposit-amount').value = amount; + }); + }); +}); + +// Helper Functions +function transitionToGame() { + document.getElementById('landing-error').classList.add('hidden'); + document.getElementById('screen-landing').classList.remove('active'); + document.getElementById('screen-game').classList.add('active'); + + // Setup initial visuals on reels + const innerReels = [ + document.getElementById('reel-0').querySelector('.reel-inner'), + document.getElementById('reel-1').querySelector('.reel-inner'), + document.getElementById('reel-2').querySelector('.reel-inner') + ]; + + const initialSymbols = ['🍒', '🍋', '🔔', '💎']; + innerReels.forEach(inner => { + inner.innerHTML = ''; + for (let r = 0; r < ROWS; r++) { + const cell = document.createElement('div'); + cell.className = 'slot-cell'; + // Random starter symbols + cell.textContent = initialSymbols[Math.floor(Math.random() * initialSymbols.length)]; + inner.appendChild(cell); + } + }); + + // Clear message board + const announcement = document.getElementById('announcement-text'); + announcement.textContent = 'PLACE YOUR BET & SPIN!'; + announcement.className = 'announcement-idle'; + + updateGameDashboard(); +} + +function openDepositModal() { + document.getElementById('modal-deposit').classList.remove('hidden'); +} + +function closeDepositModal() { + document.getElementById('modal-deposit').classList.add('hidden'); +} + +function submitDeposit() { + const inputVal = parseInt(document.getElementById('input-deposit-amount').value); + if (isNaN(inputVal) || inputVal <= 0) { + alert('Please enter a valid deposit amount.'); + return; + } + + currentBalance += inputVal; + allProfiles[currentProfileName].balance = currentBalance; + + playSound('deposit'); + saveProfilesToServer(); + updateGameDashboard(); + closeDepositModal(); + + // Reset deposit input field default + document.getElementById('input-deposit-amount').value = 200; +} + +function showError(msg) { + const errorEl = document.getElementById('landing-error'); + errorEl.textContent = msg; + errorEl.classList.remove('hidden'); + + // Automatically hide after 4 seconds + setTimeout(() => { + errorEl.classList.add('hidden'); + }, 4000); +} diff --git a/Slot machine python project/web/index.html b/Slot machine python project/web/index.html new file mode 100644 index 0000000..e0e8ec1 --- /dev/null +++ b/Slot machine python project/web/index.html @@ -0,0 +1,296 @@ + + + + + + Premium Slot Machine - Interactive Web Edition + + + + + + + + +
+
+
+
+
+ + +
+ + +
+
+ 🎰 +

PREMIUM SLOT MACHINE

+

Interactive Web Edition

+
+ +
+ +
+

👥 Select Existing Profile

+
+ +
+ +
+ +
+ OR +
+ + +
+

✨ Create New Profile

+
+ + +
+
+ + +
+ +
+
+ + +
+ + +
+ +
+ + +
+ Current Balance +
$0
+
+ +
+ + +
+
+ +
+ +
+ + +
+ +
+
+ 💎 💎 💎 + JACKPOT + 💎 💎 💎 +
+ + +
+ +
+
+
+ +
+ +
+
+
🍒
+
🍋
+
🔔
+
+
+ +
+
+
🍋
+
🍒
+
💎
+
+
+ +
+
+
🔔
+
💎
+
🍒
+
+
+
+
+ + +
+
PLACE YOUR BET & SPIN!
+
+
+ + +
+
+ +
+ +
+ + + +
+
+ + +
+ +
+ + + +
+
+
+ +
+
+ Total Bet: + $10 +
+ + +
+
+
+ + +
+ + +
+
+

📊 Session Statistics

+
+
+
+ 0 + Spins +
+
+ 0.0% + Win Rate +
+
+ $0 + Total Bet +
+
+ $0 + Total Won +
+
+ $0 + Net Profit / Loss +
+
+ $0 + Biggest Win +
+
+
+ + +
+
+

📜 Multipliers & Odds

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolQuantityPayout
💎 Diamond25x
🔔 Bell44x
🍋 Lemon63x
🍒 Cherry82x
+ +
+
+
+
+
+ + + + + +
+ +
+ + + + + diff --git a/Slot machine python project/web/style.css b/Slot machine python project/web/style.css new file mode 100644 index 0000000..db52b49 --- /dev/null +++ b/Slot machine python project/web/style.css @@ -0,0 +1,1132 @@ +/* STYLE SYSTEM - SLOT MACHINE WEB UI */ + +:root { + --bg-dark: hsl(240, 20%, 5%); + --bg-deep: hsl(240, 25%, 8%); + --card-bg: rgba(18, 18, 28, 0.65); + --card-border: rgba(255, 255, 255, 0.08); + --primary: hsl(271, 91%, 65%); + --primary-glow: hsla(271, 91%, 65%, 0.4); + --accent: hsl(48, 100%, 55%); + --accent-glow: hsla(48, 100%, 55%, 0.4); + --green: hsl(145, 85%, 50%); + --green-glow: hsla(145, 85%, 50%, 0.3); + --red: hsl(355, 85%, 60%); + --red-glow: hsla(355, 85%, 60%, 0.3); + --blue: hsl(200, 90%, 55%); + --blue-glow: hsla(200, 90%, 55%, 0.3); + --white: hsl(0, 0%, 100%); + --text-main: hsl(220, 15%, 90%); + --text-muted: hsl(220, 10%, 65%); + --shadow-neon: 0 0 15px var(--primary-glow); + --shadow-card: 0 10px 30px rgba(0, 0, 0, 0.5); + --border-radius-lg: 16px; + --border-radius-md: 8px; + --font-heading: 'Outfit', sans-serif; + --font-body: 'Inter', sans-serif; + --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +/* RESET */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-dark); + color: var(--text-main); + font-family: var(--font-body); + min-height: 100vh; + overflow-x: hidden; + position: relative; + display: flex; + justify-content: center; + align-items: center; +} + +/* STARS BACKGROUND & BACKGROUND GLOWS */ +.stars-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -2; + background-image: + radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 40px), + radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 30px), + radial-gradient(white, rgba(255,255,255,.1) 2px, transparent 40px); + background-size: 550px 550px, 350px 350px, 250px 250px; + background-position: 0 0, 40px 60px, 130px 270px; + opacity: 0.15; +} + +.glow-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; + pointer-events: none; +} + +.glow-circle { + position: absolute; + border-radius: 50%; + filter: blur(120px); + opacity: 0.25; +} + +.glow-circle-1 { + top: -10%; + left: -10%; + width: 50vw; + height: 50vw; + background: radial-gradient(circle, var(--primary) 0%, transparent 70%); +} + +.glow-circle-2 { + bottom: -10%; + right: -10%; + width: 60vw; + height: 60vw; + background: radial-gradient(circle, var(--blue) 0%, transparent 70%); +} + +/* APP CONTAINER */ +.app-container { + width: 100%; + max-width: 1200px; + padding: 24px; + z-index: 1; +} + +/* CARDS */ +.card { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-card); +} + +.glass-card { + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +/* SCREENS CONTROL */ +.screen-section { + display: none; + animation: fadeIn 0.5s ease; +} + +.screen-section.active { + display: block; +} + +/* ANIMATIONS */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.animate-bounce { + animation: bounce 2s infinite ease-in-out; +} + +@keyframes spin-shake { + 0% { transform: translate(1px, 1px) rotate(0deg); } + 10% { transform: translate(-1px, -2px) rotate(-1deg); } + 20% { transform: translate(-3px, 0px) rotate(1deg); } + 30% { transform: translate(0px, 2px) rotate(0deg); } + 40% { transform: translate(1px, -1px) rotate(1deg); } + 50% { transform: translate(-1px, 2px) rotate(-1deg); } + 60% { transform: translate(-3px, 1px) rotate(0deg); } + 70% { transform: translate(2px, 1px) rotate(-1deg); } + 80% { transform: translate(-1px, -1px) rotate(1deg); } + 90% { transform: translate(2px, 2px) rotate(0deg); } + 100% { transform: translate(1px, -2px) rotate(-1deg); } +} + +/* 1. LANDING SCREEN */ +#screen-landing { + max-width: 700px; + margin: 40px auto; + padding: 40px; +} + +.brand-header { + text-align: center; + margin-bottom: 40px; +} + +.brand-emoji { + font-size: 4.5rem; + display: inline-block; + margin-bottom: 12px; +} + +.brand-header h1 { + font-family: var(--font-heading); + font-size: 2.8rem; + font-weight: 800; + letter-spacing: 1.5px; + background: linear-gradient(135deg, var(--white) 30%, var(--accent) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + text-shadow: 0 4px 10px rgba(0,0,0,0.3); +} + +.brand-subtitle { + font-size: 1.1rem; + color: var(--text-muted); + letter-spacing: 3px; + text-transform: uppercase; + margin-top: 6px; +} + +.profile-flow-container { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 24px; + align-items: stretch; + margin-bottom: 24px; +} + +.flow-box { + padding: 24px; + background: rgba(255, 255, 255, 0.02); + border-radius: var(--border-radius-lg); + border: 1px solid rgba(255, 255, 255, 0.04); + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.flow-box h2 { + font-family: var(--font-heading); + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 20px; + color: var(--white); + text-align: center; +} + +.flow-divider { + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + font-weight: 700; + font-size: 0.9rem; + position: relative; +} + +.flow-divider::before, .flow-divider::after { + content: ''; + position: absolute; + width: 1px; + height: 40%; + background: rgba(255,255,255,0.08); +} + +.flow-divider::before { top: 0; } +.flow-divider::after { bottom: 0; } + +/* INPUTS & FORM GROUPS */ +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 6px; + font-weight: 600; +} + +.input-select, .input-text, .input-number { + width: 100%; + padding: 12px 16px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--border-radius-md); + color: var(--text-main); + font-size: 0.95rem; + font-family: var(--font-body); + transition: var(--transition); +} + +.input-select:focus, .input-text:focus, .input-number:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 10px rgba(168, 85, 247, 0.3); + background: rgba(0, 0, 0, 0.4); +} + +/* BUTTONS */ +.btn { + cursor: pointer; + font-family: var(--font-body); + font-weight: 600; + font-size: 0.95rem; + padding: 12px 24px; + border-radius: var(--border-radius-md); + border: 1px solid transparent; + transition: var(--transition); + display: inline-flex; + justify-content: center; + align-items: center; + gap: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.btn:hover { + transform: translateY(-2px); +} + +.btn:active { + transform: translateY(0); +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary) 0%, hsl(280, 80%, 55%) 100%); + color: var(--white); + box-shadow: 0 4px 15px rgba(168, 85, 247, 0.4); +} + +.btn-primary:hover { + box-shadow: 0 6px 20px rgba(168, 85, 247, 0.6); +} + +.btn-accent { + background: linear-gradient(135deg, var(--accent) 0%, hsl(40, 100%, 45%) 100%); + color: hsl(240, 20%, 5%); + box-shadow: 0 4px 15px rgba(234, 179, 8, 0.3); +} + +.btn-accent:hover { + box-shadow: 0 6px 20px rgba(234, 179, 8, 0.5); +} + +.btn-secondary-outline { + background: transparent; + border-color: rgba(255, 255, 255, 0.15); + color: var(--text-main); +} + +.btn-secondary-outline:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.3); +} + +.btn-danger-outline { + background: transparent; + border-color: rgba(239, 68, 68, 0.3); + color: var(--red); +} + +.btn-danger-outline:hover { + background: rgba(239, 68, 68, 0.1); + border-color: var(--red); +} + +.btn-block { + display: flex; + width: 100%; +} + +.error-message { + margin-top: 15px; + padding: 12px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.2); + border-radius: var(--border-radius-md); + color: var(--red); + font-size: 0.85rem; + text-align: center; +} + +.hidden { + display: none !important; +} + +/* 2. GAME SCREEN OVERALL */ +.game-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + margin-bottom: 24px; +} + +.user-info { + display: flex; + align-items: center; + gap: 12px; +} + +.user-avatar { + font-size: 2rem; + background: rgba(255,255,255,0.05); + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid rgba(255,255,255,0.1); +} + +.user-label { + display: block; + font-size: 0.75rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; +} + +.user-name { + font-size: 1.25rem; + font-weight: 800; + font-family: var(--font-heading); + color: var(--white); +} + +.balance-display { + text-align: center; +} + +.balance-label { + display: block; + font-size: 0.8rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1.5px; + margin-bottom: 2px; +} + +.balance-amount { + font-size: 2.2rem; + font-weight: 800; + font-family: var(--font-heading); + color: var(--green); + text-shadow: 0 0 10px var(--green-glow); +} + +.header-actions { + display: flex; + gap: 12px; +} + +/* GAME GRID */ +.game-grid { + display: grid; + grid-template-columns: 7fr 4fr; + gap: 24px; +} + +/* SLOT CABINET */ +.slot-machine-cabinet { + background: linear-gradient(180deg, hsl(240, 20%, 15%) 0%, hsl(240, 20%, 8%) 100%); + border: 4px solid hsl(240, 15%, 22%); + border-radius: 24px; + box-shadow: + 0 15px 40px rgba(0, 0, 0, 0.7), + inset 0 4px 0 rgba(255, 255, 255, 0.1), + 0 0 30px rgba(168, 85, 247, 0.2); + padding: 24px; + margin-bottom: 24px; + position: relative; + overflow: hidden; +} + +.cabinet-banner { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; + padding: 10px; + background: hsl(240, 20%, 6%); + border-radius: 12px; + border: 1px solid rgba(255,255,255,0.05); + margin-bottom: 20px; + position: relative; +} + +.neon-light-bar { + position: absolute; + bottom: -1px; + left: 10%; + width: 80%; + height: 2px; + background: linear-gradient(90deg, transparent, var(--primary), var(--blue), transparent); + box-shadow: 0 0 8px var(--primary); +} + +.cabinet-title { + font-family: var(--font-heading); + font-size: 1.6rem; + font-weight: 800; + letter-spacing: 5px; + color: var(--accent); + text-shadow: 0 0 10px var(--accent-glow); +} + +.jackpot-symbols { + font-size: 1.1rem; + opacity: 0.8; +} + +/* REELS FRAME & REELS */ +.reels-frame { + background: hsl(240, 25%, 3%); + border: 4px solid hsl(240, 20%, 6%); + border-radius: 16px; + padding: 16px; + position: relative; + box-shadow: inset 0 10px 20px rgba(0,0,0,0.8); + overflow: hidden; +} + +.reels-container { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; + height: 260px; /* displays 3 symbols at height ~80px each */ + position: relative; + mask-image: linear-gradient(to bottom, transparent, #000 15%, #000 85%, transparent); + -webkit-mask-image: linear-gradient(to bottom, transparent, #000 15%, #000 85%, transparent); +} + +.reel { + background: linear-gradient(180deg, hsl(240, 15%, 12%) 0%, hsl(240, 15%, 7%) 100%); + border: 1px solid rgba(255,255,255,0.03); + border-radius: 12px; + height: 100%; + overflow: hidden; + position: relative; +} + +.reel-inner { + position: absolute; + width: 100%; + top: 0; + left: 0; + display: flex; + flex-direction: column; +} + +.slot-cell { + height: 86.6px; /* 260px / 3 */ + display: flex; + justify-content: center; + align-items: center; + font-size: 3.2rem; + user-select: none; + text-shadow: 0 4px 10px rgba(0,0,0,0.5); + transition: transform 0.1s ease; +} + +.reel.spinning .reel-inner { + animation: roll-spin 0.4s infinite linear; +} + +.reel.shaking { + animation: spin-shake 0.15s infinite; +} + +@keyframes roll-spin { + 0% { transform: translateY(0); filter: blur(2px); } + 100% { transform: translateY(-346.4px); filter: blur(6px); } /* exactly 4 cells height */ +} + +/* PAYLINE OVERLAYS */ +.payline-visual { + position: absolute; + left: 16px; + right: 16px; + height: 4px; + background: transparent; + z-index: 5; + pointer-events: none; + border-radius: 2px; + transition: var(--transition); +} + +.payline-visual.active { + background: var(--accent); + box-shadow: + 0 0 10px var(--accent-glow), + 0 0 20px var(--accent); + animation: pulse-payline 1s infinite alternate; +} + +@keyframes pulse-payline { + from { opacity: 0.6; } + to { opacity: 1; } +} + +.line-row-1 { + top: calc(16px + 43.3px - 2px); /* Row 1 Center */ +} + +.line-row-2 { + top: calc(16px + 130px - 2px); /* Row 2 Center */ +} + +.line-row-3 { + top: calc(16px + 216.7px - 2px); /* Row 3 Center */ +} + +/* ANNOUNCEMENT BOARD */ +.announcement-board { + margin-top: 20px; + background: hsl(240, 30%, 4%); + border: 2px solid hsl(240, 20%, 12%); + border-radius: 12px; + padding: 14px; + text-align: center; + font-family: var(--font-heading); + box-shadow: inset 0 2px 5px rgba(0,0,0,0.5); +} + +.announcement-idle { + color: var(--text-muted); + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 1px; +} + +.announcement-spin { + color: var(--accent); + font-size: 0.95rem; + font-weight: 700; + letter-spacing: 2px; + animation: flash-text 0.8s infinite alternate; +} + +.announcement-win { + color: var(--green); + font-size: 1.15rem; + font-weight: 800; + letter-spacing: 1px; + text-shadow: 0 0 10px var(--green-glow); +} + +.announcement-lose { + color: var(--red); + font-size: 1.05rem; + font-weight: 700; + letter-spacing: 1px; +} + +@keyframes flash-text { + from { opacity: 0.5; } + to { opacity: 1; } +} + +/* CONTROLS CARD */ +.controls-card { + padding: 24px; +} + +.control-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; + margin-bottom: 24px; +} + +.control-group { + display: flex; + flex-direction: column; +} + +.control-label { + font-size: 0.8rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 8px; + font-weight: 600; +} + +/* BET LINES BUTTON GROUP */ +.btn-group { + display: flex; + background: rgba(0,0,0,0.2); + border-radius: var(--border-radius-md); + padding: 3px; + border: 1px solid rgba(255,255,255,0.06); +} + +.btn-selector { + flex: 1; + background: transparent; + color: var(--text-muted); + border: none; + font-size: 0.85rem; + padding: 10px; + border-radius: 6px; + text-transform: capitalize; + font-weight: 600; + transition: var(--transition); +} + +.btn-selector:hover { + color: var(--white); + background: rgba(255,255,255,0.03); +} + +.btn-selector.active { + background: var(--primary); + color: var(--white); + box-shadow: 0 2px 10px var(--primary-glow); +} + +/* BET QUANTITY ADJUSTERS */ +.bet-input-wrapper { + display: flex; + background: rgba(0,0,0,0.2); + border-radius: var(--border-radius-md); + border: 1px solid rgba(255,255,255,0.06); + padding: 3px; + align-items: center; +} + +.btn-adjust { + background: rgba(255, 255, 255, 0.05); + border: none; + color: var(--text-main); + width: 38px; + height: 38px; + border-radius: 6px; + font-size: 1.25rem; + font-weight: 700; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: var(--transition); +} + +.btn-adjust:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--white); +} + +.input-bet { + flex: 1; + background: transparent; + border: none; + color: var(--white); + text-align: center; + font-size: 1.15rem; + font-weight: 700; + font-family: var(--font-body); + width: 100%; +} + +.input-bet:focus { + outline: none; +} + +/* Hide scroll spinners on inputs */ +.input-bet::-webkit-outer-spin-button, +.input-bet::-webkit-inner-spin-button, +.input-number::-webkit-outer-spin-button, +.input-number::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.input-bet, .input-number { + -moz-appearance: textfield; +} + +/* TOTAL BET & SPIN ROW */ +.control-row-summary { + display: flex; + justify-content: space-between; + align-items: center; +} + +.total-bet-display { + display: flex; + flex-direction: column; +} + +.total-bet-label { + font-size: 0.8rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; +} + +.total-bet-amount { + font-size: 1.8rem; + font-weight: 800; + color: var(--white); + font-family: var(--font-heading); +} + +/* SPIN TRIGGER BUTTON */ +.btn-spin-trigger { + background: linear-gradient(135deg, var(--accent) 0%, hsl(38, 100%, 50%) 100%); + color: hsl(240, 20%, 5%); + font-size: 1.25rem; + font-weight: 800; + padding: 16px 40px; + border-radius: 12px; + border: none; + cursor: pointer; + transition: var(--transition); + position: relative; + overflow: hidden; + box-shadow: + 0 4px 20px rgba(234, 179, 8, 0.4), + inset 0 2px 0 rgba(255, 255, 255, 0.2); + letter-spacing: 1px; +} + +.btn-spin-trigger:hover { + transform: scale(1.05) translateY(-2px); + box-shadow: + 0 8px 25px rgba(234, 179, 8, 0.6), + inset 0 2px 0 rgba(255, 255, 255, 0.3); +} + +.btn-spin-trigger:active { + transform: scale(0.98) translateY(0); +} + +.btn-spin-trigger:disabled { + background: hsl(240, 10%, 15%); + color: var(--text-muted); + box-shadow: none; + transform: none; + cursor: not-allowed; +} + +.spin-glow { + position: absolute; + top: 0; + left: -100%; + width: 50%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); + transform: skewX(-20deg); + transition: 0.5s; +} + +.btn-spin-trigger:hover .spin-glow { + left: 150%; + transition: 0.8s ease-in-out; +} + +/* SIDE PANEL CARDS */ +.stats-card, .rules-card { + padding: 24px; + margin-bottom: 24px; +} + +.stats-card:last-child, .rules-card:last-child { + margin-bottom: 0; +} + +.card-header { + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + padding-bottom: 12px; + margin-bottom: 16px; +} + +.card-header h3 { + font-family: var(--font-heading); + font-size: 1.15rem; + font-weight: 600; + color: var(--white); + letter-spacing: 0.5px; +} + +/* STATS GRID */ +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.stat-box { + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.03); + border-radius: 12px; + padding: 14px; + display: flex; + flex-direction: column; + align-items: center; +} + +.col-span-2 { + grid-column: span 2; +} + +.stat-num { + font-family: var(--font-heading); + font-size: 1.4rem; + font-weight: 800; + color: var(--white); + margin-bottom: 4px; +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-muted); + font-weight: 600; + text-transform: uppercase; +} + +.highlight-box { + background: rgba(255, 255, 255, 0.03); + border-color: rgba(255, 255, 255, 0.08); +} + +.text-green { color: var(--green); } +.text-red { color: var(--red); } +.text-blue { color: var(--blue); } +.text-gold { color: var(--accent); } + +/* RULES TABLE */ +.rules-table { + width: 100%; + border-collapse: collapse; +} + +.rules-table th, .rules-table td { + padding: 10px 8px; + text-align: left; + font-size: 0.85rem; +} + +.rules-table th { + color: var(--text-muted); + font-weight: 600; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + text-transform: uppercase; + font-size: 0.75rem; +} + +.rules-table td { + border-bottom: 1px solid rgba(255, 255, 255, 0.02); + color: var(--text-main); + vertical-align: middle; +} + +.symbol-badge { + font-size: 1.35rem; + display: inline-block; + vertical-align: middle; + margin-right: 6px; +} + +.multiplier-val { + font-weight: 700; + color: var(--accent); + text-align: right; +} + +.rules-table th:last-child { + text-align: right; +} + +.rules-footer { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.04); +} + +.rules-footer p { + font-size: 0.8rem; + color: var(--text-muted); + text-align: center; + line-height: 1.4; +} + +/* 3. MODALS */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + display: flex; + justify-content: center; + align-items: center; + transition: opacity 0.3s ease; +} + +.modal-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(5px); +} + +.modal-content { + position: relative; + width: 100%; + max-width: 440px; + padding: 32px; + z-index: 2; + animation: zoomIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.modal-close-btn { + position: absolute; + top: 16px; + right: 20px; + background: transparent; + border: none; + color: var(--text-muted); + font-size: 1.8rem; + cursor: pointer; + transition: var(--transition); +} + +.modal-close-btn:hover { + color: var(--white); +} + +.modal-content h2 { + font-family: var(--font-heading); + font-size: 1.6rem; + font-weight: 800; + margin-bottom: 8px; + color: var(--white); +} + +.modal-desc { + font-size: 0.9rem; + color: var(--text-muted); + margin-bottom: 24px; +} + +.deposit-presets { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + margin-bottom: 24px; +} + +.btn-preset { + padding: 8px; + font-size: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.06); + color: var(--text-main); +} + +.btn-preset:hover { + background: var(--primary); + color: var(--white); + border-color: transparent; +} + +@keyframes zoomIn { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 1; transform: scale(1); } +} + +/* FLOAT FLOATING CONTROLS */ +.audio-control-float { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 50; +} + +.btn-sound { + background: var(--card-bg); + border: 1px solid var(--card-border); + backdrop-filter: blur(8px); + border-radius: 30px; + padding: 10px 18px; + font-size: 0.8rem; + color: var(--text-main); + box-shadow: var(--shadow-card); +} + +.btn-sound:hover { + background: rgba(255,255,255,0.06); + border-color: rgba(255,255,255,0.2); +} + +.btn-sound.muted .sound-icon-unmuted { display: none; } +.btn-sound.muted .sound-icon-muted { display: inline; } +.btn-sound:not(.muted) .sound-icon-muted { display: none; } +.btn-sound:not(.muted) .sound-icon-unmuted { display: inline; } + +.btn-sound:not(.muted) { + border-color: var(--green); + box-shadow: 0 0 10px var(--green-glow); +} + +/* RESPONSIVE LAYOUTS */ +@media (max-width: 900px) { + .game-grid { + grid-template-columns: 1fr; + } + .profile-flow-container { + grid-template-columns: 1fr; + } + .flow-divider { + padding: 12px 0; + } + .flow-divider::before, .flow-divider::after { + width: 40%; + height: 1px; + } + .flow-divider::before { left: 0; top: 50%; } + .flow-divider::after { right: 0; bottom: 50%; } +} + +@media (max-width: 500px) { + .app-container { + padding: 12px; + } + .brand-header h1 { + font-size: 2.1rem; + } + .slot-machine-cabinet { + padding: 12px; + } + .slot-cell { + font-size: 2.5rem; + } + .reels-container { + height: 200px; + } + .slot-cell { + height: 66.6px; + } + @keyframes roll-spin { + 0% { transform: translateY(0); filter: blur(2px); } + 100% { transform: translateY(-266.4px); filter: blur(6px); } + } + .line-row-1 { top: calc(16px + 33.3px - 2px); } + .line-row-2 { top: calc(16px + 100px - 2px); } + .line-row-3 { top: calc(16px + 166.7px - 2px); } +} diff --git a/agy.md b/agy.md new file mode 100644 index 0000000..191c6cf --- /dev/null +++ b/agy.md @@ -0,0 +1,111 @@ +# Premium Python Slot Machine Game + +A terminal-based slot machine simulation game written in Python featuring rich formatting, spin animations, emoji themes, session statistics, and local JSON profile persistence. + +--- + +## Project Structure + +- **[Slot machine python project/main.py](file:///C:/Users/navam/PycharmProjects/PythonProject/Slot%20machine%20python%20project/main.py)**: The main entry point and source code containing game logic, interactive menus, layout rendering, and profile storage. +- **`Slot machine python project/player_profiles.json`**: Auto-generated file storing player balances and session stats. + +--- + +## 💎 Premium Features + +1. **Terminal Colors**: High-contrast, clean UI styling utilizing standard ANSI escape sequences for text colors (green for wins, red for losses/errors, yellow for headers). +2. **Reel Spin Animation**: A visual ease-out spinning animation where random symbols flash and slow down gradually, simulating a real casino slot machine. +3. **Emoji Reels**: Uses high-definition emojis instead of plain characters: + - `💎` (Diamond) — Jackpot / Payout: 5x + - `🔔` (Bell) — Payout: 4x + - `🍋` (Lemon) — Payout: 3x + - `🍒` (Cherry) — Payout: 2x +4. **Session Stats Tracker**: Real-time stats are tracked during gameplay and can be viewed inside a dedicated dashboard menu: + - Total Spins + - Win Rate (%) + - Total Amount Bet + - Total Amount Won + - Net Profit/Loss (dynamic green/red highlights) + - Biggest Single Win +5. **Interactive Main Menu**: + - `1. Spin the Slots` + - `2. View Rules & Payouts` + - `3. View Session Statistics` + - `4. Deposit More Funds` + - `5. Change Profile / Log Out` + - `6. Save & Quit` + +--- + +## 💾 Profile Persistence & JSON Storage + +Game state is persisted to a local `player_profiles.json` file stored in the script's directory. + +### Data Scheme + +```json +{ + "Alice": { + "balance": 500, + "stats": { + "total_spins": 12, + "total_won": 140, + "total_bet": 120, + "biggest_win": 60, + "rounds_won": 3 + } + }, + "Bob": { + "balance": 1000, + "stats": { + "total_spins": 0, + "total_won": 0, + "total_bet": 0, + "biggest_win": 0, + "rounds_won": 0 + } + } +} +``` + +### Launch Sequence +1. Upon execution, the game loads all stored accounts from `player_profiles.json`. +2. The player is prompted to choose an existing profile (listing balances), create a new profile with an initial deposit, or exit. +3. Game states (balances and statistics) are automatically saved to `player_profiles.json` after every spin, deposit, profile logout, and exit. + +--- + +## 🛠️ Code Fixes Applied + +### 1. Indentation Bug in `check_winnings` +The logic error in [main.py](file:///C:/Users/navam/PycharmProjects/PythonProject/Slot%20machine%20python%20project/main.py) where symbol checking was placed outside the line iteration loop has been fully corrected. The checking loop is now correctly nested inside the line loop: + +```python +def check_winnings(columns, lines, bet, values): + winnings = 0 + winning_lines = [] + for line in range(lines): + symbol = columns[0][line] + for column in columns: + symbol_to_check = column[line] + if symbol_to_check != symbol: + break + else: + winnings += values[symbol] * bet + winning_lines.append(line + 1) + + return winnings, winning_lines +``` + +### 2. Auto-ANSI Escape Support on Windows +Added `os.system("")` on launch within `main()` to ensure that ANSI escape codes (colors and cursor movement animations) render correctly on legacy Windows command prompts. + +--- + +## How to Run + +Open your terminal, navigate to the root directory of the project, and run: + +```bash +python "Slot machine python project/main.py" +```