پروژه چهارم : همستر کامبت (ساده)

خب برای این پروژه وسایلی که نیاز دارید :

  • برنامه پایتون

  • کتابخانه tkinter

  • کتابخانه json

  • کتابخانه datetime

  • کتابخانه random

  • کتابخانه os

  • کتابخانه math

کتابخانه های لازم را نصب کنید و کد زیر را دخل آن وارد کنید.

import tkinter as tk
from tkinter import ttk  # For themed widgets like Notebook (tabs)
import json
import datetime
import random
import os
import math # For ceiling function

SETTINGS_FILE = "hamster_combat_settings.json"

# --- Initial Data ---
INITIAL_UPGRADES = {
    "tap_boost": {"name": "Tap Boost", "description": "Increases balance per tap", "base_cost": 100, "cost_multiplier": 1.15, "tap_increase": 1, "level": 0, "icon": "⚡"},
    "hourly_profit_boost": {"name": "Hourly Profit Boost", "description": "Increases profit per hour", "base_cost": 500, "cost_multiplier": 1.2, "hourly_profit": 10, "level": 0, "icon": "💰"},
    "cost_reduction": {"name": "Cost Reduction", "description": "Reduces upgrade costs", "base_cost": 1000, "cost_multiplier": 1.3, "cost_reduction_percent": 0.05, "level": 0, "icon": "📉"}, # 5% reduction
    "special_card_chance": {"name": "Special Card Chance", "description": "Increases chance of special cards", "base_cost": 2000, "cost_multiplier": 1.25, "special_card_chance_boost": 0.02, "level": 0, "icon": "❓"} # 2% boost
}

SPECIAL_CARDS = {
    "double_tap": {"name": "Double Tap", "duration": 60, "effect": lambda balance, tap_value: (balance + tap_value * 2, tap_value * 2), "description": "Tap value doubled", "icon": "💥"},
    "hourly_boost_x5": {"name": "Hourly Boost x5", "duration": 120, "effect": lambda profit: profit * 5, "description": "Hourly profit x5", "icon": "🚀"},
    "free_upgrade": {"name": "Free Upgrade", "duration": 10, "effect": lambda: True, "description": "Next upgrade is free", "icon": "🎁"}
}

# --- Colors and Fonts ---
BG_COLOR = '#f0e6d2'
FRAME_COLOR = '#e8dcc2'
BUTTON_COLOR = '#a08a7a'
BUTTON_HOVER_COLOR = '#8a7a6a'
TEXT_COLOR_DARK = '#3a2a1a'
TEXT_COLOR_MEDIUM = '#5a4a3a'

FONT_LARGE_BOLD = ('Arial', 24, 'bold')
FONT_MEDIUM_BOLD = ('Arial', 18, 'bold')
FONT_MEDIUM = ('Arial', 16)
FONT_SMALL_BOLD = ('Arial', 14, 'bold')
FONT_SMALL = ('Arial', 12)
FONT_TINY_ITALIC = ('Arial', 10, 'italic')

# --- Game Class ---
class HamsterCombatGame:
    def __init__(self, master):
        self.master = master
        self.master.title("Hamster Combat Sim")
        self.master.geometry("900x700") # Adjusted size for better layout
        self.master.configure(bg=BG_COLOR)

        # --- Game State Variables ---
        self.balance = tk.IntVar(value=0)
        self.hourly_profit = tk.IntVar(value=0)
        self.tap_value = tk.IntVar(value=1)
        self.current_cost_multiplier = tk.DoubleVar(value=1.0)
        self.special_card_chance_boost = tk.DoubleVar(value=0.0)
        self.active_special_card = None
        self.special_card_end_time = None
        self.last_hourly_profit_time = datetime.datetime.now()
        self.original_hourly_profit_for_boost = 0 # To store profit before x5 boost

        self.upgrades = self.load_settings()
        self.save_settings_on_exit()

        self.create_main_layout()
        self.update_display()
        self.add_hourly_profit_periodically()
        self.check_special_cards()
        self.trigger_random_special_card() # Start checking for special cards
        self.auto_save()

    def create_main_layout(self):
        # --- Main Notebook (Tabbed Interface) ---
        self.notebook = ttk.Notebook(self.master)
        self.notebook.pack(pady=10, padx=10, expand=True, fill='both')

        # --- Frame Colors for Notebook ---
        style = ttk.Style()
        style.configure("TNotebook", background=BG_COLOR, borderwidth=0)
        style.map("TNotebook", background=[("selected", BG_COLOR)]) # Make selected tab background match
        style.configure("TNotebook.Tab", padding=[10, 5], font=FONT_SMALL_BOLD, background=FRAME_COLOR, foreground=TEXT_COLOR_MEDIUM)
        style.map("TNotebook.Tab", background=[("selected", BG_COLOR)], foreground=[("selected", TEXT_COLOR_DARK)])

        # --- Create individual tabs ---
        self.home_tab = ttk.Frame(self.notebook, padding=15, style="TFrame")
        self.upgrades_tab = ttk.Frame(self.notebook, padding=15, style="TFrame")
        self.mines_tab = ttk.Frame(self.notebook, padding=15, style="TFrame") # Placeholder for future expansion

        self.notebook.add(self.home_tab, text='  🏠 Home  ')
        self.notebook.add(self.upgrades_tab, text='  🛠️ Upgrades  ')
        self.notebook.add(self.mines_tab, text='  ⛏️ Mines  ')

        # --- Populate Tabs ---
        self.create_home_tab_widgets()
        self.create_upgrades_tab_widgets()
        # self.create_mines_tab_widgets() # Call if mines tab is implemented

    # --- Home Tab ---
    def create_home_tab_widgets(self):
        home_frame = self.home_tab

        # Top Panel: Balance and Profit Display
        top_panel = tk.Frame(home_frame, bg=FRAME_COLOR, pady=15, relief=tk.RIDGE, borderwidth=2)
        top_panel.pack(fill=tk.X, pady=(0, 20))

        tk.Label(top_panel, text="Hamster Coin:", font=FONT_LARGE_BOLD, bg=FRAME_COLOR, fg=TEXT_COLOR_DARK).pack(pady=(0, 5))
        self.balance_label = tk.Label(top_panel, textvariable=self.balance, font=('Arial', 48, 'bold'), bg=FRAME_COLOR, fg=TEXT_COLOR_DARK)
        self.balance_label.pack(pady=5)

        self.profit_label = tk.Label(home_frame, text="", font=FONT_MEDIUM, bg=BG_COLOR, fg=TEXT_COLOR_MEDIUM)
        self.profit_label.pack(pady=5)

        # Central Area: Click Button
        click_area = tk.Frame(home_frame, bg=BG_COLOR)
        click_area.pack(pady=30, expand=True)

        self.click_button = tk.Button(click_area, text="Click Me!", font=('Impact', 40), bg=BUTTON_COLOR, fg='white',
                                       activebackground=BUTTON_HOVER_COLOR, activeforeground='white',
                                       command=self.increase_balance_by_tap, relief=tk.FLAT, borderwidth=0, # Flat button style
                                       padx=30, pady=20, width=10) # Increased padding for button size
        self.click_button.pack(pady=20)
        # Add a subtle visual effect on hover (optional, requires more advanced binding)

        # Special Card Info Display
        self.special_card_label = tk.Label(home_frame, text="", font=FONT_MEDIUM_BOLD, bg=BG_COLOR, fg='#d85000')
        self.special_card_label.pack(pady=10)

    # --- Upgrades Tab ---
    def create_upgrades_tab_widgets(self):
        upgrade_frame = self.upgrades_tab

        # Canvas for scrollable upgrades
        self.upgrade_canvas = tk.Canvas(upgrade_frame, bg=BG_COLOR, highlightthickness=0)
        self.upgrade_scrollbar = ttk.Scrollbar(upgrade_frame, orient="vertical", command=self.upgrade_canvas.yview)
        self.scrollable_upgrade_frame = tk.Frame(self.upgrade_canvas, bg=BG_COLOR)

        self.upgrade_scrollbar.pack(side="right", fill="y")
        self.upgrade_canvas.pack(side="left", fill="both", expand=True)
        self.upgrade_canvas.configure(yscrollcommand=self.upgrade_scrollbar.set)
        self.upgrade_canvas.create_window((0, 0), window=self.scrollable_upgrade_frame, anchor="nw")
        self.scrollable_upgrade_frame.bind("<Configure>", lambda e: self.upgrade_canvas.configure(scrollregion=self.upgrade_canvas.bbox("all")))

        self.load_upgrade_buttons()

    def load_upgrade_buttons(self):
        # Clear previous buttons if any
        for widget in self.scrollable_upgrade_frame.winfo_children():
            widget.destroy()

        row_num = 0
        for key, upgrade_data in sorted(self.upgrades.items()): # Sort for consistent order
            frame = tk.Frame(self.scrollable_upgrade_frame, bg=FRAME_COLOR, relief=tk.RAISED, borderwidth=2, padx=10, pady=8)
            frame.grid(row=row_num, column=0, sticky="ew", padx=5, pady=5)

            # Icon and Name
            icon = upgrade_data.get("icon", "")
            tk.Label(frame, text=f"{icon} {upgrade_data['name']} (Lv. {upgrade_data['level']})", font=FONT_SMALL_BOLD, bg=FRAME_COLOR, fg=TEXT_COLOR_DARK, anchor='w').grid(row=0, column=0, sticky="w", padx=(5, 0))

            # Description
            desc = upgrade_data.get('description', '')
            tk.Label(frame, text=desc, font=FONT_TINY_ITALIC, bg=FRAME_COLOR, fg=TEXT_COLOR_MEDIUM, anchor='w').grid(row=1, column=0, sticky="w", padx=(5, 0))

            # Cost and Buy Button
            cost = self.calculate_upgrade_cost(key)
            cost_str = f"{cost:,}" # Formatted cost
            button_text = "خرید"
            button_color = BUTTON_COLOR
            button_hover_color = BUTTON_HOVER_COLOR

            # Check if enough balance
            if self.balance.get() < cost:
                button_text = "موجودی کافی نیست"
                button_color = '#cccccc' # Greyed out
                button_hover_color = '#cccccc'

            buy_button = tk.Button(frame, text=f"{button_text} ({cost_str})", font=FONT_SMALL, command=lambda k=key: self.buy_upgrade(k), bg=button_color, fg='white',
                                    activebackground=button_hover_color, activeforeground='white', relief=tk.FLAT, borderwidth=0, padx=10, pady=5)
            buy_button.grid(row=0, column=1, rowspan=2, sticky="e", padx=5)

            row_num += 1
        
        # Configure grid weights for responsiveness
        self.scrollable_upgrade_frame.grid_columnconfigure(0, weight=1) # Upgrade info takes available space
        self.scrollable_upgrade_frame.grid_columnconfigure(1, weight=0) # Button takes minimum space


    # --- Core Game Logic (mostly unchanged, minor tweaks for UI) ---

    def load_settings(self):
        if os.path.exists(SETTINGS_FILE):
            try:
                with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    loaded_upgrades = {}
                    # Load upgrades ensuring all fields exist
                    for key, initial_data in INITIAL_UPGRADES.items():
                        upgrade_data = data.get("upgrades", {}).get(key, initial_data.copy())
                        for field, default_value in initial_data.items():
                            if field not in upgrade_data:
                                upgrade_data[field] = default_value
                        loaded_upgrades[key] = upgrade_data
                    
                    # Ensure upgrades dictionary always contains keys from INITIAL_UPGRADES
                    for key, initial_data in INITIAL_UPGRADES.items():
                        if key not in loaded_upgrades:
                            loaded_upgrades[key] = initial_data.copy()
                        else:
                            # Ensure all fields are present in loaded upgrades
                            for field, default_val in initial_data.items():
                                if field not in loaded_upgrades[key]:
                                    loaded_upgrades[key][field] = default_val
                    
                    self.balance.set(data.get("balance", 0))
                    self.hourly_profit.set(data.get("hourly_profit", 0))
                    self.tap_value.set(data.get("tap_value", 1))
                    self.active_special_card = data.get("active_special_card", None)
                    
                    if self.active_special_card:
                         try:
                             end_time_str = data.get("special_card_end_time")
                             if end_time_str:
                                 self.special_card_end_time = datetime.datetime.fromisoformat(end_time_str)
                             else: self.special_card_end_time = None
                         except ValueError:
                              self.active_special_card, self.special_card_end_time = None, None
                    else: self.special_card_end_time = None

                    self.last_hourly_profit_time = datetime.datetime.fromisoformat(data.get("last_hourly_profit_time", datetime.datetime.now().isoformat()))
                    self.current_cost_multiplier.set(data.get("current_cost_multiplier", 1.0))
                    self.special_card_chance_boost.set(data.get("special_card_chance_boost", 0.0))
                    self.original_hourly_profit_for_boost = data.get("original_hourly_profit_for_boost", 0) # Load boost value too

                    return loaded_upgrades
            except (json.JSONDecodeError, FileNotFoundError, ValueError, KeyError) as e:
                print(f"Error loading settings: {e}. Creating default settings.")
                return self.create_default_settings()
        else:
            return self.create_default_settings()

    def create_default_settings(self):
        default_upgrades = {}
        for key, value in INITIAL_UPGRADES.items():
            default_upgrades[key] = value.copy()
        return default_upgrades

    def save_settings_on_exit(self):
        self.master.protocol("WM_DELETE_WINDOW", self.on_exit)

    def on_exit(self):
        self.save_settings()
        self.master.destroy()

    def save_settings(self):
        settings = {
            "balance": self.balance.get(),
            "hourly_profit": self.hourly_profit.get(),
            "tap_value": self.tap_value.get(),
            "upgrades": self.upgrades,
            "active_special_card": self.active_special_card,
            "special_card_end_time": self.special_card_end_time.isoformat() if self.special_card_end_time else None,
            "last_hourly_profit_time": self.last_hourly_profit_time.isoformat(),
            "current_cost_multiplier": self.current_cost_multiplier.get(),
            "special_card_chance_boost": self.special_card_chance_boost.get(),
            "original_hourly_profit_for_boost": self.original_hourly_profit_for_boost # Save boost value
        }
        try:
            with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
                json.dump(settings, f, indent=4, ensure_ascii=False)
        except IOError as e:
            print(f"Error saving settings: {e}")

    def recalculate_profits(self):
        base_tap = 1
        base_hourly = 0
        cost_reduction_percent = 0.0

        # Recalculate based on current upgrade levels
        for key, upgrade_data in self.upgrades.items():
            level = upgrade_data.get('level', 0)
            if level > 0:
                if key == "tap_boost":
                    base_tap += upgrade_data.get("tap_increase", 0) * level
                elif key == "hourly_profit_boost":
                    base_hourly += upgrade_data.get("hourly_profit", 0) * level
                elif key == "cost_reduction":
                    cost_reduction_percent += upgrade_data.get("cost_reduction_percent", 0.0) * level
                elif key == "special_card_chance":
                    self.special_card_chance_boost.set(upgrade_data.get("special_card_chance_boost", 0.0) * level)

        self.tap_value.set(base_tap)
        # Apply hourly boost multiplier ONLY IF the card is active and it's the x5 boost
        current_hourly_profit = base_hourly
        if self.active_special_card == "hourly_boost_x5":
             # Ensure original_hourly_profit_for_boost is set correctly before applying multiplier
             self.original_hourly_profit_for_boost = base_hourly # Store the base hourly profit
             current_hourly_profit = self.original_hourly_profit_for_boost * SPECIAL_CARDS["hourly_boost_x5"]["effect"](1) # Apply the multiplier
        else:
             self.original_hourly_profit_for_boost = 0 # Reset if not x5 boost card
             current_hourly_profit = base_hourly # Use the base hourly profit

        self.hourly_profit.set(current_hourly_profit)
        self.current_cost_multiplier.set(max(0.1, 1.0 - cost_reduction_percent))


    def calculate_upgrade_cost(self, upgrade_key):
        upgrade_data = self.upgrades[upgrade_key]
        base_cost = upgrade_data["base_cost"]
        level = upgrade_data["level"]
        cost_multiplier = upgrade_data["cost_multiplier"]
        effective_cost_multiplier = self.current_cost_multiplier.get()
        
        # Calculate cost: base * (multiplier^level) * reduction_multiplier
        cost = base_cost * (cost_multiplier ** level) * effective_cost_multiplier
        return math.ceil(cost) # Use ceiling to ensure cost is always rounded up

    def increase_balance_by_tap(self):
        tap = self.tap_value.get()
        current_tap_value = tap

        # Apply special card effect if active
        if self.active_special_card == "double_tap":
            current_tap_value *= 2
            self.balance.set(self.balance.get() + current_tap_value)
            self.update_display()
            # Optionally, consume the double tap card after one use if it's meant to be single-use per activation
            # self.deactivate_special_card() # Uncomment if double tap should only apply once per activation
        else:
            self.balance.set(self.balance.get() + current_tap_value)
            self.update_display()

    def buy_upgrade(self, upgrade_key):
        upgrade_data = self.upgrades[upgrade_key]
        cost = self.calculate_upgrade_cost(upgrade_key)
        is_free_upgrade_card = (self.active_special_card == "free_upgrade")

        if is_free_upgrade_card:
            # If free upgrade card is active, the cost is effectively zero for this purchase
            # But we still need to consume the card
            pass # Cost is ignored, card will be consumed later
        elif self.balance.get() < cost:
            print("Not enough balance!") # Show message to user
            return # Do not proceed if balance is insufficient and not a free card

        # If we reach here, either balance is sufficient OR it's a free upgrade card
        
        # Deduct cost only if it's not a free upgrade card purchase
        if not is_free_upgrade_card:
            self.balance.set(self.balance.get() - cost)

        # Increase level and update data
        upgrade_data["level"] += 1
        self.upgrades[upgrade_key] = upgrade_data
        
        # Recalculate profits/tap value AFTER the upgrade level is updated
        self.recalculate_profits()
        
        # Consume the free upgrade card if it was used
        if is_free_upgrade_card:
            self.deactivate_special_card()

        self.update_display()
        self.load_upgrade_buttons() # Refresh the upgrade buttons to show new costs/levels

    def add_hourly_profit_periodically(self):
        now = datetime.datetime.now()
        time_elapsed = (now - self.last_hourly_profit_time).total_seconds()
        
        # Calculate profit based on current hourly_profit value
        # Ensure hourly_profit reflects any active multipliers (like x5 boost)
        profit_per_second = self.hourly_profit.get() / 3600.0
        earned_profit = int(profit_per_second * time_elapsed)

        if earned_profit > 0:
            self.balance.set(self.balance.get() + earned_profit)
            self.last_hourly_profit_time = now # Reset timer only when profit is earned
            self.update_display()

        # Schedule next check
        self.master.after(60000, self.add_hourly_profit_periodically) # Check every minute

    def check_special_cards(self):
        if self.active_special_card and self.special_card_end_time:
            if datetime.datetime.now() >= self.special_card_end_time:
                self.deactivate_special_card()
            else:
                self.update_special_card_label()
        self.master.after(1000, self.check_special_cards) # Check every second

    def update_special_card_label(self):
        if self.active_special_card:
            card_info = SPECIAL_CARDS.get(self.active_special_card)
            if card_info and self.special_card_end_time:
                remaining_time = int((self.special_card_end_time - datetime.datetime.now()).total_seconds())
                icon = card_info.get("icon", "")
                self.special_card_label.config(text=f"{icon} {card_info['name']} ({remaining_time}s)")
            else: # Card info missing or time ended but not caught?
                self.special_card_label.config(text="")
                self.active_special_card = None
                self.special_card_end_time = None
        else:
            self.special_card_label.config(text="")

    def deactivate_special_card(self):
        if self.active_special_card:
            card_info = SPECIAL_CARDS.get(self.active_special_card)
            print(f"Special card '{card_info['name']}' ended.") # Log message

            # Revert effects if necessary
            if self.active_special_card == "hourly_boost_x5":
                 # Restore hourly profit to its base value before the boost
                 self.hourly_profit.set(self.original_hourly_profit_for_boost)
                 self.original_hourly_profit_for_boost = 0 # Clear stored value
            elif self.active_special_card == "double_tap":
                 # Tap value is read dynamically, so no explicit revert needed here
                 pass
            elif self.active_special_card == "free_upgrade":
                 # No ongoing effect to revert
                 pass

            self.active_special_card = None
            self.special_card_end_time = None
            self.update_special_card_label()
            self.update_display() # Update display to reflect changes if any

    def trigger_random_special_card(self):
        base_chance = 0.01 # 1% base chance
        total_chance = base_chance + self.special_card_chance_boost.get()

        if not self.active_special_card and random.random() < total_chance:
            card_key = random.choice(list(SPECIAL_CARDS.keys()))
            self.activate_special_card(card_key)

        self.master.after(5000, self.trigger_random_special_card) # Check every 5 seconds

    def activate_special_card(self, card_key):
        if card_key in SPECIAL_CARDS:
            card_info = SPECIAL_CARDS[card_key]
            duration = card_info["duration"]
            self.active_special_card = card_key
            self.special_card_end_time = datetime.datetime.now() + datetime.timedelta(seconds=duration)

            # Apply immediate effect if any
            if card_key == "hourly_boost_x5":
                 self.original_hourly_profit_for_boost = self.hourly_profit.get() # Store current profit BEFORE applying boost
                 self.hourly_profit.set(self.original_hourly_profit_for_boost * card_info['effect'](1)) # Apply the multiplier
            elif card_key == "free_upgrade":
                 # Effect is applied during purchase, just mark it active
                 pass
            elif card_key == "double_tap":
                 # Effect applied during tap
                 pass
            
            print(f"Special card activated: {card_info['name']} for {duration} seconds.")
            self.update_special_card_label()
            self.update_display() # Update display to reflect changes immediately (e.g., hourly profit)
        else:
            print(f"Special card with key '{card_key}' not found.")

    def update_display(self):
        # Update balance label (formatted)
        self.balance_label.config(text=f"{self.balance.get():,}")

        # Update profit label with current tap value and hourly profit
        tap_val = self.tap_value.get()
        hourly_prof = self.hourly_profit.get()
        
        profit_text = f"Tap: {tap_val:,} | Hourly: {hourly_prof:,}"
        
        # Add indicators for active special cards
        if self.active_special_card == "double_tap":
             profit_text += " (Tap x2)"
        elif self.active_special_card == "hourly_boost_x5":
             profit_text += f" (Hourly x{SPECIAL_CARDS['hourly_boost_x5']['effect'](1)})"

        self.profit_label.config(text=profit_text)
        
        # Update costs on upgrade buttons (this is called after purchase or when needed)
        self.load_upgrade_buttons()


    def auto_save(self):
        self.save_settings()
        self.master.after(300000, self.auto_save) # Save every 5 minutes

# --- Main Execution ---
if __name__ == "__main__":
    root = tk.Tk()
    game = HamsterCombatGame(root)
    root.mainloop()

خب کد را اجرا کنید ، صفحه پایین را می بینید.

فایل اجرایی
فایل اجرایی

اینم از این ، نسخه سادش هست و خودتون این را ارتقا بدهید .

برای دانلود همین پایتون یا فایل تبدیل شده به برنامه روی نوشته لینک بزنید و 15ثانیه صبر کنید و فایل را دریافت کنید .

اگر ایده یا پروژه ای دیگری میخواهید در نظر ها بنویسید .