Hi ! Debug, enhance and optimize the following game. Generate the full code here, so i can copy/paste and run. import pickle
import random
import time
from datetime import datetime, timedelta

# ====================== Item Classes ======================

class Item:
    def __init__(self, name, description, quantity=1):
        self.name = name.lower()
        self.description = description
        self.quantity = quantity

    def use(self, player, game):
        print(f"You can't use the {self.name}.")

    def __str__(self):
        return f"{self.name.capitalize()} (x{self.quantity}): {self.description}"

class Weapon(Item):
    def __init__(self, name, description, damage, upgrade_level=0, quantity=1):
        super().__init__(name, description, quantity)
        self.damage = damage
        self.upgrade_level = upgrade_level

    def use(self, player, game):
        player.equip_weapon(self)
        print(f"You have equipped the {self.name}.")

    def upgrade(self):
        self.upgrade_level += 1
        self.damage += 5
        print(f"{self.name} has been upgraded to level {self.upgrade_level}! Damage increased to {self.damage}.")

class Consumable(Item):
    def __init__(self, name, description, heal_amount, quantity=1):
        super().__init__(name, description, quantity)
        self.heal_amount = heal_amount

    def use(self, player, game):
        if self.quantity > 0:
            player.heal(self.heal_amount)
            self.quantity -= 1
            print(f"You used a {self.name}. Your health is now {player.health}/{player.max_health}.")
            if self.quantity <= 0:
                player.remove_from_inventory(self)
        else:
            print(f"You don't have any {self.name} left.")

class Craftable(Item):
    def __init__(self, name, description, recipe, quantity=1):
        super().__init__(name, description, quantity)
        self.recipe = recipe  # dict of item_name: required_quantity

    def can_craft(self, player):
        for item_name, qty in self.recipe.items():
            if not player.has_item_quantity(item_name, qty):
                return False
        return True

    def craft(self, player, game):
        if self.can_craft(player):
            for item_name, qty in self.recipe.items():
                player.remove_specific_item(item_name, qty)
            crafted_item = Item(self.name, self.description, self.quantity)
            player.add_to_inventory(crafted_item)
            print(f"You have crafted a {self.name}.")
            game.update_quests("craft", self.name)
            return True
        else:
            print("You don't have the required items to craft this.")
            return False

class LockedChest(Item):
    def __init__(self, name, description, locked=True, contents=None, quantity=1):
        super().__init__(name, description, quantity)
        self.locked = locked
        self.contents = contents or []

    def use(self, player, game):
        if self.locked:
            key = player.has_item('key')
            if key:
                self.locked = False
                player.remove_specific_item('key', 1)
                print("You used a key to unlock the chest.")
                if self.contents:
                    for item in self.contents:
                        player.add_to_inventory(item)
                    self.contents = []
                else:
                    print("The chest is empty.")
                game.update_quests("chest_opened", self.name)
            else:
                print("The chest is locked. You need a key to open it.")
        else:
            if self.contents:
                for item in self.contents:
                    player.add_to_inventory(item)
                self.contents = []
            else:
                print("The chest is empty.")

class Armor(Item):
    def __init__(self, name, description, defense, upgrade_level=0, quantity=1):
        super().__init__(name, description, quantity)
        self.defense = defense
        self.upgrade_level = upgrade_level

    def use(self, player, game):
        player.equip_armor(self)
        print(f"You have equipped the {self.name}.")

    def upgrade(self):
        self.upgrade_level += 1
        self.defense += 3
        print(f"{self.name} has been upgraded to level {self.upgrade_level}! Defense increased to {self.defense}.")

class Gold(Item):
    def __init__(self, quantity=1):
        super().__init__("gold", "Shiny gold coins used for trading.", quantity)

# ====================== Monster Class ======================

class Monster:
    def __init__(self, name, health, attack_power, defense, attack_chance=0.8, critical_chance=0.1, xp_reward=50, gold_reward=20):
        self.name = name
        self.health = health
        self.max_health = health
        self.attack_power = attack_power
        self.defense = defense
        self.attack_chance = attack_chance
        self.critical_chance = critical_chance
        self.xp_reward = xp_reward
        self.gold_reward = gold_reward

    def is_alive(self):
        return self.health > 0

    def attack(self, player):
        if random.random() <= self.attack_chance:
            if random.random() <= self.critical_chance:
                damage = max(0, int((self.attack_power * 2) - player.defense))
                print(f"The {self.name} lands a CRITICAL HIT dealing {damage} damage!")
            else:
                damage = max(0, self.attack_power - player.defense)
                print(f"The {self.name} attacks dealing {damage} damage.")
            player.health -= damage
            if player.health < 0:
                player.health = 0
            print(f"Your health is now {player.health}/{player.max_health}.")
        else:
            print(f"The {self.name} attempts to attack but misses!")

# ====================== Quest and Objective Classes ======================

class Objective:
    def __init__(self, objective_type, target, required_amount):
        self.objective_type = objective_type  # e.g., 'kill', 'collect', 'reach', 'chest_opened'
        self.target = target  # e.g., 'Goblin', 'Gold Coin', 'Mysterious Ruins', 'Locked Chest'
        self.required_amount = required_amount
        self.current_amount = 0

    def update_progress(self, amount=1):
        self.current_amount += amount
        if self.current_amount > self.required_amount:
            self.current_amount = self.required_amount

    def is_completed(self):
        return self.current_amount >= self.required_amount

    def __str__(self):
        return f"{self.objective_type.capitalize()} {self.target}: {self.current_amount}/{self.required_amount}"

class Quest:
    def __init__(self, name, description, reward, objectives):
        self.name = name
        self.description = description
        self.reward = reward  # list of Items
        self.objectives = [Objective(**obj) for obj in objectives]  # list of Objective objects
        self.completed = False

    def update_progress(self, objective_type, target=None):
        for obj in self.objectives:
            if obj.objective_type == objective_type:
                if target:
                    if obj.target.lower() == target.lower():
                        obj.update_progress()
                else:
                    obj.update_progress()
        self.check_completion()

    def check_completion(self):
        if all(obj.is_completed() for obj in self.objectives):
            self.complete()

    def complete(self):
        if not self.completed:
            self.completed = True
            print(f"\nCongratulations! You have completed the quest: {self.name}")
            print("Reward Received:")
            for item in self.reward:
                print(f"- {item.name.capitalize()} (x{item.quantity})")
            return self.reward
        else:
            print(f"Quest '{self.name}' is already completed.")
            return []

    def __str__(self):
        status = "Completed" if self.completed else "In Progress"
        objectives_str = '\n  '.join([str(obj) for obj in self.objectives])
        return (f"Quest: {self.name}\nDescription: {self.description}\nStatus: {status}\nObjectives:\n  {objectives_str}")

# ====================== Skill Classes ======================

class Skill:
    def __init__(self, name, description, cooldown=0):
        self.name = name
        self.description = description
        self.cooldown = cooldown  # in turns
        self.current_cooldown = 0

    def use(self, player, monster, game):
        print(f"You used {self.name}, but nothing happened.")
        return False

    def tick_cooldown(self):
        if self.current_cooldown > 0:
            self.current_cooldown -= 1

class PowerStrike(Skill):
    def __init__(self):
        super().__init__("Power Strike", "A powerful attack that deals extra damage.", cooldown=2)

    def use(self, player, monster, game):
        if self.current_cooldown == 0:
            if player.equipped_weapon:
                damage = max(0, int((player.equipped_weapon.damage * 1.5) - monster.defense))
                print(f"\nYou used Power Strike dealing {damage} damage to the {monster.name}!")
                monster.health -= damage
                player.score += damage
                self.current_cooldown = self.cooldown
                if monster.health <= 0:
                    print(f"\nYou have defeated the {monster.name} using Power Strike!")
                    player.score += 200
                    game.award_rewards(monster, player)
                    game.update_quests("kill", monster.name)
                    return True
            else:
                print("You have no weapon equipped.")
        else:
            print(f"The skill {self.name} is on cooldown for {self.current_cooldown} more turn(s).")
        return False

class Fireball(Skill):
    def __init__(self):
        super().__init__("Fireball", "A fiery spell that burns the enemy.", cooldown=3)

    def use(self, player, monster, game):
        if self.current_cooldown == 0:
            damage = max(0, int((15) - monster.defense))
            print(f"\nYou cast Fireball dealing {damage} fire damage to the {monster.name}!")
            monster.health -= damage
            player.score += damage
            self.current_cooldown = self.cooldown
            if monster.health <= 0:
                print(f"\nYou have defeated the {monster.name} using Fireball!")
                player.score += 200
                game.award_rewards(monster, player)
                game.update_quests("kill", monster.name)
                return True
        else:
            print(f"The skill {self.name} is on cooldown for {self.current_cooldown} more turn(s).")
        return False

class ArrowShot(Skill):
    def __init__(self):
        super().__init__("Arrow Shot", "A precise arrow aimed at the enemy.", cooldown=1)

    def use(self, player, monster, game):
        if self.current_cooldown == 0:
            damage = max(0, int((player.equipped_weapon.damage * 1.2) - monster.defense))
            print(f"\nYou used Arrow Shot dealing {damage} damage to the {monster.name}!")
            monster.health -= damage
            player.score += damage
            self.current_cooldown = self.cooldown
            if monster.health <= 0:
                print(f"\nYou have defeated the {monster.name} using Arrow Shot!")
                player.score += 200
                game.award_rewards(monster, player)
                game.update_quests("kill", monster.name)
                return True
        else:
            print(f"The skill {self.name} is on cooldown for {self.current_cooldown} more turn(s).")
        return False

class Heal(Skill):
    def __init__(self):
        super().__init__("Heal", "Restore a portion of your health.", cooldown=3)

    def use(self, player, monster, game):
        if self.current_cooldown == 0:
            heal_amount = 30
            player.heal(heal_amount)
            print(f"\nYou used Heal and restored {heal_amount} health. Current health: {player.health}/{player.max_health}.")
            self.current_cooldown = self.cooldown
            return True
        else:
            print(f"The skill {self.name} is on cooldown for {self.current_cooldown} more turn(s).")
        return False

# ====================== NPC Classes ======================

class NPC:
    def __init__(self, name, description, dialogues):
        self.name = name
        self.description = description
        self.dialogues = dialogues
        self.current_dialogue = 0

    def talk(self, player, game):
        while self.current_dialogue < len(self.dialogues):
            player_choices, npc_response, next_dialogues = self.dialogues[self.current_dialogue]
            print(f"\n{self.name}: {npc_response}")
            if not player_choices:
                self.current_dialogue += 1
                continue
            print("Your options:")
            for idx, choice in enumerate(player_choices, 1):
                print(f"{idx}. {choice}")
            choice = input("Enter the number of your choice: ").strip()
            if choice.isdigit() and 1 <= int(choice) <= len(player_choices):
                selected = player_choices[int(choice) - 1]
                print(f"\nYou: {selected}")
                self.current_dialogue += 1
            else:
                print("Invalid choice. Please try again.")
        print(f"{self.name} has nothing more to say.")

class FriendlyNPC(NPC):
    def __init__(self, name, description, dialogues, quest=None, gift=None):
        super().__init__(name, description, dialogues)
        self.quest = quest
        self.gift = gift
        self.has_received_gift = False

    def interact(self, player, game):
        self.talk(player, game)
        if self.quest and self.quest not in player.quests:
            player.add_quest(self.quest)
            print(f"{self.name} has given you a quest: {self.quest.name}")
            if self.gift and not self.has_received_gift:
                player.add_to_inventory(self.gift)
                print(f"{self.name} has given you a {self.gift.name}.")
                self.has_received_gift = True

class UnfriendlyNPC(NPC):
    def __init__(self, name, description, dialogues, monster):
        super().__init__(name, description, dialogues)
        self.monster = monster

    def interact(self, player, game):
        self.talk(player, game)
        print(f"{self.name} is hostile and attacks you!")
        game.battle(player, self.monster)

# ====================== Character Classes ======================

class CharacterClass:
    def __init__(self, name, description, base_health, base_defense, skills, xp_multiplier=1.0):
        self.name = name
        self.description = description
        self.base_health = base_health
        self.base_defense = base_defense
        self.skills = skills  # list of Skill instances
        self.xp_multiplier = xp_multiplier

class Player:
    def __init__(self, name, character_class):
        self.name = name
        self.character_class = character_class
        self.health = character_class.base_health
        self.max_health = character_class.base_health
        self.defense = character_class.base_defense
        self.inventory = []
        self.equipped_weapon = None
        self.equipped_armor = None
        self.quests = []
        self.score = 0
        self.lives = 3
        self.skills = character_class.skills.copy()
        self.level = 1
        self.xp = 0
        self.xp_to_next_level = 100
        self.gold = 0
        self.inventory_capacity = 20  # Limit the number of items

    def is_alive(self):
        return self.health > 0

    def attack(self, monster):
        if self.equipped_weapon:
            damage = max(0, self.equipped_weapon.damage - monster.defense)
            monster.health -= damage
            print(f"\nYou attack the {monster.name} with your {self.equipped_weapon.name} dealing {damage} damage.")
            self.score += damage
        else:
            damage = max(0, 10 - monster.defense)
            monster.health -= damage
            print(f"\nYou attack the {monster.name} with your bare hands dealing {damage} damage.")
            self.score += damage

    def heal(self, amount):
        self.health += amount
        if self.health > self.max_health:
            self.health = self.max_health

    def add_to_inventory(self, item):
        if self.get_inventory_count() + item.quantity > self.inventory_capacity:
            print("You don't have enough inventory space to carry this item.")
            return
        existing_item = next((i for i in self.inventory if i.name == item.name.lower()), None)
        if existing_item:
            existing_item.quantity += item.quantity
        else:
            self.inventory.append(item)
        print(f"\nAdded {item.quantity} x {item.name.capitalize()} to your inventory.")

    def get_inventory_count(self):
        return sum(item.quantity for item in self.inventory)

    def remove_from_inventory(self, item):
        if item in self.inventory:
            self.inventory.remove(item)

    def remove_specific_item(self, item_name, quantity):
        for item in self.inventory:
            if item.name == item_name.lower():
                if item.quantity > quantity:
                    item.quantity -= quantity
                    print(f"\nRemoved {quantity} x {item.name.capitalize()} from your inventory.")
                    return
                elif item.quantity == quantity:
                    self.inventory.remove(item)
                    print(f"\nRemoved {item.name.capitalize()} from your inventory.")
                    return
                else:
                    print(f"\nYou don't have enough {item_name} to remove.")
                    return
        print(f"\nYou don't have a {item_name} in your inventory.")

    def has_item(self, item_name):
        for item in self.inventory:
            if item.name == item_name.lower():
                return item
        return None

    def has_item_quantity(self, item_name, quantity):
        item = self.has_item(item_name)
        if item and item.quantity >= quantity:
            return True
        return False

    def equip_weapon(self, weapon):
        self.equipped_weapon = weapon
        print(f"\nYou have equipped the {weapon.name}.")

    def equip_armor(self, armor):
        if self.equipped_armor:
            self.defense -= self.equipped_armor.defense
            print(f"\nYou have unequipped the {self.equipped_armor.name}.")
        self.equipped_armor = armor
        self.defense += armor.defense
        print(f"\nYou have equipped the {armor.name}.")

    def unequip_armor(self):
        if self.equipped_armor:
            self.defense -= self.equipped_armor.defense
            print(f"\nYou have unequipped the {self.equipped_armor.name}.")
            self.equipped_armor = None
        else:
            print("\nYou have no armor equipped.")

    def show_inventory(self):
        if not self.inventory:
            print("\nYour inventory is empty.")
            return
        print("\n=== Inventory ===")
        for item in self.inventory:
            print(f"- {item.name.capitalize()} (x{item.quantity}): {item.description}")
        print(f"Gold: {self.gold}")
        print(f"Inventory Capacity: {self.get_inventory_count()}/{self.inventory_capacity}")
        print("=================\n")

    def add_quest(self, quest):
        if any(q.name == quest.name for q in self.quests):
            return  # Quest already added
        self.quests.append(quest)
        print(f"\nNew Quest Added: {quest.name}\nDescription: {quest.description}")

    def check_level_up(self):
        while self.xp >= self.xp_to_next_level:
            self.xp -= self.xp_to_next_level
            self.level += 1
            self.xp_to_next_level = int(self.xp_to_next_level * 1.5)
            self.max_health += 20
            self.health = self.max_health
            self.defense += 2
            print(f"\n*** Congratulations! You have reached Level {self.level}! ***")
            print(f"Health increased to {self.max_health}. Defense increased to {self.defense}.")

    def gain_xp(self, amount):
        self.xp += int(amount * self.character_class.xp_multiplier)
        print(f"\nYou gained {int(amount * self.character_class.xp_multiplier)} XP.")
        self.check_level_up()

    def upgrade_skill(self, skill_name, game):
        skill = next((s for s in self.skills if s.name.lower() == skill_name.lower()), None)
        if skill:
            if hasattr(skill, 'upgrade'):
                skill.upgrade()
                print(f"{skill.name} has been upgraded.")
            else:
                print("This skill cannot be upgraded.")
        else:
            print("You don't have that skill.")

    def upgrade_weapon(self, game):
        if self.equipped_weapon:
            if self.gold >= 50:
                self.gold -= 50
                self.equipped_weapon.upgrade()
                print("Weapon upgraded successfully!")
            else:
                print("You don't have enough gold to upgrade your weapon.")
        else:
            print("You have no weapon equipped.")

    def upgrade_armor(self, game):
        if self.equipped_armor:
            if self.gold >= 50:
                self.gold -= 50
                self.equipped_armor.upgrade()
                print("Armor upgraded successfully!")
            else:
                print("You don't have enough gold to upgrade your armor.")
        else:
            print("You have no armor equipped.")

    def __str__(self):
        weapon = self.equipped_weapon.name if self.equipped_weapon else "None"
        armor = self.equipped_armor.name if self.equipped_armor else "None"
        skills = ', '.join([skill.name for skill in self.skills]) if self.skills else 'None'
        return (f"Name: {self.name}\n"
                f"Class: {self.character_class.name}\n"
                f"Level: {self.level}\n"
                f"XP: {self.xp}/{self.xp_to_next_level}\n"
                f"Health: {self.health}/{self.max_health}\n"
                f"Defense: {self.defense}\n"
                f"Equipped Weapon: {weapon}\n"
                f"Equipped Armor: {armor}\n"
                f"Score: {self.score}\n"
                f"Lives: {self.lives}\n"
                f"Skills: {skills}\n"
                f"Gold: {self.gold}")

# ====================== Room Class with Coordinates ======================

class Room:
    def __init__(self, name, description, x, y, items=None, monsters=None, npcs=None, quests=None):
        self.name = name
        self.description = description
        self.x = x  # Coordinate x
        self.y = y  # Coordinate y
        self.items = items or []
        self.monsters = monsters or []
        self.npcs = npcs or []
        self.quests = quests or []
        self.connections = {}  # direction: Room

    def connect_rooms(self, direction, room):
        self.connections[direction.lower()] = room

    def get_room_in_direction(self, direction):
        return self.connections.get(direction.lower(), None)

    def describe(self):
        print(f"\n=== {self.name} ===")
        print(self.description)
        if self.items:
            print("\nItems in the room:")
            for item in self.items:
                print(f"- {item.name.capitalize()} (x{item.quantity}): {item.description}")
        if self.monsters:
            print("\nMonsters in the room:")
            for monster in self.monsters:
                print(f"- {monster.name} (Health: {monster.health}/{monster.max_health})")
        if self.npcs:
            print("\nNPCs in the room:")
            for npc in self.npcs:
                print(f"- {npc.name}: {npc.description}")
        if self.quests:
            print("\nQuests available in this room:")
            for quest in self.quests:
                print(f"- {quest.name}: {quest.description}")

# ====================== Game Class ======================

class Game:
    def __init__(self):
        self.game_time = datetime.now().replace(hour=9, minute=0, second=0, microsecond=0)
        self.player = self.create_player()
        self.current_room = self.create_world()
        self.running = True
        self.all_rooms = self.get_all_rooms()

    def create_player(self):
        print("=== Character Class Selection ===")
        classes = self.get_character_classes()
        for idx, cls in enumerate(classes, 1):
            print(f"{idx}. {cls.name} - {cls.description}")
        while True:
            choice = input("Select your character class by entering the number: ").strip()
            if choice.isdigit():
                choice_num = int(choice)
                if 1 <= choice_num <= len(classes):
                    selected_class = classes[choice_num - 1]
                    break
            print("Invalid selection. Please try again.")
        name = ""
        while not name:
            name = input("Enter your character's name: ").strip()
            if not name:
                print("Name cannot be empty. Please enter a valid name.")
        return Player(name, selected_class)

    def get_character_classes(self):
        warrior = CharacterClass(
            "Warrior",
            "A strong melee fighter with high health and defense.",
            base_health=150,
            base_defense=15,
            skills=[PowerStrike(), Heal()],
            xp_multiplier=1.0
        )
        mage = CharacterClass(
            "Mage",
            "A master of elemental magic with powerful spells.",
            base_health=100,
            base_defense=5,
            skills=[Fireball(), Heal()],
            xp_multiplier=1.2
        )
        archer = CharacterClass(
            "Archer",
            "A skilled marksman with ranged attacks and agility.",
            base_health=120,
            base_defense=10,
            skills=[ArrowShot(), Heal()],
            xp_multiplier=1.1
        )
        paladin = CharacterClass(
            "Paladin",
            "A holy warrior with healing and combat prowess.",
            base_health=140,
            base_defense=12,
            skills=[Heal(), PowerStrike()],
            xp_multiplier=1.05
        )
        rogue = CharacterClass(
            "Rogue",
            "A stealthy fighter with critical strikes and agility.",
            base_health=110,
            base_defense=8,
            skills=[ArrowShot(), PowerStrike()],
            xp_multiplier=1.15
        )
        return [warrior, mage, archer, paladin, rogue]

    def create_world(self):
        # Define Items
        sword = Weapon("Sword", "A sharp blade gleaming in the light.", damage=20)
        potion = Consumable("Potion", "Heals 25 health points.", heal_amount=25)
        fishing_rod = Craftable("Fishing Rod", "Assembled from a stick and string.", recipe={"stick": 1, "string": 1})
        key = Item("Key", "An old rusty key. It might open something.")
        gold = Gold(quantity=10)
        food = Consumable("Food", "Restores 20 health points.", heal_amount=20)
        water = Consumable("Water", "Quenches your thirst.", heal_amount=10)
        herbs = Consumable("Herbs", "Medicinal herbs for healing.", heal_amount=15)
        stick = Item("Stick", "A sturdy wooden stick.", quantity=5)
        string = Item("String", "A piece of durable string.", quantity=5)
        bow = Weapon("Bow", "A ranged weapon for attacking from afar.", damage=15)
        dagger = Weapon("Dagger", "A small blade ideal for quick attacks.", damage=10)
        shield = Armor("Shield", "Provides additional defense.", defense=8)
        leather_armor = Armor("Leather Armor", "Light armor made of tough leather.", defense=5)
        chainmail = Armor("Chainmail", "Sturdy chainmail offering better protection.", defense=10)

        # Define Monsters
        goblin = Monster("Goblin", health=30, attack_power=8, defense=5, xp_reward=50, gold_reward=20)
        orc = Monster("Orc", health=50, attack_power=12, defense=8, xp_reward=80, gold_reward=35)
        troll = Monster("Troll", health=80, attack_power=15, defense=10, xp_reward=120, gold_reward=50)
        skeleton = Monster("Skeleton", health=25, attack_power=7, defense=4, xp_reward=40, gold_reward=15)
        bandit = Monster("Bandit", health=40, attack_power=10, defense=6, xp_reward=60, gold_reward=25)
        hydra = Monster("Hydra", health=120, attack_power=18, defense=12, attack_chance=0.7, critical_chance=0.15, xp_reward=200, gold_reward=80)
        dark_wizard = Monster("Dark Wizard", health=70, attack_power=20, defense=5, attack_chance=0.8, critical_chance=0.25, xp_reward=250, gold_reward=100)

        # Define Quests
        quest1 = Quest(
            name="Goblin Menace",
            description="Eliminate 3 goblins causing trouble in the cave and collect 2 gold coins.",
            reward=[potion, gold],
            objectives=[
                {"objective_type": "kill", "target": "Goblin", "required_amount": 3},
                {"objective_type": "collect", "target": "Gold", "required_amount": 2}
            ]
        )
        quest2 = Quest(
            name="Crafting Basics",
            description="Craft a fishing rod using a stick and string.",
            reward=[food],
            objectives=[
                {"objective_type": "craft", "target": "Fishing Rod", "required_amount": 1}
            ]
        )
        quest3 = Quest(
            name="Treasure Hunter",
            description="Find and open the locked chest in the mysterious ruins and reach the sanctuary.",
            reward=[water, herbs],
            objectives=[
                {"objective_type": "chest_opened", "target": "Locked Chest", "required_amount": 1},
                {"objective_type": "reach", "target": "Sanctuary", "required_amount": 1}
            ]
        )
        quest4 = Quest(
            name="Defeat the Dark Wizard",
            description="Defeat the Dark Wizard threatening the village.",
            reward=[chainmail],
            objectives=[
                {"objective_type": "kill", "target": "Dark Wizard", "required_amount": 1}
            ]
        )
        quest5 = Quest(
            name="Skeleton Challenge",
            description="Defeat 5 skeletons lurking in the dungeon.",
            reward=[food, gold],
            objectives=[
                {"objective_type": "kill", "target": "Skeleton", "required_amount": 5}
            ]
        )

        # Define NPCs
        elder_mira = FriendlyNPC(
            name="Elder Mira",
            description="A wise elder who knows much about the land.",
            dialogues=[
                (["Ask about the cave", "Ask about the river"], "Greetings, traveler. What would you like to know?", [0, 1]),
                (["Thank you"], "Safe travels!", [])
            ],
            quest=quest1,
            gift=sword
        )
        rokhar = UnfriendlyNPC(
            name="Rokhar",
            description="A nasty bandit looking for trouble.",
            dialogues=[
                (["Attack him"], "You'll regret challenging me!", [])
            ],
            monster=bandit
        )
        merchant = FriendlyNPC(
            name="Merchant Tom",
            description="A traveling merchant with various goods.",
            dialogues=[
                (["Buy Potion", "Buy Sword", "Buy Food"], "What would you like to buy?", [0, 1, 2]),
                (["Yes, I want to buy a Potion"], "Here's your Potion.", []),
                (["Yes, I want to buy a Sword"], "Here's your Sword.", []),
                (["Yes, I want to buy Food"], "Here's your Food.", [])
            ],
            quest=None,
            gift=None
        )
        sorcerer = FriendlyNPC(
            name="Sage Elara",
            description="A mysterious sorcerer offering knowledge.",
            dialogues=[
                (["Learn Fireball", "Ask about the dark wizard"], "What knowledge do you seek?", [0, 1]),
                (["I want to learn Fireball"], "Very well. I have bestowed upon you the Fireball spell.", []),
                (["Tell me about the dark wizard"], "The Dark Wizard wields immense power and seeks to dominate us all.", [])
            ],
            quest=quest4,
            gift=Fireball()  # Assuming learning a new skill gives a Fireball spell
        )
        healer = FriendlyNPC(
            name="Healer Lina",
            description="A compassionate healer offering assistance.",
            dialogues=[
                (["Ask for healing", "Ask about herbs"], "How can I assist you today?", [0, 1]),
                (["Yes, please heal me"], "You have been healed to full health.", []),
                (["Tell me about the herbs"], "Herbs are essential for crafting potions and healing items.", [])
            ],
            quest=None,
            gift=herbs
        )

        # Define Rooms with Coordinates
        village = Room(
            name="Village",
            description="A peaceful village with friendly inhabitants.",
            x=0,
            y=0,
            items=[leather_armor, potion, gold],
            npcs=[elder_mira, merchant, healer],
            quests=[quest4]
        )
        cave = Room(
            name="Cave",
            description="A dark cave where goblins lurk.",
            x=0,
            y=1,
            monsters=[goblin, goblin, goblin],
            quests=[]
        )
        clearing = Room(
            name="Clearing",
            description="A sunny clearing with a gentle stream.",
            x=1,
            y=1,
            items=[chainmail, bow],
            quests=[quest2]
        )
        river = Room(
            name="River",
            description="A flowing river. The sound of water is soothing.",
            x=1,
            y=2,
            items=[fishing_rod, stick, string, water],
            quests=[]
        )
        mysterious_ruins = Room(
            name="Mysterious Ruins",
            description="Ancient ruins filled with secrets.",
            x=2,
            y=2,
            items=[locked_chest],
            quests=[quest3],
            npcs=[sorcerer]
        )
        castle = Room(
            name="Abandoned Castle",
            description="An old castle with towering walls.",
            x=3,
            y=2,
            items=[key],
            npcs=[rokhar],
            quests=[]
        )
        forest = Room(
            name="Forest",
            description="A dense forest with towering trees and hidden paths.",
            x=-1,
            y=0,
            monsters=[orc],
            quests=[]
        )
        mountain = Room(
            name="Mountain",
            description="A steep mountain offering a challenging climb.",
            x=-1,
            y=1,
            monsters=[troll],
            quests=[]
        )
        dark_fortress = Room(
            name="Dark Fortress",
            description="A foreboding fortress where the Dark Wizard resides.",
            x=2,
            y=3,
            monsters=[dark_wizard],
            quests=[]
        )
        dungeon = Room(
            name="Dungeon",
            description="A dank dungeon filled with the undead.",
            x=3,
            y=1,
            monsters=[skeleton, skeleton, skeleton],
            quests=[quest5]
        )
        oasis = Room(
            name="Oasis",
            description="A refreshing oasis in the midst of the desert.",
            x=4,
            y=2,
            items=[water],
            quests=[]
        )
        sanctuary = Room(
            name="Sanctuary",
            description="A holy place offering peace and refuge.",
            x=2,
            y=4,
            items=[shield],
            quests=[]
        )

        # Connect Rooms
        village.connect_rooms("north", cave)
        cave.connect_rooms("south", village)
        cave.connect_rooms("east", clearing)
        clearing.connect_rooms("west", cave)
        clearing.connect_rooms("east", river)
        river.connect_rooms("west", clearing)
        river.connect_rooms("north", mysterious_ruins)
        mysterious_ruins.connect_rooms("south", river)
        mysterious_ruins.connect_rooms("east", castle)
        castle.connect_rooms("west", mysterious_ruins)
        castle.connect_rooms("east", dark_fortress)
        dark_fortress.connect_rooms("west", castle)
        village.connect_rooms("east", forest)
        forest.connect_rooms("west", village)
        forest.connect_rooms("north", mountain)
        mountain.connect_rooms("south", forest)
        mountain.connect_rooms("east", dungeon)
        dungeon.connect_rooms("west", mountain)
        dungeon.connect_rooms("east", oasis)
        oasis.connect_rooms("west", dungeon)
        oasis.connect_rooms("north", sanctuary)
        sanctuary.connect_rooms("south", oasis)

        return village

    def get_all_rooms(self):
        # Perform BFS to collect all rooms
        visited = set()
        queue = [self.current_room]
        all_rooms = []
        while queue:
            current = queue.pop(0)
            if current in visited:
                continue
            visited.add(current)
            all_rooms.append(current)
            for connected_room in current.connections.values():
                if connected_room not in visited:
                    queue.append(connected_room)
        return all_rooms

    # ====================== Utility Functions ======================

    def display_choices(self, choices):
        print("\n=== Available Actions ===")
        for idx, choice in enumerate(choices, 1):
            print(f"{idx}. {choice}")
        print("M. Map")
        print("B. Back")
        print("Q. Quit")
        print("S. Save")

    def get_user_choice(self, choices):
        while True:
            choice = input("\nEnter your choice: ").strip().lower()
            if choice.isdigit():
                num = int(choice)
                if 1 <= num <= len(choices):
                    return choices[num - 1].upper()
            elif choice in ['m', 'map']:
                return 'MAP'
            elif choice in ['b', 'back']:
                return 'BACK'
            elif choice in ['q', 'quit']:
                return 'QUIT'
            elif choice in ['s', 'save']:
                return 'SAVE'
            else:
                print("Invalid input. Please try again.")

    def save_game(self):
        try:
            with open("save_game.pkl", "wb") as f:
                pickle.dump(self, f)
            print("\nGame has been saved successfully!")
        except Exception as e:
            print(f"\nFailed to save the game: {e}")

    @staticmethod
    def load_game():
        try:
            with open("save_game.pkl", "rb") as f:
                game = pickle.load(f)
            print("\nGame loaded successfully!")
            return game
        except FileNotFoundError:
            print("\nNo saved game found. Starting a new game.")
            return None
        except Exception as e:
            print(f"\nFailed to load the game: {e}")
            return None

    def is_daytime(self):
        return 6 <= self.game_time.hour < 18

    def display_map(self):
        # Define map boundaries based on room coordinates
        rooms = self.all_rooms
        min_x = min(room.x for room in rooms)
        max_x = max(room.x for room in rooms)
        min_y = min(room.y for room in rooms)
        max_y = max(room.y for room in rooms)

        # Create grid
        grid = []
        for y in range(min_y, max_y + 1):
            row = []
            for x in range(min_x, max_x + 1):
                room = self.get_room_by_coordinates(x, y)
                if room:
                    if room == self.current_room:
                        row.append("[P]")
                    else:
                        row.append("[R]")
                else:
                    row.append("   ")
            grid.append(row)

        print("\n=== Game Map ===")
        for y_index, row in enumerate(grid, start=min_y):
            row_str = f"Y={y_index} " + " ".join(row)
            print(row_str)
        print("\nLegend: [P] - Your Position | [R] - Room")
        print("X Coordinates ranging from left to right.")
        print("================\n")

    def display_text(self, text, delay=1):
        print(text)
        time.sleep(delay)

    # ====================== Battle System ======================

    def battle(self, player, monster):
        print(f"\n=== Battle Start ===")
        print(f"You have encountered a {monster.name}!")

        while player.is_alive() and monster.is_alive():
            print(f"\nYour Health: {player.health}/{player.max_health}")
            print(f"{monster.name} Health: {monster.health}/{monster.max_health}")
            print(f"Your Defense: {player.defense}")
            choices = ["Attack", "Use Item", "Use Skill"]
            self.display_choices(choices)
            action = self.get_user_choice(choices)

            if action == "ATTACK":
                player.attack(monster)
            elif action == "USE ITEM":
                consumables = [item for item in player.inventory if isinstance(item, Consumable)]
                if consumables:
                    player.show_inventory()
                    item_name = input("Enter the name of the item you want to use: ").strip().lower()
                    item = player.has_item(item_name)
                    if item and isinstance(item, Consumable):
                        item.use(player, self)
                        self.update_quests("use_item", item.name)
                    else:
                        print("\nYou can't use that item.")
                else:
                    print("\nYou have no consumable items to use.")
            elif action == "USE SKILL":
                if player.skills:
                    print("\nAvailable Skills:")
                    for idx, skill in enumerate(player.skills, 1):
                        status = f"(Cooldown: {skill.current_cooldown})" if skill.current_cooldown > 0 else ""
                        print(f"{idx}. {skill.name} - {skill.description} {status}")
                    skill_choice = input("Enter the number of the skill you want to use: ").strip()
                    if skill_choice.isdigit():
                        skill_num = int(skill_choice)
                        if 1 <= skill_num <= len(player.skills):
                            selected_skill = player.skills[skill_num - 1]
                            if selected_skill.current_cooldown == 0:
                                if player.use_skill(selected_skill.name, monster, self):
                                    # If the skill defeated the monster
                                    break
                            else:
                                print(f"\nSkill '{selected_skill.name}' is on cooldown for {selected_skill.current_cooldown} more turn(s).")
                        else:
                            print("\nInvalid skill selection.")
                    else:
                        print("\nPlease enter a valid number.")
                else:
                    print("\nYou have no skills to use.")

            # Tick down cooldowns
            player.tick_skills_cooldown()

            if monster.is_alive():
                monster.attack(player)
                if not player.is_alive():
                    print("\nYou have been defeated.")
                    player.lives -= 1
                    if player.lives > 0:
                        print(f"You have {player.lives} lives left.")
                        player.health = player.max_health
                        print("You respawned with full health.")
                    else:
                        print("You have no lives left. Game Over.")
                        exit()

        if player.is_alive() and not monster.is_alive():
            print(f"\nYou have defeated the {monster.name}!")
            player.score += 100
            player.gain_xp(monster.xp_reward)
            player.gold += monster.gold_reward
            print(f"You received {monster.xp_reward} XP and {monster.gold_reward} gold.")
            self.update_quests("kill", monster.name)
            if monster in self.current_room.monsters:
                self.current_room.monsters.remove(monster)

    # ====================== Quest Management ======================

    def update_quests(self, objective_type, target=None):
        for quest in self.player.quests:
            if not quest.completed:
                quest.update_progress(objective_type, target)
                rewards = quest.complete()
                if rewards:
                    for item in rewards:
                        self.player.add_to_inventory(item)
                        if isinstance(item, Gold):
                            self.player.gold += item.quantity
                            print(f"You received {item.quantity} gold.")
                    print("Your rewards have been added to your inventory.")

    def award_rewards(self, monster, player):
        # Reward logic can be expanded here
        pass

    # ====================== Upgrade Systems ======================

    def upgrade_menu(self):
        while True:
            print("\n=== Upgrade Menu ===")
            print("1. Upgrade Skills")
            print("2. Upgrade Weapon")
            print("3. Upgrade Armor")
            print("B. Back")
            choice = input("Enter your choice: ").strip().lower()
            if choice == '1' or choice == '1':
                self.upgrade_skills()
            elif choice == '2' or choice == '2':
                self.player.upgrade_weapon(self)
            elif choice == '3' or choice == '3':
                self.player.upgrade_armor(self)
            elif choice in ['b', 'back']:
                break
            else:
                print("Invalid choice. Please try again.")

    def upgrade_skills(self):
        print("\n=== Skills Upgrade ===")
        for idx, skill in enumerate(self.player.skills, 1):
            print(f"{idx}. {skill.name} - {skill.description}")
        choice = input("Enter the number of the skill you want to upgrade or 'B' to go back: ").strip().lower()
        if choice.isdigit():
            skill_num = int(choice)
            if 1 <= skill_num <= len(self.player.skills):
                skill = self.player.skills[skill_num - 1]
                self.player.upgrade_skill(skill.name, self)
            else:
                print("Invalid skill selection.")
        elif choice in ['b', 'back']:
            return
        else:
            print("Invalid input.")

    # ====================== Map Feature ======================

    def display_map_feature(self):
        self.display_map()

    # ====================== Main Game Loop ======================

    def play(self):
        print(f"\n=== Welcome to the Adventure Game, {self.player.name}! ===")
        print(f"You are a Level {self.player.level} {self.player.character_class.name}.\n")

        while self.running:
            self.current_room.describe()

            if self.is_daytime():
                print("\nIt's daytime.")
            else:
                print("\nIt's nighttime.")

            # Add quests present in the current room
            for quest in self.current_room.quests:
                if quest not in self.player.quests:
                    self.player.add_quest(quest)

            print(f"\n=== {self.player} ===")

            actions = [
                "Look Around",
                "Inventory",
                "Take Item",
                "Use Item",
                "Craft Item",
                "Quests",
                "Fight",
                "Interact with NPC",
                "Move",
                "Upgrade",
                "View Map"
            ]
            self.display_choices(actions)
            action = self.get_user_choice(actions)

            if action == "LOOK AROUND":
                self.current_room.describe()
            elif action == "INVENTORY":
                self.player.show_inventory()
            elif action == "TAKE ITEM":
                self.handle_take_item()
            elif action == "USE ITEM":
                self.handle_use_item()
            elif action == "CRAFT ITEM":
                self.handle_craft_item()
            elif action == "QUESTS":
                self.handle_show_quests()
            elif action == "FIGHT":
                self.handle_fight()
            elif action == "INTERACT WITH NPC":
                self.handle_npc_interaction()
            elif action == "MOVE":
                self.handle_move()
            elif action == "UPGRADE":
                self.upgrade_menu()
            elif action == "MAP":
                self.display_map_feature()
            elif action == "BACK":
                print("\nReturning to the previous menu.")
            elif action == "QUIT":
                confirm = input("Are you sure you want to quit? (y/n): ").strip().lower()
                if confirm == 'y':
                    self.save_game()
                    print("\nThank you for playing! Goodbye!")
                    break
                else:
                    continue
            elif action == "SAVE":
                self.save_game()

            # Update quests completion
            self.player.check_quests_completion()

            # Advance game time
            self.game_time += timedelta(minutes=15)

            # Increment score based on time or events
            self.player.score += 1

            # Simulate day-night cycle effects or other time-based events
            time.sleep(0.2)

    # ====================== Handle Actions ======================

    def handle_take_item(self):
        if self.current_room.items:
            print("\nAvailable items to take:")
            for item in self.current_room.items:
                print(f"- {item.name.capitalize()} (x{item.quantity})")
            item_choice = input("Enter the name of the item you want to take: ").strip().lower()
            target_item = next((i for i in self.current_room.items if i.name == item_choice), None)
            if target_item:
                quantity = 1
                if target_item.quantity > 1 and not isinstance(target_item, LockedChest):
                    try:
                        quantity = int(input(f"How many {target_item.name.capitalize()}s do you want to take? (1-{target_item.quantity}): "))
                        if quantity < 1 or quantity > target_item.quantity:
                            print("Invalid quantity.")
                            return
                    except ValueError:
                        print("Please enter a valid number.")
                        return
                # Handle Locked Chest separately
                if isinstance(target_item, LockedChest):
                    target_item.use(self.player, self)
                else:
                    # Create a copy of the item to add
                    picked_item = Item(target_item.name, target_item.description, quantity)
                    self.player.add_to_inventory(picked_item)
                    target_item.quantity -= quantity
                    if target_item.quantity <= 0:
                        self.current_room.items.remove(target_item)
                    print(f"\nYou took {quantity} x {target_item.name.capitalize()}.")
                    # Update quests if necessary
                    self.update_quests("collect", target_item.name)
            else:
                chest = next((i for i in self.current_room.items if i.name == item_choice and isinstance(i, LockedChest)), None)
                if chest:
                    chest.use(self.player, self)
                else:
                    print("\nThat item is not available here.")
        else:
            print("\nThere are no items to take here.")

    def handle_use_item(self):
        if self.player.inventory:
            self.player.show_inventory()
            item_name = input("Enter the name of the item you want to use: ").strip().lower()
            item = self.player.has_item(item_name)
            if item:
                item.use(self.player, self)
                if isinstance(item, Consumable):
                    self.update_quests("use_item", item.name)
            else:
                print("\nYou don't have that item.")
        else:
            print("\nYour inventory is empty.")

    def handle_craft_item(self):
        craftable_items = [item for item in self.player.inventory if isinstance(item, Craftable)]
        if craftable_items:
            craftable_available = [item for item in craftable_items if item.can_craft(self.player)]
            if craftable_available:
                print("\nAvailable items to craft:")
                for idx, craftable in enumerate(craftable_available, 1):
                    recipe = ', '.join([f"{qty} x {name.capitalize()}" for name, qty in craftable.recipe.items()])
                    print(f"{idx}. {craftable.name.capitalize()} - {craftable.description} (Recipe: {recipe})")
                craft_choice = input("Enter the number of the item you want to craft or 'B' to go back: ").strip().lower()
                if craft_choice.isdigit():
                    craft_num = int(craft_choice)
                    if 1 <= craft_num <= len(craftable_available):
                        craft_item = craftable_available[craft_num - 1]
                        craft_item.craft(self.player, self)
                    else:
                        print("\nInvalid selection.")
                elif craft_choice in ['b', 'back']:
                    return
                else:
                    print("\nPlease enter a valid number.")
            else:
                print("\nYou don't have the necessary materials to craft any items.")
        else:
            print("\nYou have no craftable items.")

    def handle_show_quests(self):
        if self.player.quests:
            print("\n=== Your Quests ===")
            for quest in self.player.quests:
                print(f"\n{quest}")
            print("====================")
        else:
            print("\nYou have no active quests.")

    def handle_fight(self):
        alive_monsters = [monster for monster in self.current_room.monsters if monster.is_alive()]
        if alive_monsters:
            print("\nMonsters available to fight:")
            for idx, monster in enumerate(alive_monsters, 1):
                print(f"{idx}. {monster.name} (Health: {monster.health}/{monster.max_health})")
            fight_choice = input("Enter the number of the monster you want to fight or 'B' to go back: ").strip().lower()
            if fight_choice.isdigit():
                fight_num = int(fight_choice)
                if 1 <= fight_num <= len(alive_monsters):
                    monster = alive_monsters[fight_num - 1]
                    self.battle(self.player, monster)
                else:
                    print("\nInvalid selection.")
            elif fight_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no monsters to fight here.")

    def handle_npc_interaction(self):
        if self.current_room.npcs:
            print("\nNPCs available to interact with:")
            for idx, npc in enumerate(self.current_room.npcs, 1):
                print(f"{idx}. {npc.name}: {npc.description}")
            npc_choice = input("Enter the number of the NPC you want to interact with or 'B' to go back: ").strip().lower()
            if npc_choice.isdigit():
                npc_num = int(npc_choice)
                if 1 <= npc_num <= len(self.current_room.npcs):
                    npc = self.current_room.npcs[npc_num - 1]
                    npc.interact(self.player, self)
                else:
                    print("\nInvalid selection.")
            elif npc_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no NPCs to interact with here.")

    def handle_move(self):
        available_directions = list(self.current_room.connections.keys())
        if available_directions:
            print("\nAvailable directions to move:")
            for idx, direction in enumerate(available_directions, 1):
                connected_room = self.current_room.get_room_in_direction(direction)
                print(f"{idx}. {direction.capitalize()} to {connected_room.name}")
            direction_choice = input("Enter the number of the direction you want to move or 'B' to go back: ").strip().lower()
            if direction_choice.isdigit():
                dir_num = int(direction_choice)
                if 1 <= dir_num <= len(available_directions):
                    chosen_direction = available_directions[dir_num - 1]
                    next_room = self.current_room.get_room_in_direction(chosen_direction)
                    self.current_room = next_room
                    print(f"\nYou move {chosen_direction.capitalize()} to the {self.current_room.name}.")
                    self.update_quests("reach", self.current_room.name)
                else:
                    print("\nInvalid selection.")
            elif direction_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no exits from this room.")

    # ====================== Upgrade Systems ======================

    def upgrade_menu(self):
        while True:
            print("\n=== Upgrade Menu ===")
            print("1. Upgrade Skills")
            print("2. Upgrade Weapon")
            print("3. Upgrade Armor")
            print("B. Back")
            choice = input("Enter your choice: ").strip().lower()
            if choice == '1':
                self.upgrade_skills()
            elif choice == '2':
                self.player.upgrade_weapon(self)
            elif choice == '3':
                self.player.upgrade_armor(self)
            elif choice in ['b', 'back']:
                break
            else:
                print("Invalid choice. Please try again.")

    def upgrade_skills(self):
        print("\n=== Skills Upgrade ===")
        for idx, skill in enumerate(self.player.skills, 1):
            print(f"{idx}. {skill.name} - {skill.description}")
        choice = input("Enter the number of the skill you want to upgrade or 'B' to go back: ").strip().lower()
        if choice.isdigit():
            skill_num = int(choice)
            if 1 <= skill_num <= len(self.player.skills):
                skill = self.player.skills[skill_num - 1]
                if isinstance(skill, (PowerStrike, Fireball, ArrowShot, Heal)):
                    # Assume upgrading a skill costs 100 gold
                    if self.player.gold >= 100:
                        self.player.gold -= 100
                        if hasattr(skill, 'upgrade'):
                            skill.upgrade()
                        else:
                            print("This skill cannot be upgraded further.")
                    else:
                        print("You don't have enough gold to upgrade this skill.")
                else:
                    print("This skill cannot be upgraded.")
            else:
                print("Invalid skill selection.")
        elif choice in ['b', 'back']:
            return
        else:
            print("Invalid input.")

    # ====================== Map Feature ======================

    def display_map_feature(self):
        self.display_map()

    # ====================== Main Game Loop ======================

    def play(self):
        print(f"\n=== Welcome to the Adventure Game, {self.player.name}! ===")
        print(f"You are a Level {self.player.level} {self.player.character_class.name}.\n")

        while self.running:
            self.current_room.describe()

            if self.is_daytime():
                print("\nIt's daytime.")
            else:
                print("\nIt's nighttime.")

            # Add quests present in the current room
            for quest in self.current_room.quests:
                if quest not in self.player.quests:
                    self.player.add_quest(quest)

            print(f"\n=== {self.player} ===")

            actions = [
                "Look Around",
                "Inventory",
                "Take Item",
                "Use Item",
                "Craft Item",
                "Quests",
                "Fight",
                "Interact with NPC",
                "Move",
                "Upgrade",
                "View Map",
                "Collect Gold"  # Additional action to collect gold
            ]
            self.display_choices(actions)
            action = self.get_user_choice(actions)

            if action == "LOOK AROUND":
                self.current_room.describe()
            elif action == "INVENTORY":
                self.player.show_inventory()
            elif action == "TAKE ITEM":
                self.handle_take_item()
            elif action == "USE ITEM":
                self.handle_use_item()
            elif action == "CRAFT ITEM":
                self.handle_craft_item()
            elif action == "QUESTS":
                self.handle_show_quests()
            elif action == "FIGHT":
                self.handle_fight()
            elif action == "INTERACT WITH NPC":
                self.handle_npc_interaction()
            elif action == "MOVE":
                self.handle_move()
            elif action == "UPGRADE":
                self.upgrade_menu()
            elif action == "MAP":
                self.display_map_feature()
            elif action == "COLLECT GOLD":
                collected_gold = random.randint(5, 15)
                self.player.gold += collected_gold
                print(f"\nYou searched the area and found {collected_gold} gold!")
            elif action == "BACK":
                print("\nReturning to the previous menu.")
            elif action == "QUIT":
                confirm = input("Are you sure you want to quit? (y/n): ").strip().lower()
                if confirm == 'y':
                    self.save_game()
                    print("\nThank you for playing! Goodbye!")
                    break
                else:
                    continue
            elif action == "SAVE":
                self.save_game()

            # Update quests completion
            self.player.check_quests_completion()

            # Advance game time
            self.game_time += timedelta(minutes=15)

            # Increment score based on time or events
            self.player.score += 1

            # Simulate day-night cycle effects or other time-based events
            time.sleep(0.2)

    # ====================== Handle Actions ======================

    def handle_take_item(self):
        if self.current_room.items:
            print("\nAvailable items to take:")
            for item in self.current_room.items:
                print(f"- {item.name.capitalize()} (x{item.quantity})")
            item_choice = input("Enter the name of the item you want to take or 'B' to go back: ").strip().lower()
            if item_choice in ['b', 'back']:
                return
            target_item = next((i for i in self.current_room.items if i.name == item_choice), None)
            if target_item:
                quantity = 1
                if target_item.quantity > 1 and not isinstance(target_item, LockedChest):
                    try:
                        quantity = int(input(f"How many {target_item.name.capitalize()}s do you want to take? (1-{target_item.quantity}): "))
                        if quantity < 1 or quantity > target_item.quantity:
                            print("Invalid quantity.")
                            return
                    except ValueError:
                        print("Please enter a valid number.")
                        return
                # Handle Locked Chest separately
                if isinstance(target_item, LockedChest):
                    target_item.use(self.player, self)
                else:
                    # Create a copy of the item to add
                    picked_item = Item(target_item.name, target_item.description, quantity)
                    self.player.add_to_inventory(picked_item)
                    target_item.quantity -= quantity
                    if target_item.quantity <= 0:
                        self.current_room.items.remove(target_item)
                    print(f"\nYou took {quantity} x {target_item.name.capitalize()}.")
                    # Update quests if necessary
                    self.update_quests("collect", target_item.name)
            else:
                chest = next((i for i in self.current_room.items if i.name == item_choice and isinstance(i, LockedChest)), None)
                if chest:
                    chest.use(self.player, self)
                else:
                    print("\nThat item is not available here.")
        else:
            print("\nThere are no items to take here.")

    def handle_use_item(self):
        if self.player.inventory:
            self.player.show_inventory()
            item_name = input("Enter the name of the item you want to use or 'B' to go back: ").strip().lower()
            if item_name in ['b', 'back']:
                return
            item = self.player.has_item(item_name)
            if item:
                item.use(self.player, self)
                if isinstance(item, Consumable):
                    self.update_quests("use_item", item.name)
            else:
                print("\nYou don't have that item.")
        else:
            print("\nYour inventory is empty.")

    def handle_craft_item(self):
        craftable_items = [item for item in self.player.inventory if isinstance(item, Craftable)]
        if craftable_items:
            craftable_available = [item for item in craftable_items if item.can_craft(self.player)]
            if craftable_available:
                print("\nAvailable items to craft:")
                for idx, craftable in enumerate(craftable_available, 1):
                    recipe = ', '.join([f"{qty} x {name.capitalize()}" for name, qty in craftable.recipe.items()])
                    print(f"{idx}. {craftable.name.capitalize()} - {craftable.description} (Recipe: {recipe})")
                craft_choice = input("Enter the number of the item you want to craft or 'B' to go back: ").strip().lower()
                if craft_choice.isdigit():
                    craft_num = int(craft_choice)
                    if 1 <= craft_num <= len(craftable_available):
                        craft_item = craftable_available[craft_num - 1]
                        craft_item.craft(self.player, self)
                    else:
                        print("\nInvalid selection.")
                elif craft_choice in ['b', 'back']:
                    return
                else:
                    print("\nPlease enter a valid number.")
            else:
                print("\nYou don't have the necessary materials to craft any items.")
        else:
            print("\nYou have no craftable items.")

    def handle_show_quests(self):
        if self.player.quests:
            print("\n=== Your Quests ===")
            for quest in self.player.quests:
                print(f"\n{quest}")
            print("====================")
        else:
            print("\nYou have no active quests.")

    def handle_fight(self):
        alive_monsters = [monster for monster in self.current_room.monsters if monster.is_alive()]
        if alive_monsters:
            print("\nMonsters available to fight:")
            for idx, monster in enumerate(alive_monsters, 1):
                print(f"{idx}. {monster.name} (Health: {monster.health}/{monster.max_health})")
            fight_choice = input("Enter the number of the monster you want to fight or 'B' to go back: ").strip().lower()
            if fight_choice.isdigit():
                fight_num = int(fight_choice)
                if 1 <= fight_num <= len(alive_monsters):
                    monster = alive_monsters[fight_num - 1]
                    self.battle(self.player, monster)
                else:
                    print("\nInvalid selection.")
            elif fight_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no monsters to fight here.")

    def handle_npc_interaction(self):
        if self.current_room.npcs:
            print("\nNPCs available to interact with:")
            for idx, npc in enumerate(self.current_room.npcs, 1):
                print(f"{idx}. {npc.name}: {npc.description}")
            npc_choice = input("Enter the number of the NPC you want to interact with or 'B' to go back: ").strip().lower()
            if npc_choice.isdigit():
                npc_num = int(npc_choice)
                if 1 <= npc_num <= len(self.current_room.npcs):
                    npc = self.current_room.npcs[npc_num - 1]
                    npc.interact(self.player, self)
                else:
                    print("\nInvalid selection.")
            elif npc_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no NPCs to interact with here.")

    def handle_move(self):
        available_directions = list(self.current_room.connections.keys())
        if available_directions:
            print("\nAvailable directions to move:")
            for idx, direction in enumerate(available_directions, 1):
                connected_room = self.current_room.get_room_in_direction(direction)
                print(f"{idx}. {direction.capitalize()} to {connected_room.name}")
            direction_choice = input("Enter the number of the direction you want to move or 'B' to go back: ").strip().lower()
            if direction_choice.isdigit():
                dir_num = int(direction_choice)
                if 1 <= dir_num <= len(available_directions):
                    chosen_direction = available_directions[dir_num - 1]
                    next_room = self.current_room.get_room_in_direction(chosen_direction)
                    self.current_room = next_room
                    print(f"\nYou move {chosen_direction.capitalize()} to the {self.current_room.name}.")
                    self.update_quests("reach", self.current_room.name)
                else:
                    print("\nInvalid selection.")
            elif direction_choice in ['b', 'back']:
                return
            else:
                print("\nPlease enter a valid number.")
        else:
            print("\nThere are no exits from this room.")

    # ====================== Upgrade Systems ======================

    def upgrade_menu(self):
        while True:
            print("\n=== Upgrade Menu ===")
            print("1. Upgrade Skills")
            print("2. Upgrade Weapon")
            print("3. Upgrade Armor")
            print("B. Back")
            choice = input("Enter your choice: ").strip().lower()
            if choice == '1':
                self.upgrade_skills()
            elif choice == '2':
                self.player.upgrade_weapon(self)
            elif choice == '3':
                self.player.upgrade_armor(self)
            elif choice in ['b', 'back']:
                break
            else:
                print("Invalid choice. Please try again.")

    def upgrade_skills(self):
        print("\n=== Skills Upgrade ===")
        for idx, skill in enumerate(self.player.skills, 1):
            print(f"{idx}. {skill.name} - {skill.description} {'(Upgradeable)' if isinstance(skill, (PowerStrike, Fireball, ArrowShot, Heal)) else ''}")
        choice = input("Enter the number of the skill you want to upgrade or 'B' to go back: ").strip().lower()
        if choice.isdigit():
            skill_num = int(choice)
            if 1 <= skill_num <= len(self.player.skills):
                skill = self.player.skills[skill_num - 1]
                if isinstance(skill, (PowerStrike, Fireball, ArrowShot, Heal)):
                    # Assume upgrading a skill costs 100 gold
                    if self.player.gold >= 100:
                        self.player.gold -= 100
                        if hasattr(skill, 'upgrade'):
                            skill.upgrade()
                        else:
                            print("This skill cannot be upgraded further.")
                    else:
                        print("You don't have enough gold to upgrade this skill.")
                else:
                    print("This skill cannot be upgraded.")
            else:
                print("Invalid skill selection.")
        elif choice in ['b', 'back']:
            return
        else:
            print("Invalid input.")

    # ====================== Map Feature ======================

    def display_map(self):
        # Define map boundaries based on room coordinates
        rooms = self.all_rooms
        min_x = min(room.x for room in rooms)
        max_x = max(room.x for room in rooms)
        min_y = min(room.y for room in rooms)
        max_y = max(room.y for room in rooms)

        # Create grid
        grid = []
        for y in range(min_y, max_y + 1):
            row = []
            for x in range(min_x, max_x + 1):
                room = self.get_room_by_coordinates(x, y)
                if room:
                    if room == self.current_room:
                        row.append("[P]")
                    else:
                        row.append("[R]")
                else:
                    row.append("   ")
            grid.append(row)

        print("\n=== Game Map ===")
        for y_index, row in enumerate(grid, start=min_y):
            row_str = f"Y={y_index} " + " ".join(row)
            print(row_str)
        print("\nLegend: [P] - Your Position | [R] - Room")
        print("X Coordinates range from left (min_x) to right (max_x).")
        print("Y Coordinates range from bottom (min_y) to top (max_y).")
        print("================\n")

    # ====================== Quest Management ======================

    def update_quests(self, objective_type, target=None):
        for quest in self.player.quests:
            if not quest.completed:
                quest.update_progress(objective_type, target)
                rewards = quest.complete()
                if rewards:
                    for item in rewards:
                        if isinstance(item, Gold):
                            self.player.gold += item.quantity
                            print(f"You received {item.quantity} gold.")
                        else:
                            self.player.add_to_inventory(item)
                    print("Your rewards have been added to your inventory.")

    def award_rewards(self, monster, player):
        # Reward logic can be expanded here
        pass

    # ====================== Upgrade Systems ======================

    def trigger_event_based_on_time(self):
        # Placeholder for events based on time
        if not self.is_daytime():
            print("\nIt's nighttime. Some NPCs might not be available now.")

    # ====================== Entry Point ======================

    @staticmethod
    def start():
        loaded_game = Game.load_game()
        if loaded_game:
            loaded_game.play()
        else:
            new_game = Game()
            new_game.play()

# ====================== Entry Point ======================

if __name__ == "__main__":
    Game.start()