diff --git a/data/inventory.json b/data/inventory.json new file mode 100644 index 0000000..ae04b5d --- /dev/null +++ b/data/inventory.json @@ -0,0 +1,43 @@ +{ + "Gold Coin": {"type": "currency", "effect": "collect", "quantity": 1}, + "Silver Coin": {"type": "currency", "effect": "collect", "quantity": 1}, + "Coin Stack (1)": {"type": "currency", "effect": "collect", "quantity": 3}, + "Coin Stack (2)": {"type": "currency", "effect": "collect", "quantity": 5}, + "Circular Gem": {"type": "gem", "effect": "trade", "quantity": 1}, + "Single Gold Bar": {"type": "treasure", "effect": "trade", "quantity": 1}, + "Gold Bar Stack": {"type": "treasure", "effect": "trade", "quantity": 3}, + "Treasure Block": {"type": "treasure", "effect": "open_for_reward", "quantity": 1}, + "Golden Crown": {"type": "artifact", "effect": "boost_status", "quantity": 1}, + "Ornate Cup": {"type": "artifact", "effect": "boost_status", "quantity": 1}, + "Golden Figurine": {"type": "artifact", "effect": "boost_status", "quantity": 1}, + "Simple Sword": {"type": "weapon", "effect": "melee_attack", "quantity": 1}, + "Ornate Sword": {"type": "weapon", "effect": "melee_attack", "quantity": 1}, + "Double-Bladed Axe": {"type": "weapon", "effect": "melee_attack", "quantity": 1}, + "Spear": {"type": "weapon", "effect": "melee_attack", "quantity": 1}, + "Circular Shield": {"type": "armor", "effect": "defense_boost", "quantity": 1}, + "Golden Trophy": {"type": "reward", "effect": "achievement", "quantity": 1}, + "Candelabra": {"type": "decorative", "effect": "none", "quantity": 1}, + "Potion (Red)": {"type": "consumable", "effect": "restore_health", "quantity": 1}, + "Potion (Blue)": {"type": "consumable", "effect": "restore_mana", "quantity": 1}, + "Potion (Green)": {"type": "consumable", "effect": "poison_resistance", "quantity": 1}, + "Square Jar": {"type": "consumable", "effect": "unknown", "quantity": 1}, + "Cake": {"type": "food", "effect": "restore_health", "quantity": 1}, + "Donut": {"type": "food", "effect": "restore_health", "quantity": 1}, + "Bread": {"type": "food", "effect": "restore_health", "quantity": 1}, + "Rug Tile": {"type": "decorative", "effect": "none", "quantity": 1}, + "Geometric Pattern": {"type": "decorative", "effect": "none", "quantity": 1}, + "Glowing Orb (Blue)": {"type": "artifact", "effect": "magic_boost", "quantity": 1}, + "Glowing Orb (Red)": {"type": "artifact", "effect": "fire_boost", "quantity": 1}, + "Glowing Orb (Green)": {"type": "artifact", "effect": "nature_boost", "quantity": 1}, + "Golden Ring": {"type": "artifact", "effect": "magic_resistance", "quantity": 1}, + "Amulet": {"type": "artifact", "effect": "protection", "quantity": 1}, + "Scroll": {"type": "scroll", "effect": "learn_spell", "quantity": 1}, + "Key": {"type": "tool", "effect": "unlock", "quantity": 1}, + "Tool": {"type": "tool", "effect": "repair", "quantity": 1}, + "Dragon (Red)": {"type": "creature", "effect": "fire_attack", "quantity": 1}, + "Dragon (Green)": {"type": "creature", "effect": "nature_attack", "quantity": 1}, + "Dragon (Black)": {"type": "creature", "effect": "dark_attack", "quantity": 1}, + "Dragon (White)": {"type": "creature", "effect": "light_attack", "quantity": 1}, + "Gem Cluster": {"type": "treasure", "effect": "trade", "quantity": 1}, + "Glowing Crystal": {"type": "treasure", "effect": "magic_boost", "quantity": 1} +} diff --git a/data/messages.json b/data/messages.json new file mode 100644 index 0000000..2dcf311 --- /dev/null +++ b/data/messages.json @@ -0,0 +1,10 @@ +{ + "inventory": { + "add_success": "Successfully added {quantity} {item}(s) to your inventory.", + "add_fail": "Failed to add {item} to your inventory.", + "remove_success": "Successfully removed {quantity} {item}(s) from your inventory.", + "remove_fail": "Cannot remove {quantity} {item}(s), insufficient quantity.", + "use_success": "You used {item}.", + "use_fail": "You dont' have {item} in your inventory." + } +} \ No newline at end of file diff --git a/docs/InventoryGuide.md b/docs/InventoryGuide.md new file mode 100644 index 0000000..4eb76f7 --- /dev/null +++ b/docs/InventoryGuide.md @@ -0,0 +1,70 @@ +## Inventory Guide (GUI Version) + +### Description of the Inventory GUI +The Inventory GUI serves as a user-friendly interface for managing in-game items. It allows players to view, interact with, and organize their inventory. + +[![pyseas-inventory.png](https://i.postimg.cc/G3ZXHdnK/pyseas-inventory.png)](https://postimg.cc/14rGdxtV) + +### Key Features +- **Visual Display**: Items are displayed as icon, also providing name and quantity. +- **Responsive Interaction**: Interactive buttons for using, or discarding items. +- **Dynamic Updates**: Changes are reflected in real time. +- **Real Time Message Feedback**: Display message actions for better user experience. *(Refer to [utils](./UtilsGuide) for more details.)* + +[![use-item.png](https://i.postimg.cc/9fK7JXpN/use-item.png)](https://postimg.cc/Y4N0SHT1) +[![remove-item.png](https://i.postimg.cc/QCkK8QrR/remove-item.png)](https://postimg.cc/H8nk37n2) + +## Controls Documentation + +### Controls Summary +- **Keyboard**: Press `I` to toggle the inventory screen on and off. +- **Mouse**: Click buttons to perform actions. + +## Running Tests +Tests in this project use `pytest`. To run the tests: +`pytest tests/test_inventory.py` + +## Testing Items + +### Modifying `inventory.json` +The `data/inventory.json` file controls the data for all items in the inventory. It can be modified for testing purposes as follows: + +1. Open the `inventory.json` file. +2. Add, remove, or edit item entries using the following format: + ```json + { + ... + "Gold Coin": {"type": "currency", "effect": "collect", "quantity": 1}, + ... + } + ``` + +## Key Properties + +Each item in the inventory has the following properties: + +- **`type`**: The classification of the item (e.g., `weapon`, `potion`, `material`). +- **`effect`**: The functional impact of the item (e.g., `damage`, `healing`, `crafting material`). +- **`quantity`**: The number of instances available for the item. + +## Working with icons + +### Adding a New Item Icon +To add a new item icon to the inventory: + +1. Open `src/GUI/inventory_gui.py`. +2. Locate the initializer method (`self.icons: {}`). +3. Map the item name in `inventory.json` to the icon's location in the spritesheet. Use the following format: + +```python + "Gold Coin": self.extract_icon(0, 0), +``` + +[![icons-inventory-gui.png](https://i.postimg.cc/CMNKKL8T/icons-inventory-gui.png)](https://postimg.cc/231YcYT2) + +4. Test the new item in-game to verify functionality and ensure no errors occur. + +## Known Issues + +1. Icons Mapping: Placeholder icons are used for certain items as not all of them +accurately represent the icon they are linked to. diff --git a/docs/UtilsGuide.md b/docs/UtilsGuide.md new file mode 100644 index 0000000..f6252a0 --- /dev/null +++ b/docs/UtilsGuide.md @@ -0,0 +1,45 @@ +# UtilsGuide.md + +## Utility: `messaging.py` + +This utility module provides functionality for retrieving and formatting messages stored in a centralized JSON file. It simplifies message management and allows for better modularity, multi-language support, and scalability across the game. + +--- + +### File: `src/utils/messaging.py` + +#### Function: `get_message(category: str, key: str, **kwargs) -> str` + +Retrieve and format a message from a JSON file located at `data/messages.json`. + +**Parameters:** +- `category` (`str`): The category of the message (e.g., `"inventory"`). +- `key` (`str`): The specific key for the desired message (e.g., `"add_success"`). +- `**kwargs`: Dynamic keyword arguments for formatting placeholders in the message. + +**Returns:** +- The formatted message string from the JSON file. +- If the message is not found or the file is missing, a default error message is returned: `"An error occurred while retrieving the message."` + +### Usage Examples + +[![utils-usage.png](https://i.postimg.cc/MHvbCbWH/utils-usage.png)](https://postimg.cc/WqckrZqc) + +## Possible Expansion +The messaging.py utility can be expanded to other areas of the game, including: + +- Multi-language support by replacing or extending data/messages.json with localized versions. +- General feedback and logging, ensuring consistency across UI and gameplay mechanics. +- Integration with game state management for dynamic message generation. + +## Best Practices +### Error Handling: + +- Ensure data/messages.json is correctly formatted and accessible. +- Validate category and key inputs to avoid KeyError. + +- Message Consistency: + - Keep all game-related messages in the JSON file for easier updates and consistency. + +- Localization: + - Prepare data/messages.json to support multi-language keys for localization, e.g., en, es, ko. \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index ebd9fe3..6317e4c 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,3 @@ ruff>=0.5.3 -mypy>=1.11.1 \ No newline at end of file +mypy>=1.11.1 +pytest>=7.4.4 \ No newline at end of file diff --git a/src/GUI/__init__.py b/src/GUI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/GUI/gameloop.py b/src/GUI/gameloop.py index 8e12969..5d2efd4 100644 --- a/src/GUI/gameloop.py +++ b/src/GUI/gameloop.py @@ -1,5 +1,6 @@ from os.path import join import sys +import json # import dataclasses and typchecking from dataclasses import dataclass, field @@ -12,6 +13,10 @@ from src.settings import SCREEN_WIDTH, SCREEN_HEIGHT, TILE_SIZE import src.sprites +# import inventory related classes +from src.GUI.inventory_gui import InventoryGUI +from src.GUI.inventory import Inventory + @dataclass class GUI: @@ -31,6 +36,14 @@ def __post_init__(self): pygame.display.set_caption("PySeas") self.clock = pygame.Clock() + # Initialize player inventory + self.player_inventory = Inventory() + self.inventory_gui = InventoryGUI(self.screen, self.player_inventory) + + # Load initial inventory items from JSON file + self.load_inventory_from_json("data/inventory.json") + + # Players list currently commented out; keeping for potential future use # self.players: list[src.sprites.Player] = [src.sprites.Player()] self.all_sprites = src.sprites.AllSprites() @@ -91,6 +104,38 @@ def handle_events(self) -> None: case pygame.QUIT: pygame.quit() sys.exit() + case pygame.KEYDOWN: + if event.key == pygame.K_i: # Toggle inventory with "I" key + self.toggle_inventory() + + def toggle_inventory(self): + """Toggle the inventory overlay.""" + self.inventory_gui.running = not self.inventory_gui.running + + while self.inventory_gui.running: + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN and event.key == pygame.K_i: + self.inventory_gui.running = False # Close the inventory + elif ( + event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 + ): # Left click + self.inventory_gui.handle_mouse_click(event.pos) + elif event.type == pygame.MOUSEWHEEL: + self.inventory_gui.handle_events(event) + + self.inventory_gui.draw() + pygame.display.flip() # Update the display + + def load_inventory_from_json(self, file_path: str): + """Load initial inventory items from JSON file.""" + try: + with open(file_path, "r") as f: + items = json.load(f) + for item_name, properties in items.items(): + quantity = properties.get("quantity", 1) # Default to 1 if missing + self.player_inventory.add_item(item_name, quantity) + except (FileNotFoundError, json.JSONDecodeError): + print(f"Error: The file at {file_path} does not exist.") def render(self) -> None: """draw sprites to the canvas""" diff --git a/src/GUI/inventory.py b/src/GUI/inventory.py index 6533de1..e1591c1 100644 --- a/src/GUI/inventory.py +++ b/src/GUI/inventory.py @@ -3,6 +3,8 @@ this file contain types of items, like Chest """ +from src.utils.messaging import get_message + class Chest: """contain loot, and worth""" @@ -19,28 +21,71 @@ def __init__(self) -> None: class Inventory: - """contain money, chests, quests""" + """Manage player's inventory, including money, item, chests, and quests""" def __init__(self) -> None: - # special attribute + # Currency self.money: int = 0 - # items : + # Item management + self.items: dict[str, int] = {} # name: quantity + + # Special attributes: self.chests: list[Chest] = [] self.quests: list[Quest] = [] + # General item management + def add_item(self, item_name: str, quantity: int) -> str: + """Add an item to the inventory""" + if item_name in self.items: + self.items[item_name] += quantity + return get_message( + "inventory", "add_success", item=item_name, quantity=quantity + ) + else: + self.items[item_name] = quantity + return get_message( + "inventory", "add_success", item=item_name, quantity=quantity + ) + + def remove_item(self, item_name: str, quantity: int) -> str: + """Remove an item from the inventory. Return True if successful.""" + if item_name in self.items and self.items[item_name] >= quantity: + self.items[item_name] -= quantity + if self.items[item_name] == 0: + del self.items[item_name] + return get_message( + "inventory", "remove_success", item=item_name, quantity=quantity + ) + return get_message( + "inventory", "remove_fail", item=item_name, quantity=quantity + ) + + def use_item(self, item_name: str) -> str: + """Use an item, applying its effect. Return a message.""" + if self.remove_item(item_name, 1) == get_message( + "inventory", "remove_success", item=item_name, quantity=1 + ): + return get_message("inventory", "use_success", item=item_name) + return get_message("inventory", "use_fail", item=item_name) + + def get_items(self) -> dict[str, int]: + """Return a copy of the items dictionary.""" + return self.items.copy() + + # Methods for Chest and Quest def add_chest(self, chest: Chest) -> None: - """append a chest to the inventory""" + """Add a chest to the inventory.""" self.chests.append(chest) def get_chests(self) -> list[Chest]: - """Return a copy of the chests list""" + """Return a copy of the chests list.""" return self.chests.copy() def add_quest(self, quest: Quest) -> None: - """append a quest to the inventory""" + """Add a quest to the inventory.""" self.quests.append(quest) def get_quests(self) -> list[Quest]: - """Return a copy of the quests list""" + """Return a copy of the quests list.""" return self.quests.copy() diff --git a/src/GUI/inventory_gui.py b/src/GUI/inventory_gui.py new file mode 100644 index 0000000..2b39118 --- /dev/null +++ b/src/GUI/inventory_gui.py @@ -0,0 +1,188 @@ +from typing import Dict, Tuple +import pygame +from src.GUI.inventory import Inventory + + +class InventoryGUI: + """Graphical User Interface to display the player's inventory.""" + + def __init__(self, screen: pygame.Surface, inventory: Inventory) -> None: + self.screen = screen + self.inventory = inventory + self.font = pygame.font.Font(None, 36) + self.running = False + + # Scrolling inventory + self.scroll_offset = 0 + self.max_visible_items = 10 + self.item_height = 60 + + # Load sprite sheet and extract the icons (Testing purposes) + # To be replaced when: + # 1) Spritesheet has been decide. 2) A 'Buy', 'Found' or 'Add' in-game feature has been implemented + self.sprite_sheet = pygame.image.load( + "images/tilesets/Treasure+.png" + ).convert_alpha() + self.icons = { + "Gold Coin": self.extract_icon(0, 0), + "Silver Coin": self.extract_icon(16, 0), + "Coin Stack (1)": self.extract_icon(32, 0), + "Coin Stack (2)": self.extract_icon(48, 0), + "Circular Gem": self.extract_icon(64, 0), + "Single Gold Bar": self.extract_icon(0, 16), + "Gold Bar Stack": self.extract_icon(16, 16), + "Treasure Block": self.extract_icon(32, 16), + "Golden Crown": self.extract_icon(0, 32), + "Ornate Cup": self.extract_icon(16, 32), + "Golden Figurine": self.extract_icon(32, 32), + "Simple Sword": self.extract_icon(0, 48), + "Ornate Sword": self.extract_icon(16, 48), + "Double-Bladed Axe": self.extract_icon(32, 48), + "Spear": self.extract_icon(48, 48), + "Circular Shield": self.extract_icon(64, 48), + "Golden Trophy": self.extract_icon(0, 64), + "Candelabra": self.extract_icon(16, 64), + "Potion (Red)": self.extract_icon(0, 80), + "Potion (Blue)": self.extract_icon(16, 80), + "Potion (Green)": self.extract_icon(32, 80), + "Square Jar": self.extract_icon(48, 80), + "Cake": self.extract_icon(0, 96), + "Donut": self.extract_icon(16, 96), + "Bread": self.extract_icon(32, 96), + "Rug Tile": self.extract_icon(0, 112), + "Geometric Pattern": self.extract_icon(16, 112), + "Glowing Orb (Blue)": self.extract_icon(0, 128), + "Glowing Orb (Red)": self.extract_icon(16, 128), + "Glowing Orb (Green)": self.extract_icon(32, 128), + "Golden Ring": self.extract_icon(48, 128), + "Amulet": self.extract_icon(64, 128), + "Scroll": self.extract_icon(0, 144), + "Key": self.extract_icon(16, 144), + "Tool": self.extract_icon(32, 144), + "Dragon (Red)": self.extract_icon(0, 160), + "Dragon (Green)": self.extract_icon(16, 160), + "Dragon (Black)": self.extract_icon(32, 160), + "Dragon (White)": self.extract_icon(48, 160), + "Gem Cluster": self.extract_icon(0, 176), + "Glowing Crystal": self.extract_icon(16, 176), + } + + # Button dimmentions + self.button_width = 100 + self.button_height = 50 + + # Initialize button actions + self.button_actions: Dict[str, Tuple[pygame.Rect, pygame.Rect]] = {} + + # Action messages + self.message = "" + self.message_end_time = 0 # Time to display the message + + def handle_events(self, event): + """Handle events like keypress or mouse wheel.""" + if event.type == pygame.MOUSEWHEEL: + # Adjust scroll offset + self.scroll_offset = max(0, self.scroll_offset - event.y) + max_offset = max( + 0, len(self.inventory.get_items()) - self.max_visible_items + ) + self.scroll_offset = min(self.scroll_offset, max_offset) + + def extract_icon(self, x, y, size=16): + """Extract a single icon from the sprite sheet.""" + return self.sprite_sheet.subsurface((x, y, size, size)) + + def draw_buttons(self, x: int, y: int, item: str) -> Tuple[pygame.Rect, pygame.Rect]: + """Draw Use and Discard buttons for a specific item.""" + use_button = pygame.Rect(x, y, self.button_width, self.button_height) + discard_button = pygame.Rect( + x + self.button_width + 10, y, self.button_width, self.button_height + ) + + pygame.draw.rect(self.screen, (0, 255, 0), use_button) # Green + pygame.draw.rect(self.screen, (150, 75, 0), discard_button) # Brown + + use_text = self.font.render("Use", True, (0, 0, 0)) # Black + discard_text = self.font.render("Discard", True, (0, 0, 0)) + + self.screen.blit(use_text, (x + 10, y + 10)) + self.screen.blit(discard_text, (x + self.button_width + 20, y + 10)) + + return use_button, discard_button + + def draw(self): + """Draw the inventory overlay.""" + self.screen.fill((0, 0, 0)) # Solid Black background + + # Reset button actions + self.button_actions = {} + + # Draw the inventory items + items = list(self.inventory.get_items().items()) + visible_items = items[ + self.scroll_offset : self.scroll_offset + self.max_visible_items + ] + y_offset = 50 # Start below the title + + for item, quantity in visible_items: + # Draw icon + if item in self.icons: + self.screen.blit(self.icons[item], (50, y_offset)) + + # Draw quantity next to the icon + quantity_text = self.font.render(f"x{quantity}", True, (255, 255, 255)) + self.screen.blit(quantity_text, (100, y_offset + 5)) + + # Draw item name (move text to the right) + text = self.font.render(item, True, (255, 255, 255)) + self.screen.blit(text, (150, y_offset)) + + # Draw buttons + use_button, discard_button = self.draw_buttons(400, y_offset, item) + + # Store button references for event handling + self.button_actions[item] = (use_button, discard_button) + y_offset += 60 # Move down for the next item + + # Draw hint + hint_text = self.font.render( + "Press 'I' to close inventory", True, (200, 200, 200) + ) # Light gray text + self.screen.blit(hint_text, (50, self.screen.get_height() - 60)) + + # Display action message above the hint + if self.message and pygame.time.get_ticks() < self.message_end_time: + # Render the message text + message_text = self.font.render(self.message, True, (255, 255, 0)) # Yellow + + # Measure the message text size + text_width, text_height = message_text.get_size() + + # Message background + message_bg_x = 40 + message_bg_y = self.screen.get_height() - 120 + message_bg_width = text_width + 20 # Add padding + message_bg_height = text_height + 10 # Add padding + + # Draw background rectangle for the message + pygame.draw.rect( + self.screen, + (0, 0, 0), # Black background + (message_bg_x, message_bg_y, message_bg_width, message_bg_height), + ) + + # Draw the message text on top of the background + self.screen.blit( + message_text, + (message_bg_x + 10, message_bg_y + 5), # Position text with padding + ) + + def handle_mouse_click(self, mouse_pos) -> None: + """Handle mouse clicks on buttons.""" + for item, (use_button, discard_button) in self.button_actions.items(): + if use_button.collidepoint(mouse_pos): + self.message = self.inventory.use_item(item) # `self.message` stores strings + self.message_end_time = pygame.time.get_ticks() + 3000 # 3 seconds + elif discard_button.collidepoint(mouse_pos): + self.message = self.inventory.remove_item(item, 1) + self.message_end_time = pygame.time.get_ticks() + 4000 # 4 seconds diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/messaging.py b/src/utils/messaging.py new file mode 100644 index 0000000..bd7baa2 --- /dev/null +++ b/src/utils/messaging.py @@ -0,0 +1,12 @@ +import json + + +def get_message(category: str, key: str, **kwargs) -> str: + """Retrieve and format a message from the JSON file.""" + try: + with open("data/messages.json", "r") as f: + messages = json.load(f) + message = messages[category][key] + return message.format(**kwargs) + except (KeyError, FileNotFoundError): + return "An error occurred while retrieving the message." diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_inventory.py b/tests/test_inventory.py new file mode 100644 index 0000000..8e5f3fd --- /dev/null +++ b/tests/test_inventory.py @@ -0,0 +1,105 @@ +import sys +import os + +# Add the project root to sys.path to allow imports to work when running tests directly with `python`. +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +import unittest +from src.GUI.inventory import Inventory, Chest, Quest + + + +class TestInventory(unittest.TestCase): + def setUp(self): + """Set up a new Inventory object before each test.""" + self.inventory = Inventory() + + # Test add_item + def test_add_item_new(self): + """Test adding a new item.""" + result = self.inventory.add_item("Sword", 1) + self.assertEqual(self.inventory.items, {"Sword": 1}) + self.assertEqual(result, "Successfully added 1 Sword(s) to your inventory.") + + def test_add_item_existing(self): + """Test adding to an existing item.""" + self.inventory.add_item("Potion", 1) + result = self.inventory.add_item("Potion", 2) + self.assertEqual(self.inventory.items, {"Potion": 3}) + self.assertEqual(result, "Successfully added 2 Potion(s) to your inventory.") + + # Test remove_item + def test_remove_item_success(self): + """Test successfully removing an item.""" + self.inventory.add_item("Potion", 3) + result = self.inventory.remove_item("Potion", 2) + self.assertEqual(self.inventory.items, {"Potion": 1}) + self.assertEqual( + result, "Successfully removed 2 Potion(s) from your inventory." + ) + + def test_remove_item_fail(self): + """Test failing to remove an item not in inventory or insufficient quantity.""" + result = self.inventory.remove_item("Sword", 1) + self.assertEqual(self.inventory.items, {}) + self.assertEqual(result, "Cannot remove 1 Sword(s), insufficient quantity.") + + # Test use_item + def test_use_item_success(self): + """Test using an item.""" + self.inventory.add_item("Potion", 1) + result = self.inventory.use_item("Potion") + self.assertEqual(self.inventory.items, {}) + self.assertEqual(result, "You used Potion.") + + def test_use_item_fail(self): + """Test failing to use an item.""" + result = self.inventory.use_item("Potion") + self.assertEqual(self.inventory.items, {}) + self.assertEqual(result, "You dont' have Potion in your inventory.") + + # Test add_chest + def test_add_chest(self): + """Test adding a chest.""" + chest = Chest("Gold Chest") + self.inventory.add_chest(chest) + self.assertEqual(len(self.inventory.chests), 1) + self.assertEqual(self.inventory.chests[0].name, "Gold Chest") + + # Test add_quest + def test_add_quest(self): + """Test adding a quest.""" + quest = Quest() + self.inventory.add_quest(quest) + self.assertEqual(len(self.inventory.quests), 1) + self.assertFalse(self.inventory.quests[0].completed) + + # Test get_items + def test_get_items(self): + """Test getting a copy of items.""" + self.inventory.add_item("Sword", 1) + items = self.inventory.get_items() + self.assertEqual(items, {"Sword": 1}) + self.assertIsNot(items, self.inventory.items) # Copy of items + + # Test get_chests + def test_get_chests(self): + """Test getting a copy of chests.""" + chest = Chest("Gold Chest") + self.inventory.add_chest(chest) + chests = self.inventory.get_chests() + self.assertEqual(len(chests), 1) + self.assertIsNot(chests, self.inventory.chests) # Copy of items + + # Test get_quests + def test_get_quests(self): + """Test getting a copy of quests.""" + quest = Quest() + self.inventory.add_quest(quest) + quests = self.inventory.get_quests() + self.assertEqual(len(quests), 1) + self.assertIsNot(quests, self.inventory.quests) # Copy of items + + +if __name__ == "__main__": + unittest.main()