Skip to content

Commit

Permalink
Merge pull request #47 from mmacy/more-docstring-work
Browse files Browse the repository at this point in the history
adventure module docstring + fix some missing type hints
  • Loading branch information
mmacy committed Feb 2, 2024
2 parents 8d0a28a + 8a12266 commit 3e5a85f
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 53 deletions.
15 changes: 13 additions & 2 deletions osrlib/osrlib/adventure.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
"""The `adventure` module provides the [Adventure][osrlib.adventure.Adventure] class, which represents a scenario to be played through by the adventuring `Party`.
The `Adventure` class is intended to encapsulate a set of thematically related dungeons and quests the player's
[Party][osrlib.party.Party] can explore and complete. It's the thing a game game designer would typically present as a
cohesive story or portion of a story for the player to experience.
Classes:
Adventure: Manages the `Dungeon` and `Quest` collections in a game scenario and the progress of the player's `Party` through the scenario.
DungeonNotFoundError: Raised for missing dungeons.
DungeonAlreadyExistsError: Raised for duplicate dungeon additions.
"""
import json, os, datetime
from osrlib.game_manager import logger
from osrlib.dungeon import Dungeon
Expand All @@ -19,7 +30,7 @@ class DungeonAlreadyExistsError(Exception):


class Adventure:
"""An Adventure is a collection of dungeons that can be played through by a party of characters.
"""An `Adventure`has a collection of dungeons that can be played through and quests that can be completed by a party of characters.
To start an adventure, add a ``Party`` to the adventure with ``set_active_party`` and then call ``start_adventure``.
Once you've started an adventure, you can't add or remove the party or its characters until you call ``end_adventure``
Expand Down Expand Up @@ -106,7 +117,7 @@ def start_adventure(self):
self.is_started = True
logger.debug(f"Started adventure {self.name}.")

def to_dict(self):
def to_dict(self) -> dict:
"""Convert the adventure to a dict.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion osrlib/osrlib/dice_roller.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __str__(self):
else:
return base

def pretty_print(self):
def pretty_print(self) -> str:
"""Return a human-readable string representation of the dice roll, including the total roll and any modifiers.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion osrlib/osrlib/dungeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def json(self):
logger.debug(json_location)
return json_location

def get_exit(self, direction: Direction):
def get_exit(self, direction: Direction) -> Exit:
"""Returns the exit in the specified direction, if it exists.
Args:
Expand Down
2 changes: 1 addition & 1 deletion osrlib/osrlib/dungeon_master.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def format_user_message(self, message_string: str) -> dict:
"""
return {"role": "user", "content": str(message_string)}

def start_session(self):
def start_session(self) -> str:
"""Start a gaming session with the dungeon master in the current adventure.
If this the first session in the adventure, the adventure is marked as started and the
Expand Down
20 changes: 10 additions & 10 deletions osrlib/osrlib/encounter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(
Args:
name (str): The name or ID of the encounter.
description (str): The description of the encounter (location, environment, etc.). Optional.
monsters (MonsterParty): The party of monsters in the encounter. Optional.
monster_party (MonsterParty): The party of monsters in the encounter. Optional.
treasure (list): A list of the treasure in the encounter. The treasure can be any item like weapons, armor,
quest pieces, or gold pieces (or gems or other valuables). Optional.
"""
Expand Down Expand Up @@ -270,14 +270,14 @@ def end_encounter(self):
self.is_started = False
self.is_ended = True

@staticmethod
def get_random_encounter(dungeon_level: int):
"""Get a random encounter based on the dungeon level.
@classmethod
def get_random_encounter(cls, dungeon_level: int) -> "Encounter":
"""Get a random encounter appropriate for the specified dungeon level.
The dungeon_level corresponds to the monster's number of hit dice, and the encounter will contain monsters of
the same type and at that level (number of hit dice). For example, if dungeon_level is 1, the encounter will
contain monsters with 1d8 (or 1d8+n) hit die. If dungeon_level is 3, the encounter will contain
monsters with 3 hit dice (or 3d8+n).
The `dungeon_level` corresponds to the monster's number of hit dice, and the encounter will contain monsters of
the same type and at that level (number of hit dice). For example, if `dungeon_level` is `1`, the encounter will
contain monsters with 1d8 or 1d8+n hit die. If `dungeon_level` is `3`, the encounter will contain
monsters with 3 or 3d8+n hit dice.
Args:
dungeon_level (int): The level of dungeon the encounter should be appropriate for.
Expand All @@ -295,13 +295,13 @@ def get_random_encounter(dungeon_level: int):
]
monster_type = random.choice(monsters_of_level)
monsters = MonsterParty(monster_type)
return Encounter(
return cls(
name=monster_type.name,
description=f"Wandering monsters.",
monster_party=monsters,
)

def to_dict(self):
def to_dict(self) -> dict:
"""Serialize the Encounter instance to a dictionary format.
This method converts the Encounter's attributes, including the associated MonsterParty, into a dictionary.
Expand Down
14 changes: 7 additions & 7 deletions osrlib/osrlib/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def add_item(self, item: Item):
f"Can't add item '{item.name}' to inventory of '{self.owner.name}' because it's already in their inventory."
)

def get_item(self, item: Item):
def get_item(self, item: Item) -> Item:
"""Gets an item from the inventory.
Args:
Expand All @@ -85,14 +85,14 @@ def get_item(self, item: Item):
f"Can't get item '{item.name}' from inventory of '{self.owner.name}' because it's not in their inventory."
)

def remove_item(self, item: Item):
def remove_item(self, item: Item) -> bool:
"""Removes an item from the inventory and resets its owner to None.
Args:
item (Item): Item to remove.
Returns:
bool: True if the item was successfully removed.
bool: Whether the item was successfully removed from the inventory.
Raises:
Exception: If the item is currently equipped.
Expand All @@ -111,14 +111,14 @@ def remove_item(self, item: Item):
f"Can't remove item '{item.name}' from inventory of '{self.owner.name}' because it's not in their inventory."
)

def equip_item(self, item: Item):
def equip_item(self, item: Item) -> bool:
"""Equips an item if it can be equipped.
Args:
item (Item): Item to equip.
Returns:
bool: True if the item was successfully equipped. False if the item could not be equipped.
bool: Whether the item was successfully equipped.
Raises:
ItemNotUsableError: If the item is not usable by the owner's character class.
Expand All @@ -133,14 +133,14 @@ def equip_item(self, item: Item):
else:
raise ItemNotUsableError(f"Can't equip item '{item.name}' because it is not usable by {self.owner.name}.")

def unequip_item(self, item: Item):
def unequip_item(self, item: Item) -> bool:
"""Unequips an item if it is currently equipped.
Args:
item (Item): Item to unequip.
Returns:
bool: True if the item was successfully unequipped.
bool: Whether the item was successfully unequipped.
Raises:
Exception: If the item is not currently equipped.
Expand Down
18 changes: 9 additions & 9 deletions osrlib/osrlib/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class Armor(Item):
Attributes:
ac_modifier (int): Armor class (AC) bonus or penalty provided by this armor. A positive number reduces AC (good)
and a negative number increases AC (bad). Defaults to 1.
and a negative number increases AC (bad). Defaults to 1.
Example:
Expand All @@ -234,13 +234,13 @@ class Armor(Item):
```
"""

def __init__(self, name: str, ac_modifier: int = -1, **kwargs):
def __init__(self, name: str, ac_modifier: int = -1, **kwargs: dict[str, any]):
"""Initialize an armor item with the specified properties.
Args:
name (str): Name of the item.
ac_modifier (int, optional): AC modifier. Lower is better. Defaults to -1.
**kwargs: Additional arguments to pass to the parent class.
**kwargs (dict[str, any]): Additional arguments to pass to the parent class.
"""
super().__init__(name, ItemType.ARMOR, **kwargs)
self.ac_modifier = ac_modifier
Expand Down Expand Up @@ -282,7 +282,7 @@ class Weapon(Item):
name (str): The name of the weapon.
to_hit_damage_die (str, optional): The to-hit and damage roll for the weapon. Defaults to '1d4'.
range (Optional[int], optional): The range of the weapon in feet. Defaults to None for melee weapons.
**kwargs: Arbitrary keyword arguments inherited from the Item class.
**kwargs (dict[str, any]): Arbitrary keyword arguments inherited from the `Item` class.
Attributes:
damage_die (str): The damage die for the weapon, formatted like '1d8', '2d4', '1d6+1', etc.
Expand All @@ -302,7 +302,7 @@ def __init__(
name: str,
to_hit_damage_die: str = "1d4",
range: Optional[int] = None, # melee weapons do not have a range
**kwargs,
**kwargs: dict[str, any],
):
"""Initialize a weapon item with the specified properties."""
super().__init__(name, ItemType.WEAPON, **kwargs)
Expand Down Expand Up @@ -350,15 +350,15 @@ class Spell(Item):
damage_die (Optional[str], optional): The damage roll for the spell. Defaults to None.
range (Optional[int], optional): The range of the spell in feet. Defaults to None for touch spells.
duration_turns (Optional[int], optional): The duration of the spell in turns (1 turn = 10 minutes). Defaults to
None for instantaneous spells.
**kwargs: Arbitrary keyword arguments inherited from the Item class.
`None` for instantaneous spells.
**kwargs (dict[str, any]): Arbitrary keyword arguments inherited from the `Item` class.
Attributes:
spell_level (int): The level of the spell.
damage_die (Optional[str]): The damage die for the spell, formatted like '1d8', '2d6', etc.
range (Optional[int]): The range of the spell in feet.
duration_minutes (Optional[int]): The duration of the spell in minutes. Defaults to None which indicates an
instantaneous spell.
instantaneous spell.
Example:
Expand All @@ -376,7 +376,7 @@ def __init__(
damage_die: Optional[str] = None,
range: Optional[int] = None, # 'touch' spells do not have a range
duration_turns: Optional[int] = None, # 'instantaneous' spells have no duration
**kwargs,
**kwargs: dict[str, any],
):
"""Initialize a spell item with the specified properties."""
super().__init__(name, ItemType.SPELL, **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions osrlib/osrlib/item_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,13 @@ def equip_party(party: "Party"):
equip_magic_user(character)


def get_random_item(item_type: ItemType, magical: bool = False):
def get_random_item(item_type: ItemType, magical: bool = False) -> Item:
"""
Gets a randomn (optionally magic) item from the given category.
Args:
item_type (ItemType): The category of item to get.
magic (bool): Whether to get a magical version of the item.
magical (bool): Whether to get a magical version of the item.
Returns:
Item: An instance of the selected item.
Expand Down
30 changes: 15 additions & 15 deletions osrlib/osrlib/monster.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,15 @@ def __init__(
self.treasure_type = treasure_type
self.alignment = alignment

def to_dict(self):
def to_dict(self) -> dict[str, any]:
"""
Serialize the MonsterStatsBlock instance to a dictionary format.
Converts the MonsterStatsBlock's attributes into a dictionary, facilitating easy serialization for storage or transmission.
Enum values (like save_as_class) are converted to their string representations for compatibility.
Returns:
dict: A dictionary representation of the MonsterStatsBlock instance.
dict[str, any]: A dictionary representation of the MonsterStatsBlock instance.
"""
return {
"name": self.name,
Expand All @@ -171,7 +171,7 @@ def to_dict(self):
}

@classmethod
def from_dict(cls, monster_stats_block_dict: dict):
def from_dict(cls, monster_stats_block_dict: dict) -> "MonsterStatsBlock":
"""
Deserialize a dictionary into a MonsterStatsBlock instance.
Expand Down Expand Up @@ -277,10 +277,10 @@ def _calculate_xp(self, hp_roll: DiceRoll, num_special_abilities: int = 0):

@property
def is_alive(self):
"""Get whether the monster is alive.
"""Gets whether the monster is alive.
Returns:
int: True if the monster has more than 0 hit points, otherwise False.
bool (bool): `True` if the monster has more than 0 hit points, otherwise `False`.
"""
return self.hit_points > 0

Expand Down Expand Up @@ -332,7 +332,7 @@ def apply_damage(self, hit_points_damage: int):
This method has no affect if the monster is already dead.
Args:
damage (int): The amount of damage done to the monster.
hit_points_damage (int): The amount of damage done to the monster.
"""
if self.is_alive:
new_hp = self.hit_points - hit_points_damage
Expand Down Expand Up @@ -445,16 +445,16 @@ def _get_treasure(self, treasure_type: TreasureType):
return treasure_items

@property
def is_alive(self):
def is_alive(self) -> bool:
"""Get whether the monster party is alive.
Returns:
int: True if the monster party has more than 0 hit points, otherwise False.
bool: `True` if at least one monster in the party has more than 0 hit points, otherwise `False`.
"""
return any(monster.is_alive for monster in self.members)

@property
def xp_value(self):
def xp_value(self) -> int:
"""Get the total XP value of the monster party.
Returns:
Expand All @@ -472,7 +472,7 @@ def get_surprise_roll(self) -> int:
)
return roll.total_with_modifier

def to_dict(self):
def to_dict(self) -> dict[str, any]:
"""
Return a dictionary representation of the monster party's MonsterStatsBlock.
Expand All @@ -482,7 +482,7 @@ def to_dict(self):
to rehydrate a MonsterParty instance using the `from_dict` class method.
Returns:
dict: A dictionary representation of the MonsterStatsBlock associated with the MonsterParty. This dictionary
dict[str, any]: A dictionary representation of the MonsterStatsBlock associated with the MonsterParty. This dictionary
contains key-value pairs representing the attributes of the MonsterStatsBlock, such as 'name',
'armor_class', 'hit_dice', etc.
Expand All @@ -500,7 +500,7 @@ def to_dict(self):
return self.monster_stats_block.to_dict()

@classmethod
def from_dict(cls, monster_stats_block_dict: dict):
def from_dict(cls, monster_stats_block_dict: dict) -> "MonsterParty":
"""
Create a MonsterParty instance from a dictionary representing a MonsterStatsBlock.
Expand All @@ -510,12 +510,12 @@ def from_dict(cls, monster_stats_block_dict: dict):
Args:
monster_stats_block_dict (dict): A dictionary containing key-value pairs representing the attributes of a
MonsterStatsBlock. The dictionary structure should correspond to the output
of the `to_dict` method of a MonsterStatsBlock instance.
MonsterStatsBlock. The dictionary structure should correspond to the output
of the `to_dict` method of a MonsterStatsBlock instance.
Returns:
MonsterParty: An instance of MonsterParty initialized with the MonsterStatsBlock created from the provided
dictionary.
dictionary.
Raises:
ValueError: If the dictionary does not contain the necessary information to create a valid MonsterStatsBlock.
Expand Down
6 changes: 2 additions & 4 deletions osrlib/osrlib/party.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class CharacterAlreadyInPartyError(Exception):
"""Raised when attempting to add a player character to a party that already has that character as a member.
Example:
```python
# Check whether the character is in the party before adding them
if not party.is_member(some_new_character):
Expand All @@ -43,7 +42,6 @@ class CharacterNotInPartyError(Exception):
"""Raised when attempting to remove a character from a party when that character isn't in the party.
Example:
```python
# Check for membership before removing a character from the party
if party.is_member(character):
Expand Down Expand Up @@ -175,7 +173,7 @@ def add_character(
self,
character: PlayerCharacter,
set_as_active_character: bool = True,
):
) -> PlayerCharacter:
"""Add a character to the party.
A character can be added to a party only once, and a party has a maximum number of characters.
Expand Down Expand Up @@ -384,7 +382,7 @@ def get_characters_by_class(self, character_class_type: CharacterClassType) -> L
```
Args:
character_class (character_classes.CharacterClassType): The class of characters to return.
character_class_type (character_classes.CharacterClassType): The class of characters to return.
Returns:
List[PlayerCharacter]: A list of all characters in the party of the given class.
Expand Down
Loading

0 comments on commit 3e5a85f

Please sign in to comment.