From c9ac9084167e9ca81e8a98a6a4178198c52545f5 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sun, 10 Dec 2023 19:52:08 -0800 Subject: [PATCH 1/9] move Direction enum to enums module --- osrlib/osrlib/dungeon.py | 17 +---------------- osrlib/osrlib/enums.py | 15 +++++++++++++++ osrlib/pyproject.toml | 2 +- tests/poetry.lock | 8 ++++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/osrlib/osrlib/dungeon.py b/osrlib/osrlib/dungeon.py index 0c135e1..c84f85d 100644 --- a/osrlib/osrlib/dungeon.py +++ b/osrlib/osrlib/dungeon.py @@ -2,27 +2,12 @@ from enum import Enum import random, json, asyncio, uuid from openai import OpenAI -from osrlib.enums import OpenAIModelVersion +from osrlib.enums import Direction, OpenAIModelVersion from osrlib.game_manager import logger from osrlib.encounter import Encounter from osrlib.dice_roller import roll_dice -class Direction(Enum): - """Enumeration for directions a player can go within a location. - - Attributes: - NORTH, SOUTH, EAST, WEST, UP, DOWN: Cardinal directions and vertical movements. - """ - - NORTH = "N" - SOUTH = "S" - EAST = "E" - WEST = "W" - UP = "U" - DOWN = "D" - - class Exit: """Represents an exit leading from one location to another within a dungeon. diff --git a/osrlib/osrlib/enums.py b/osrlib/osrlib/enums.py index 1590e4c..e353d65 100644 --- a/osrlib/osrlib/enums.py +++ b/osrlib/osrlib/enums.py @@ -20,6 +20,21 @@ class AttackType(Enum): RODS_STAVES_SPELLS = "Rods, staves, or spells" +class Direction(Enum): + """Enumeration for directions a player can go within a location. + + Attributes: + NORTH, SOUTH, EAST, WEST, UP, DOWN: Cardinal directions and vertical movements. + """ + + NORTH = "N" + SOUTH = "S" + EAST = "E" + WEST = "W" + UP = "U" + DOWN = "D" + + class ModifierType(Enum): TO_HIT = "To hit" DAMAGE = "Damage" diff --git a/osrlib/pyproject.toml b/osrlib/pyproject.toml index 3525c73..611ed5e 100644 --- a/osrlib/pyproject.toml +++ b/osrlib/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "osrlib" -version = "0.1.47" +version = "0.1.48" description = "Turn-based dungeon-crawler game engine for OSR-style RPGs." authors = ["Marsh Macy "] license = "MIT" diff --git a/tests/poetry.lock b/tests/poetry.lock index 1b51687..8906417 100644 --- a/tests/poetry.lock +++ b/tests/poetry.lock @@ -167,7 +167,7 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] [[package]] name = "osrlib" -version = "0.1.47" +version = "0.1.48" description = "Turn-based dungeon-crawler game engine for OSR-style RPGs." optional = false python-versions = "^3.11" @@ -408,13 +408,13 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [metadata] From 019d24709b7a54abab734ffe5a666e9d6f3c4661 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sun, 17 Dec 2023 19:14:14 -0800 Subject: [PATCH 2/9] init treasure.py (needs work) --- osrlib/osrlib/treasure.py | 425 ++++++++++++++++++++++++++++++++------ 1 file changed, 361 insertions(+), 64 deletions(-) diff --git a/osrlib/osrlib/treasure.py b/osrlib/osrlib/treasure.py index f355ddb..1afb806 100644 --- a/osrlib/osrlib/treasure.py +++ b/osrlib/osrlib/treasure.py @@ -2,72 +2,369 @@ from osrlib.enums import ItemType, TreasureType from osrlib.item import Item, Weapon, Armor +from enum import Enum + + +# Define enums for the types of treasure. +class CoinType(Enum): + COPPER = "copper" + SILVER = "silver" + ELECTRUM = "electrum" + GOLD = "gold" + PLATINUM = "platinum" + + +class OtherType(Enum): + GEMS_JEWELRY = "gems_jewelry" + MAGIC_ITEMS = "magic_items" + + +# Example of a treasure type structure using the enums and dice notation. +treasure_types = [ + { + TreasureType.A, + { + CoinType.COPPER: {"percent_chance": 25, "amount": "1d6"}, + CoinType.SILVER: {"percent_chance": 30, "amount": "1d6"}, + CoinType.ELECTRUM: {"percent_chance": 20, "amount": "1d4"}, + CoinType.GOLD: {"percent_chance": 35, "amount": "2d6"}, + CoinType.PLATINUM: {"percent_chance": 25, "amount": "1d2"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "6d6"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 30, "description": "Any 3"}, + }, + }, + { + TreasureType.B, + { + CoinType.COPPER: {"percent_chance": 50, "amount": "1d8"}, + CoinType.SILVER: {"percent_chance": 25, "amount": "1d6"}, + CoinType.ELECTRUM: {"percent_chance": 25, "amount": "1d4"}, + CoinType.GOLD: {"percent_chance": 25, "amount": "1d3"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "1d6"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 10, + "description": "1 sword, armor, or weapon", + }, + }, + }, + { + TreasureType.C, + { + CoinType.COPPER: {"percent_chance": 20, "amount": "1d12"}, + CoinType.SILVER: {"percent_chance": 30, "amount": "1d4"}, + CoinType.ELECTRUM: {"percent_chance": 10, "amount": "1d4"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "1d4"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 10, "description": "Any 2"}, + }, + }, + { + TreasureType.D, + { + CoinType.COPPER: {"percent_chance": 10, "amount": "1d8"}, + CoinType.SILVER: {"percent_chance": 15, "amount": "1d12"}, + CoinType.GOLD: {"percent_chance": 60, "amount": "1d6"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 30, "amount": "1d8"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 15, + "description": "Any 2 + 1 potion", + }, + }, + }, + { + TreasureType.E, + { + CoinType.COPPER: {"percent_chance": 5, "amount": "1d10"}, + CoinType.SILVER: {"percent_chance": 30, "amount": "1d12"}, + CoinType.ELECTRUM: {"percent_chance": 25, "amount": "1d4"}, + CoinType.GOLD: {"percent_chance": 25, "amount": "1d8"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 10, "amount": "1d10"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 25, + "description": "Any 3 + 1 scroll", + }, + }, + }, + { + TreasureType.F, + { + CoinType.SILVER: {"percent_chance": 10, "amount": "2d10"}, + CoinType.ELECTRUM: {"percent_chance": 20, "amount": "1d8"}, + CoinType.GOLD: {"percent_chance": 45, "amount": "1d12"}, + CoinType.PLATINUM: {"percent_chance": 30, "amount": "1d3"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 20, "amount": "2d12"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 30, + "description": "Any 3 except weapons, + 1 potion, + 1 scroll", + }, + }, + }, + { + TreasureType.G, + { + CoinType.GOLD: {"percent_chance": 50, "amount": "10d4"}, + CoinType.PLATINUM: {"percent_chance": 50, "amount": "1d6"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "3d6"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 35, + "description": "Any 4 + 1 scroll", + }, + }, + }, + { + TreasureType.H, + { + CoinType.COPPER: {"percent_chance": 25, "amount": "3d6"}, + CoinType.SILVER: {"percent_chance": 50, "amount": "1d100"}, + CoinType.ELECTRUM: {"percent_chance": 50, "amount": "1d6"}, + CoinType.GOLD: {"percent_chance": 50, "amount": "1d20"}, + CoinType.PLATINUM: {"percent_chance": 25, "amount": "1d10"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d100"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 15, + "description": "Any 4 + 1 potion + 1 scroll", + }, + }, + }, + { + TreasureType.I, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 30, "amount": "1d8"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "2d12"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 15, "description": "Any 1"}, + }, + }, + { + TreasureType.J, + { + CoinType.COPPER: {"percent_chance": 25, "amount": "1d4"}, + CoinType.SILVER: {"percent_chance": 10, "amount": "1d3"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.K, + { + CoinType.COPPER: {"percent_chance": 50, "amount": "2d8"}, + CoinType.SILVER: {"percent_chance": 25, "amount": "1d12"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 50, "amount": "1d6"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 15, "amount": "1d4"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.L, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 100, "amount": "1d100"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d6"}, + OtherType.MAGIC_ITEMS: { + "percent_chance": 30, + "description": "Any 1 + 1 potion", + }, + }, + }, + { + TreasureType.M, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 40, "amount": "2d8"}, + CoinType.PLATINUM: {"percent_chance": 50, "amount": "5d30"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 55, "amount": "5d20"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.N, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 45, "amount": "2d12"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 40, "description": "2d8 potions"}, + }, + }, + { + TreasureType.O, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d4 scrolls"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.P, + { + CoinType.COPPER: {"percent_chance": 100, "amount": "4d6"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.Q, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 100, "amount": "3d6"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.R, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 100, "amount": "2d6"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.S, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 100, "amount": "2d4"}, + CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.T, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 100, "amount": "1d6"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + }, + }, + { + TreasureType.U, + { + CoinType.COPPER: {"percent_chance": 10, "amount": "1d100"}, + CoinType.SILVER: {"percent_chance": 10, "amount": "1d100"}, + CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, + CoinType.GOLD: {"percent_chance": 5, "amount": "1d100"}, + CoinType.PLATINUM: {"percent_chance": 100, "amount": "0"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 5, "amount": "1d4"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 2, "description": "Any 1"}, + }, + }, + { + TreasureType.V, + { + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 10, "amount": "1d100"}, + CoinType.ELECTRUM: {"percent_chance": 5, "amount": "1d100"}, + CoinType.GOLD: {"percent_chance": 10, "amount": "1d100"}, + CoinType.PLATINUM: {"percent_chance": 5, "amount": "1d100"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 10, "amount": "1d4"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 5, "description": "Any 1"}, + }, + }, +] + + def get_treasure(treasure_type: TreasureType): - """Get a collection of items appropriate for the specified treasure type. + """Get a collection of items appropriate for the specified treasure type. - Typical use of this method is to pass it the treasure_type attribute value of a MonsterStatBlock instance - when determining the treasure to award the party for defeating a monster. + Typical use of this method is to pass it the treasure_type attribute value of a MonsterStatBlock instance + when determining the treasure to award the party for defeating a monster. - Args: - treasure_type (TreasureType): The type of treasure to get. + Args: + treasure_type (TreasureType): The type of treasure to get. - Returns: - list: A list of the items to be awarded as treasure to the party. - """ - treasure_items = [] - if treasure_type == TreasureType.NONE: - return None - elif treasure_type == TreasureType.A: - pass - elif treasure_type == TreasureType.B: - pass - elif treasure_type == TreasureType.C: - pass - elif treasure_type == TreasureType.D: - pass - elif treasure_type == TreasureType.E: - pass - elif treasure_type == TreasureType.F: - pass - elif treasure_type == TreasureType.G: - pass - elif treasure_type == TreasureType.H: - pass - elif treasure_type == TreasureType.I: - pass - elif treasure_type == TreasureType.J: - pass - elif treasure_type == TreasureType.K: - pass - elif treasure_type == TreasureType.L: - pass - elif treasure_type == TreasureType.M: - pass - elif treasure_type == TreasureType.N: - pass - elif treasure_type == TreasureType.O: - pass - elif treasure_type == TreasureType.P: - pass - elif treasure_type == TreasureType.Q: - pass - elif treasure_type == TreasureType.R: - pass - elif treasure_type == TreasureType.S: - pass - elif treasure_type == TreasureType.T: - pass - elif treasure_type == TreasureType.U: - pass - elif treasure_type == TreasureType.V: - pass - elif treasure_type == TreasureType.W: - pass - elif treasure_type == TreasureType.X: - pass - elif treasure_type == TreasureType.Y: - pass - elif treasure_type == TreasureType.Z: - pass + Returns: + list: A list of the items to be awarded as treasure to the party. + """ + treasure_items = [] + if treasure_type == TreasureType.NONE: + return None + elif treasure_type == TreasureType.A: + pass + elif treasure_type == TreasureType.B: + pass + elif treasure_type == TreasureType.C: + pass + elif treasure_type == TreasureType.D: + pass + elif treasure_type == TreasureType.E: + pass + elif treasure_type == TreasureType.F: + pass + elif treasure_type == TreasureType.G: + pass + elif treasure_type == TreasureType.H: + pass + elif treasure_type == TreasureType.I: + pass + elif treasure_type == TreasureType.J: + pass + elif treasure_type == TreasureType.K: + pass + elif treasure_type == TreasureType.L: + pass + elif treasure_type == TreasureType.M: + pass + elif treasure_type == TreasureType.N: + pass + elif treasure_type == TreasureType.O: + pass + elif treasure_type == TreasureType.P: + pass + elif treasure_type == TreasureType.Q: + pass + elif treasure_type == TreasureType.R: + pass + elif treasure_type == TreasureType.S: + pass + elif treasure_type == TreasureType.T: + pass + elif treasure_type == TreasureType.U: + pass + elif treasure_type == TreasureType.V: + pass + elif treasure_type == TreasureType.W: + pass + elif treasure_type == TreasureType.X: + pass + elif treasure_type == TreasureType.Y: + pass + elif treasure_type == TreasureType.Z: + pass - return treasure_items \ No newline at end of file + return treasure_items From 22247b0ae951385a7024c5dd7f1192f761dff134 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sun, 17 Dec 2023 20:57:49 -0800 Subject: [PATCH 3/9] [tui] character delete --- osrgame/osrgame/screen_character.py | 13 +++++++++++++ osrgame/osrgame/widgets.py | 1 + osrlib/osrlib/party.py | 28 +++++++++++++++++++++------- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/osrgame/osrgame/screen_character.py b/osrgame/osrgame/screen_character.py index 0cad5ee..4c06e0a 100644 --- a/osrgame/osrgame/screen_character.py +++ b/osrgame/osrgame/screen_character.py @@ -20,6 +20,7 @@ class CharacterScreen(Screen): ("escape", "app.pop_screen", "Back"), ("n", "next_character", "Next character"), ("ctrl+n", "new_character", "New character"), + ("ctrl+delete", "delete_character", "Delete character"), ] def compose(self) -> ComposeResult: @@ -48,6 +49,10 @@ def on_mount(self) -> None: def default_button_pressed(self) -> None: self.action_new_character() + @on(Button.Pressed, "#btn_delete_character") + def default_button_pressed(self) -> None: + self.action_delete_character() + def on_button_pressed(self, event: Button.Pressed) -> None: pc = self.app.adventure.active_party.active_character if event.button.id == "btn_roll_abilities": @@ -77,8 +82,16 @@ def action_new_character(self) -> None: def action_next_character(self) -> None: """An action to switch to the next character in the party.""" self.app.adventure.active_party.set_next_character_as_active() + self.query_one(Log).write_line(f"Active character is now {self.app.adventure.active_party.active_character.name}.") self.on_mount() + def action_delete_character(self) -> None: + """An action to delete the active character.""" + character_to_remove = self.app.adventure.active_party.active_character + self.action_next_character() + self.app.adventure.active_party.remove_character(character_to_remove) + self.query_one(Log).write_line(f"Character {character_to_remove.name} removed from party.") + def on_event(self, event: Event) -> Coroutine[Any, Any, None]: """Handle events.""" # HACK: This is a hack to get the screen to update when the user switches to it. diff --git a/osrgame/osrgame/widgets.py b/osrgame/osrgame/widgets.py index 41c59e1..71cf0fc 100644 --- a/osrgame/osrgame/widgets.py +++ b/osrgame/osrgame/widgets.py @@ -31,6 +31,7 @@ def compose(self) -> ComposeResult: yield Button("Roll abilities", id="btn_roll_abilities", classes="button") yield Button("Roll HP", id="btn_roll_hp", classes="button") yield Button("Save character", id="btn_save_character", classes="button") + yield Button("Delete character", id="btn_delete_character", classes="button") class CharacterStatsBox(Container): """A container for the character stats like name, class, level, HP, and AC.""" diff --git a/osrlib/osrlib/party.py b/osrlib/osrlib/party.py index 4f9279f..30e8028 100644 --- a/osrlib/osrlib/party.py +++ b/osrlib/osrlib/party.py @@ -262,7 +262,7 @@ def set_active_character(self, character: PlayerCharacter): The character must be a member of the party before you can set them as the active character. Args: - character (PlayerCharacter): The party member to set as the active or currently selected character + character (PlayerCharacter): The party member to set as the active or currently selected character. Raises: CharacterNotInPartyError: Raised if the character is not in the party. @@ -280,34 +280,48 @@ def set_active_character(self, character: PlayerCharacter): def set_next_character_as_active(self): """Set the next character in the party as active. - If the currently active character is the last in the list, - this method sets the first character in the list as active. + If the party has no members, the active character is set to None. + + Example: + + .. code-block:: python + party.set_next_character_as_active() """ if not self.members or len(self.members) == 0: # Handle the case where there are no members in the party - raise NoMembersInPartyError("No members in party.") + logger.debug(f"Party has no members, setting active character to 'None'.") + self.active_character = None + return + elif len(self.members) == 1: + # Handle the case where there is only one member in the party + logger.debug(f"Party has only one member, setting active character to '{self.members[0].name}'.") + self.active_character = self.members[0] + return current_active_index = self.get_character_index(self.active_character) next_index = (current_active_index + 1) % len(self.members) next_character = self.get_character_by_index(next_index) self.set_active_character(next_character) - logger.debug(f"Set '{next_character.name}' as the next active character in the party.") + logger.debug(f"Set '{next_character.name}' as the active character in the party.") def remove_character(self, character: PlayerCharacter): - """Removes a character from the party. + """Removes a character from the party and sets the next character in the party as active. + + If the character is the last in the party, the party's active character is set to None. Example: .. code-block:: python try: party.remove_character(character) except CharacterNotInPartyError: - print(f"Character '{character.name}' wasn't in the party and thus be removed from it.") + print(f"Character '{character.name}' wasn't in the party and thus couldn't be removed from it.") Args: character (PlayerCharacter): The PC to remove from the party. """ if self.is_member(character): self.members.remove(character) + logger.debug(f"Removed '{character.name}' from party.") else: raise CharacterNotInPartyError( f"Character '{character.name}' not in party." From a343d03d7f8bdc3ac80bf968a02cc7ff218b9115 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Mon, 18 Dec 2023 19:48:26 -0800 Subject: [PATCH 4/9] [tui] char create functional --- osrgame/osrgame/screen.tcss | 19 ++++--- osrgame/osrgame/screen_character.py | 71 +++++++++++++++--------- osrgame/osrgame/screen_modal_new_char.py | 7 ++- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/osrgame/osrgame/screen.tcss b/osrgame/osrgame/screen.tcss index b693e61..318486e 100644 --- a/osrgame/osrgame/screen.tcss +++ b/osrgame/osrgame/screen.tcss @@ -8,9 +8,9 @@ AdventureBrowserScreen { CharacterScreen { layout: grid; - grid-size: 4; - grid-columns: 1.5fr 1.25fr 1fr 1fr; - grid-rows: 25% 25% 50%; + grid-size: 2; + grid-columns: 1.5fr 1fr; + grid-rows: 25% 30% 45%; background: $surface; } @@ -53,30 +53,31 @@ RadioSet { #stat-block { padding: 1; - column-span: 2; + column-span: 1; } #log { - column-span: 2; + column-span: 1; } #ability-block { padding: 1; - column-span: 2; + column-span: 1; } #saving-throw-block { padding: 1; - column-span: 2; + column-span: 1; } #item-block { - column-span: 3; + column-span: 1; } #char-buttons { border: solid $surface-lighten-3; - layout: vertical; + layout: grid; + grid-size: 2; align: center middle; } diff --git a/osrgame/osrgame/screen_character.py b/osrgame/osrgame/screen_character.py index 4c06e0a..66fb503 100644 --- a/osrgame/osrgame/screen_character.py +++ b/osrgame/osrgame/screen_character.py @@ -36,40 +36,55 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: """Perform actions when the widget is mounted.""" self.query_one(Log).border_subtitle = "LOG" - self.query_one(CharacterStatsBox).pc_name = self.app.adventure.active_party.active_character.name - self.query_one(CharacterStatsBox).pc_class = self.app.adventure.active_party.active_character.character_class - self.query_one(CharacterStatsBox).pc_level = self.app.adventure.active_party.active_character.character_class.current_level - self.query_one(CharacterStatsBox).pc_hp = self.app.adventure.active_party.active_character.character_class.hp - self.query_one(CharacterStatsBox).pc_ac = self.app.adventure.active_party.active_character.armor_class + self.query_one( + CharacterStatsBox + ).pc_name = self.app.adventure.active_party.active_character.name + self.query_one( + CharacterStatsBox + ).pc_class = self.app.adventure.active_party.active_character.character_class + self.query_one( + CharacterStatsBox + ).pc_level = ( + self.app.adventure.active_party.active_character.character_class.current_level + ) + self.query_one( + CharacterStatsBox + ).pc_hp = self.app.adventure.active_party.active_character.character_class.hp + self.query_one( + CharacterStatsBox + ).pc_ac = self.app.adventure.active_party.active_character.armor_class self.query_one(AbilityTable).update_table() self.query_one(SavingThrowTable).update_table() - self.query_one(ItemTable).items = self.app.adventure.active_party.active_character.inventory.all_items + self.query_one( + ItemTable + ).items = self.app.adventure.active_party.active_character.inventory.all_items @on(Button.Pressed, "#btn_new_character") - def default_button_pressed(self) -> None: + def btn_new_character(self) -> None: + self.query_one(Log).write_line(f"Creating a new character...") self.action_new_character() @on(Button.Pressed, "#btn_delete_character") - def default_button_pressed(self) -> None: + def btn_delete_character(self) -> None: self.action_delete_character() - def on_button_pressed(self, event: Button.Pressed) -> None: + @on(Button.Pressed, "#btn_roll_abilities") + def btn_roll_abilities(self) -> None: pc = self.app.adventure.active_party.active_character - if event.button.id == "btn_roll_abilities": - self.reroll() - self.query_one(CharacterStatsBox).pc_ac = pc.armor_class - - elif event.button.id == "btn_roll_hp": - hp_roll = pc.roll_hp() - pc.character_class.max_hp = max(hp_roll.total_with_modifier, 1) - pc.character_class.hp = pc.character_class.max_hp - roll_string = hp_roll.pretty_print() - self.query_one(Log).write_line(roll_string) - self.query_one(CharacterStatsBox).pc_hp = pc.character_class.max_hp - - elif event.button.id == "btn_save_character": - pc.save_character() - self.query_one(Log).write_line("Character saved.") + self.reroll() + self.query_one(CharacterStatsBox).pc_ac = pc.armor_class + + @on(Button.Pressed, "#btn_roll_hp") + def btn_roll_hp(self) -> None: + pc = self.app.adventure.active_party.active_character + pc.character_class.roll_hp() + self.query_one(CharacterStatsBox).pc_hp = pc.character_class.hp + + @on(Button.Pressed, "#btn_save_character") + def btn_save_character(self) -> None: + pc = self.app.adventure.active_party.active_character + pc.save_character() + self.query_one(Log).write_line(f"Character {pc.name} saved.") def action_clear_log(self) -> None: """An action to clear the log.""" @@ -82,7 +97,9 @@ def action_new_character(self) -> None: def action_next_character(self) -> None: """An action to switch to the next character in the party.""" self.app.adventure.active_party.set_next_character_as_active() - self.query_one(Log).write_line(f"Active character is now {self.app.adventure.active_party.active_character.name}.") + self.query_one(Log).write_line( + f"Active character is now {self.app.adventure.active_party.active_character.name}." + ) self.on_mount() def action_delete_character(self) -> None: @@ -90,7 +107,9 @@ def action_delete_character(self) -> None: character_to_remove = self.app.adventure.active_party.active_character self.action_next_character() self.app.adventure.active_party.remove_character(character_to_remove) - self.query_one(Log).write_line(f"Character {character_to_remove.name} removed from party.") + self.query_one(Log).write_line( + f"Character {character_to_remove.name} removed from party." + ) def on_event(self, event: Event) -> Coroutine[Any, Any, None]: """Handle events.""" diff --git a/osrgame/osrgame/screen_modal_new_char.py b/osrgame/osrgame/screen_modal_new_char.py index 227c35c..c1a282f 100644 --- a/osrgame/osrgame/screen_modal_new_char.py +++ b/osrgame/osrgame/screen_modal_new_char.py @@ -34,7 +34,8 @@ def cancel_button_pressed(self) -> None: @on(Button.Pressed, "#btn_char_create") def create_button_pressed(self) -> None: character_name = self.query_one("#character_name").value - character_class_value = self.query_one(RadioSet).pressed_button.label.plain - character_class = CharacterClassType[character_class_value.upper()] - # Implement the character creation logic here + character_class_value = self.query_one(RadioSet).pressed_button.value + character_class = CharacterClassType[character_class_value] + character = self.app.adventure.active_party.create_character(character_name, character_class) + self.app.adventure.active_party.set_active_character(character) self.app.pop_screen() From 3984ff3a42ab65d74e2403cbb25aee2c90f79cac Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Mon, 18 Dec 2023 20:12:11 -0800 Subject: [PATCH 5/9] fixed char create --- osrgame/osrgame/screen_character.py | 6 +++--- osrgame/osrgame/screen_modal_new_char.py | 4 ++-- osrgame/osrgame/widgets.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osrgame/osrgame/screen_character.py b/osrgame/osrgame/screen_character.py index 66fb503..6107f00 100644 --- a/osrgame/osrgame/screen_character.py +++ b/osrgame/osrgame/screen_character.py @@ -76,9 +76,9 @@ def btn_roll_abilities(self) -> None: @on(Button.Pressed, "#btn_roll_hp") def btn_roll_hp(self) -> None: - pc = self.app.adventure.active_party.active_character - pc.character_class.roll_hp() - self.query_one(CharacterStatsBox).pc_hp = pc.character_class.hp + roll = self.app.adventure.active_party.active_character.roll_hp() + self.query_one(Log).write_line(f"HP roll: {roll.total_with_modifier} on {roll}.") + self.query_one(CharacterStatsBox).pc_hp = self.app.adventure.active_party.active_character.max_hit_points @on(Button.Pressed, "#btn_save_character") def btn_save_character(self) -> None: diff --git a/osrgame/osrgame/screen_modal_new_char.py b/osrgame/osrgame/screen_modal_new_char.py index c1a282f..1fc24e4 100644 --- a/osrgame/osrgame/screen_modal_new_char.py +++ b/osrgame/osrgame/screen_modal_new_char.py @@ -34,8 +34,8 @@ def cancel_button_pressed(self) -> None: @on(Button.Pressed, "#btn_char_create") def create_button_pressed(self) -> None: character_name = self.query_one("#character_name").value - character_class_value = self.query_one(RadioSet).pressed_button.value - character_class = CharacterClassType[character_class_value] + character_class_name = self.query_one(RadioSet).pressed_button.name + character_class = CharacterClassType[character_class_name] character = self.app.adventure.active_party.create_character(character_name, character_class) self.app.adventure.active_party.set_active_character(character) self.app.pop_screen() diff --git a/osrgame/osrgame/widgets.py b/osrgame/osrgame/widgets.py index 71cf0fc..245aee5 100644 --- a/osrgame/osrgame/widgets.py +++ b/osrgame/osrgame/widgets.py @@ -15,7 +15,7 @@ class CharacterClassRadioButtons(Container): def compose(self) -> ComposeResult: with RadioSet(id="character_class") as radio_set: for character_class in CharacterClassType: - yield RadioButton(character_class.value, value=character_class.name) + yield RadioButton(character_class.value, name=character_class.name) class WelcomeScreenButtons(Container): From 36db0abe2c37f54bcc7f44d069197fffd41fabcf Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Tue, 26 Dec 2023 12:28:40 -0800 Subject: [PATCH 6/9] treasure WIP --- osrlib/osrlib/item_factories.py | 40 ++++++---- osrlib/osrlib/treasure.py | 132 +++++++++++++------------------- 2 files changed, 78 insertions(+), 94 deletions(-) diff --git a/osrlib/osrlib/item_factories.py b/osrlib/osrlib/item_factories.py index efb7f46..bed48fe 100644 --- a/osrlib/osrlib/item_factories.py +++ b/osrlib/osrlib/item_factories.py @@ -1,3 +1,5 @@ +import random + from osrlib.item import Armor, Item, Weapon from osrlib.enums import CharacterClassType, ItemType @@ -318,20 +320,26 @@ def equip_party(party: "Party"): elif character.character_class.class_type == CharacterClassType.MAGIC_USER: equip_magic_user(character) -# Usage example: -try: - dagger = WeaponFactory.create_weapon("Dagger") - print(dagger) - - bow = WeaponFactory.create_weapon("Short Bow") - print(bow) - - thief_tools = EquipmentFactory.create_item("Thieves' Tools") - print(thief_tools) - not_real = EquipmentFactory.create_item("Not Real") - print(not_real) -except ValueError as e: - print(e) -except ItemDataNotFoundError as e: - print(e) \ No newline at end of file +def get_random_item(item_type: ItemType, magical: bool = False): + """ + 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. + + Returns: + Item: An instance of the selected item. + """ + if item_type == ItemType.ARMOR: + data = magic_armor_data if magical else armor_data + item_name = random.choice(list(data.keys())) + return ArmorFactory.create_armor(item_name) + elif item_type == ItemType.WEAPON: + data = magic_weapon_data if magical else weapon_data + item_name = random.choice(list(data.keys())) + return WeaponFactory.create_weapon(item_name) + # TODO: Add support for SPELL, EQUIPMENT, and MAGIC_ITEM + else: + raise ValueError(f"No item selection logic for {item_type}") diff --git a/osrlib/osrlib/treasure.py b/osrlib/osrlib/treasure.py index 1afb806..e95852e 100644 --- a/osrlib/osrlib/treasure.py +++ b/osrlib/osrlib/treasure.py @@ -1,3 +1,4 @@ +from typing import Dict, NamedTuple, Union from osrlib.dice_roller import roll_dice from osrlib.enums import ItemType, TreasureType from osrlib.item import Item, Weapon, Armor @@ -5,7 +6,6 @@ from enum import Enum -# Define enums for the types of treasure. class CoinType(Enum): COPPER = "copper" SILVER = "silver" @@ -18,6 +18,9 @@ class OtherType(Enum): GEMS_JEWELRY = "gems_jewelry" MAGIC_ITEMS = "magic_items" +class Treasure(NamedTuple): + coins: Dict[CoinType, int] + other: Dict[OtherType, Union[str, int]] # Example of a treasure type structure using the enums and dice notation. treasure_types = [ @@ -113,11 +116,11 @@ class OtherType(Enum): { TreasureType.H, { - CoinType.COPPER: {"percent_chance": 25, "amount": "3d6"}, + CoinType.COPPER: {"percent_chance": 25, "amount": "3d8"}, CoinType.SILVER: {"percent_chance": 50, "amount": "1d100"}, - CoinType.ELECTRUM: {"percent_chance": 50, "amount": "1d6"}, - CoinType.GOLD: {"percent_chance": 50, "amount": "1d20"}, - CoinType.PLATINUM: {"percent_chance": 25, "amount": "1d10"}, + CoinType.ELECTRUM: {"percent_chance": 50, "amount": "10d4"}, + CoinType.GOLD: {"percent_chance": 50, "amount": "10d6"}, + CoinType.PLATINUM: {"percent_chance": 25, "amount": "5d4"}, OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d100"}, OtherType.MAGIC_ITEMS: { "percent_chance": 15, @@ -131,9 +134,9 @@ class OtherType(Enum): CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 30, "amount": "1d8"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "2d12"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, + CoinType.PLATINUM: {"percent_chance": 30, "amount": "1d8"}, + OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "2d6"}, OtherType.MAGIC_ITEMS: {"percent_chance": 15, "description": "Any 1"}, }, }, @@ -152,17 +155,17 @@ class OtherType(Enum): { TreasureType.K, { - CoinType.COPPER: {"percent_chance": 50, "amount": "2d8"}, - CoinType.SILVER: {"percent_chance": 25, "amount": "1d12"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 50, "amount": "1d6"}, + CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, + CoinType.SILVER: {"percent_chance": 30, "amount": "1d6"}, + CoinType.ELECTRUM: {"percent_chance": 10, "amount": "1d2"}, + CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 15, "amount": "1d4"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, + OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, + OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": "0"}, }, }, { - TreasureType.L, + TreasureType.L, # TODO: Resume from here. { CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, @@ -299,72 +302,45 @@ class OtherType(Enum): ] -def get_treasure(treasure_type: TreasureType): - """Get a collection of items appropriate for the specified treasure type. - - Typical use of this method is to pass it the treasure_type attribute value of a MonsterStatBlock instance - when determining the treasure to award the party for defeating a monster. +def get_treasure(treasure_type: TreasureType) -> Treasure: + """ + Calculates the treasure based on the defined treasure types and computes the total value in gold pieces. Args: - treasure_type (TreasureType): The type of treasure to get. + treasure_type (TreasureType): The type of treasure to calculate. Returns: - list: A list of the items to be awarded as treasure to the party. + Treasure: A named tuple containing the coins and other treasure items. """ - treasure_items = [] - if treasure_type == TreasureType.NONE: - return None - elif treasure_type == TreasureType.A: - pass - elif treasure_type == TreasureType.B: - pass - elif treasure_type == TreasureType.C: - pass - elif treasure_type == TreasureType.D: - pass - elif treasure_type == TreasureType.E: - pass - elif treasure_type == TreasureType.F: - pass - elif treasure_type == TreasureType.G: - pass - elif treasure_type == TreasureType.H: - pass - elif treasure_type == TreasureType.I: - pass - elif treasure_type == TreasureType.J: - pass - elif treasure_type == TreasureType.K: - pass - elif treasure_type == TreasureType.L: - pass - elif treasure_type == TreasureType.M: - pass - elif treasure_type == TreasureType.N: - pass - elif treasure_type == TreasureType.O: - pass - elif treasure_type == TreasureType.P: - pass - elif treasure_type == TreasureType.Q: - pass - elif treasure_type == TreasureType.R: - pass - elif treasure_type == TreasureType.S: - pass - elif treasure_type == TreasureType.T: - pass - elif treasure_type == TreasureType.U: - pass - elif treasure_type == TreasureType.V: - pass - elif treasure_type == TreasureType.W: - pass - elif treasure_type == TreasureType.X: - pass - elif treasure_type == TreasureType.Y: - pass - elif treasure_type == TreasureType.Z: - pass + treasure_details = treasure_types[treasure_type] + treasure_haul = { + "coins": {}, + "other": {} + } + total_gp_value = 0 # Initialize the total gold pieces value + + for item_type, details in treasure_details.items(): + # Use roll_dice for chance determination + chance_roll = roll_dice('1d100').total + if chance_roll <= details["percent_chance"]: + # Roll dice to determine the amount + amount_roll = roll_dice(details["amount"]) + amount = amount_roll.total + if isinstance(item_type, CoinType): + treasure_haul["coins"][item_type] = amount + # Update the total value in gold pieces + total_gp_value += amount * item_type.exchange_rate + else: + treasure_haul["other"][item_type] = details.get("description", amount) + + # Add the total_gp_value to the treasure haul + treasure_haul["total_gp_value"] = total_gp_value + + return Treasure(**treasure_haul) - return treasure_items +# Example usage: +treasure = get_treasure(TreasureType.A) +print(f"Treasure haul: {treasure}") +# Calculate XP from the total gold pieces value +xp_from_treasure = treasure.total_gp_value +print(f"XP from treasure: {xp_from_treasure}") From 45537aa4d5ab1ef911114de8445fba088fff2c2a Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sat, 30 Dec 2023 16:45:21 -0800 Subject: [PATCH 7/9] treasure progress checkin --- osrlib/osrlib/enums.py | 30 +++ osrlib/osrlib/treasure.py | 468 ++++++++++++++++---------------------- 2 files changed, 225 insertions(+), 273 deletions(-) diff --git a/osrlib/osrlib/enums.py b/osrlib/osrlib/enums.py index e353d65..e696d5f 100644 --- a/osrlib/osrlib/enums.py +++ b/osrlib/osrlib/enums.py @@ -69,6 +69,7 @@ class ItemType(Enum): "Potion, ring, or other item imbued with magical properties", ) ITEM = ("Normal item", "Normal (non-magical) item") + GEMS_JEWELRY = ("Gems and jewelry", "Precious stones and fine jewelry.") class OpenAIModelVersion(Enum): @@ -114,3 +115,32 @@ class TreasureType(Enum): X = "X" Y = "Y" Z = "Z" + + +class CoinType(Enum): + COPPER = ("copper", 0.01) + SILVER = ("silver", 0.1) + ELECTRUM = ("electrum", 0.5) + GOLD = ("gold", 1) + PLATINUM = ("platinum", 5) + + def __init__(self, description: str, exchange_rate: float): + self.description = description + self.exchange_rate = exchange_rate + + @staticmethod + def value_in_gold(coin_amounts: dict) -> float: + """ + Calculate the combined value in gold pieces based on the coin amounts. + + Args: + coin_amounts (dict): A dictionary with CoinType as keys and amounts as values. + + Returns: + float: The total value in gold pieces. + + Example usage: + >>> CoinType.value_in_gold({CoinType.COPPER: 1000, CoinType.SILVER: 100}) + 20.0 + """ + return sum(coin_type.exchange_rate * amount for coin_type, amount in coin_amounts.items()) diff --git a/osrlib/osrlib/treasure.py b/osrlib/osrlib/treasure.py index e95852e..b90d435 100644 --- a/osrlib/osrlib/treasure.py +++ b/osrlib/osrlib/treasure.py @@ -1,305 +1,229 @@ from typing import Dict, NamedTuple, Union +from dataclasses import dataclass from osrlib.dice_roller import roll_dice -from osrlib.enums import ItemType, TreasureType +from osrlib.enums import ItemType, TreasureType, CoinType from osrlib.item import Item, Weapon, Armor from enum import Enum -class CoinType(Enum): - COPPER = "copper" - SILVER = "silver" - ELECTRUM = "electrum" - GOLD = "gold" - PLATINUM = "platinum" +@dataclass +class TreasureDetail: + chance: float # Probability as a float between 0 and 1 + amount: str # Dice notation for the amount + magical: bool = False # Whether the item should be magical -class OtherType(Enum): - GEMS_JEWELRY = "gems_jewelry" - MAGIC_ITEMS = "magic_items" - class Treasure(NamedTuple): coins: Dict[CoinType, int] - other: Dict[OtherType, Union[str, int]] + other: Dict[ItemType, Union[str, int]] + + +TreasureContent = Dict[Union[CoinType, ItemType], TreasureDetail] -# Example of a treasure type structure using the enums and dice notation. -treasure_types = [ - { - TreasureType.A, - { - CoinType.COPPER: {"percent_chance": 25, "amount": "1d6"}, - CoinType.SILVER: {"percent_chance": 30, "amount": "1d6"}, - CoinType.ELECTRUM: {"percent_chance": 20, "amount": "1d4"}, - CoinType.GOLD: {"percent_chance": 35, "amount": "2d6"}, - CoinType.PLATINUM: {"percent_chance": 25, "amount": "1d2"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "6d6"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 30, "description": "Any 3"}, - }, + +treasure_types: Dict[TreasureType, TreasureContent] = { + TreasureType.A: { + CoinType.COPPER: TreasureDetail(chance=0.25, amount="1d6"), + CoinType.SILVER: TreasureDetail(chance=0.30, amount="1d6"), + CoinType.ELECTRUM: TreasureDetail(chance=0.20, amount="1d4"), + CoinType.GOLD: TreasureDetail(chance=0.35, amount="2d6"), + CoinType.PLATINUM: TreasureDetail(chance=0.25, amount="1d2"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.50, amount="6d6"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.30, amount="3", magical=True), }, - { - TreasureType.B, - { - CoinType.COPPER: {"percent_chance": 50, "amount": "1d8"}, - CoinType.SILVER: {"percent_chance": 25, "amount": "1d6"}, - CoinType.ELECTRUM: {"percent_chance": 25, "amount": "1d4"}, - CoinType.GOLD: {"percent_chance": 25, "amount": "1d3"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "1d6"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 10, - "description": "1 sword, armor, or weapon", - }, - }, + TreasureType.B: { + CoinType.COPPER: TreasureDetail(chance=0.50, amount="1d8"), + CoinType.SILVER: TreasureDetail(chance=0.25, amount="1d6"), + CoinType.ELECTRUM: TreasureDetail(chance=0.25, amount="1d4"), + CoinType.GOLD: TreasureDetail(chance=0.25, amount="1d3"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.25, amount="1d6"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.10, amount="1", magical=True), }, - { - TreasureType.C, - { - CoinType.COPPER: {"percent_chance": 20, "amount": "1d12"}, - CoinType.SILVER: {"percent_chance": 30, "amount": "1d4"}, - CoinType.ELECTRUM: {"percent_chance": 10, "amount": "1d4"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "1d4"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 10, "description": "Any 2"}, - }, + TreasureType.C: { + CoinType.COPPER: TreasureDetail(chance=0.20, amount="1d12"), + CoinType.SILVER: TreasureDetail(chance=0.30, amount="1d4"), + CoinType.ELECTRUM: TreasureDetail(chance=0.10, amount="1d4"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.25, amount="1d4"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.10, amount="2", magical=True), }, - { - TreasureType.D, - { - CoinType.COPPER: {"percent_chance": 10, "amount": "1d8"}, - CoinType.SILVER: {"percent_chance": 15, "amount": "1d12"}, - CoinType.GOLD: {"percent_chance": 60, "amount": "1d6"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 30, "amount": "1d8"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 15, - "description": "Any 2 + 1 potion", - }, - }, + TreasureType.D: { + CoinType.COPPER: TreasureDetail(chance=0.10, amount="1d8"), + CoinType.SILVER: TreasureDetail(chance=0.15, amount="1d12"), + CoinType.GOLD: TreasureDetail(chance=0.60, amount="1d6"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.30, amount="1d8"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.15, amount="2+1", magical=True), # Assuming "2+1" refers to the total count }, - { - TreasureType.E, - { - CoinType.COPPER: {"percent_chance": 5, "amount": "1d10"}, - CoinType.SILVER: {"percent_chance": 30, "amount": "1d12"}, - CoinType.ELECTRUM: {"percent_chance": 25, "amount": "1d4"}, - CoinType.GOLD: {"percent_chance": 25, "amount": "1d8"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 10, "amount": "1d10"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 25, - "description": "Any 3 + 1 scroll", - }, - }, + TreasureType.E: { + CoinType.COPPER: TreasureDetail(chance=0.05, amount="1d10"), + CoinType.SILVER: TreasureDetail(chance=0.30, amount="1d12"), + CoinType.ELECTRUM: TreasureDetail(chance=0.25, amount="1d4"), + CoinType.GOLD: TreasureDetail(chance=0.25, amount="1d8"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.10, amount="1d10"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.25, amount="3+1", magical=True), # Assuming "3+1" refers to the total count }, - { - TreasureType.F, - { - CoinType.SILVER: {"percent_chance": 10, "amount": "2d10"}, - CoinType.ELECTRUM: {"percent_chance": 20, "amount": "1d8"}, - CoinType.GOLD: {"percent_chance": 45, "amount": "1d12"}, - CoinType.PLATINUM: {"percent_chance": 30, "amount": "1d3"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 20, "amount": "2d12"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 30, - "description": "Any 3 except weapons, + 1 potion, + 1 scroll", - }, - }, + TreasureType.F: { + CoinType.SILVER: TreasureDetail(chance=0.10, amount="2d10"), + CoinType.ELECTRUM: TreasureDetail(chance=0.20, amount="1d8"), + CoinType.GOLD: TreasureDetail(chance=0.45, amount="1d12"), + CoinType.PLATINUM: TreasureDetail(chance=0.30, amount="1d3"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.20, amount="2d12"), + ItemType.MAGIC_ITEM: TreasureDetail( + chance=0.30, + amount="Any 3 except weapons, + 1 potion, + 1 scroll", + ), }, - { - TreasureType.G, - { - CoinType.GOLD: {"percent_chance": 50, "amount": "10d4"}, - CoinType.PLATINUM: {"percent_chance": 50, "amount": "1d6"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 25, "amount": "3d6"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 35, - "description": "Any 4 + 1 scroll", - }, - }, + TreasureType.G: { + CoinType.GOLD: TreasureDetail(chance=0.50, amount="10d4"), + CoinType.PLATINUM: TreasureDetail(chance=0.50, amount="1d6"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.25, amount="3d6"), + ItemType.MAGIC_ITEM: TreasureDetail( + chance=0.35, + amount="Any 4 + 1 scroll", + ), }, - { - TreasureType.H, - { - CoinType.COPPER: {"percent_chance": 25, "amount": "3d8"}, - CoinType.SILVER: {"percent_chance": 50, "amount": "1d100"}, - CoinType.ELECTRUM: {"percent_chance": 50, "amount": "10d4"}, - CoinType.GOLD: {"percent_chance": 50, "amount": "10d6"}, - CoinType.PLATINUM: {"percent_chance": 25, "amount": "5d4"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d100"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 15, - "description": "Any 4 + 1 potion + 1 scroll", - }, - }, + TreasureType.H: { + CoinType.COPPER: TreasureDetail(chance=0.25, amount="3d8"), + CoinType.SILVER: TreasureDetail(chance=0.50, amount="1d100"), + CoinType.ELECTRUM: TreasureDetail(chance=0.50, amount="10d4"), + CoinType.GOLD: TreasureDetail(chance=0.50, amount="10d6"), + CoinType.PLATINUM: TreasureDetail(chance=0.25, amount="5d4"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.50, amount="1d100"), + ItemType.MAGIC_ITEM: TreasureDetail( + chance=0.15, + amount="Any 4 + 1 potion + 1 scroll", + ), }, - { - TreasureType.I, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 30, "amount": "1d8"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "2d6"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 15, "description": "Any 1"}, - }, + TreasureType.I: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0.30, amount="1d8"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.50, amount="2d6"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.15, amount="Any 1"), }, - { - TreasureType.J, - { - CoinType.COPPER: {"percent_chance": 25, "amount": "1d4"}, - CoinType.SILVER: {"percent_chance": 10, "amount": "1d3"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.J: { + CoinType.COPPER: TreasureDetail(chance=0.25, amount="1d4"), + CoinType.SILVER: TreasureDetail(chance=0.10, amount="1d3"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.K, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 30, "amount": "1d6"}, - CoinType.ELECTRUM: {"percent_chance": 10, "amount": "1d2"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": "0"}, - }, + TreasureType.K: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0.30, amount="1d6"), + CoinType.ELECTRUM: TreasureDetail(chance=0.10, amount="1d2"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount="0"), }, - { - TreasureType.L, # TODO: Resume from here. - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 100, "amount": "1d100"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d6"}, - OtherType.MAGIC_ITEMS: { - "percent_chance": 30, - "description": "Any 1 + 1 potion", - }, - }, + TreasureType.L: { # TODO: Resume from here. + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=1.00, amount="1d100"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.50, amount="1d6"), + ItemType.MAGIC_ITEM: TreasureDetail( + chance=0.30, + amount="Any 1 + 1 potion", + ), }, - { - TreasureType.M, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 40, "amount": "2d8"}, - CoinType.PLATINUM: {"percent_chance": 50, "amount": "5d30"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 55, "amount": "5d20"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.M: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0.40, amount="2d8"), + CoinType.PLATINUM: TreasureDetail(chance=0.50, amount="5d30"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.55, amount="5d20"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.N, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 45, "amount": "2d12"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 40, "description": "2d8 potions"}, - }, + TreasureType.N: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.45, amount="2d12"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.40, amount="2d8 potions"), }, - { - TreasureType.O, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 50, "amount": "1d4 scrolls"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.O: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.50, amount="1d4 scrolls"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.P, - { - CoinType.COPPER: {"percent_chance": 100, "amount": "4d6"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.P: { + CoinType.COPPER: TreasureDetail(chance=1.00, amount="4d6"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.Q, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 100, "amount": "3d6"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.Q: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=1.00, amount="3d6"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.R, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 100, "amount": "2d6"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.R: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=1.00, amount="2d6"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.S, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 100, "amount": "2d4"}, - CoinType.PLATINUM: {"percent_chance": 0, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.S: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=1.00, amount="2d4"), + CoinType.PLATINUM: TreasureDetail(chance=0, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.T, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 0, "amount": "0"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 0, "amount": "0"}, - CoinType.PLATINUM: {"percent_chance": 100, "amount": "1d6"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 0, "amount": "0"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 0, "description": ""}, - }, + TreasureType.T: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0, amount="0"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0, amount="0"), + CoinType.PLATINUM: TreasureDetail(chance=1.00, amount="1d6"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0, amount="0"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0, amount=""), }, - { - TreasureType.U, - { - CoinType.COPPER: {"percent_chance": 10, "amount": "1d100"}, - CoinType.SILVER: {"percent_chance": 10, "amount": "1d100"}, - CoinType.ELECTRUM: {"percent_chance": 0, "amount": "0"}, - CoinType.GOLD: {"percent_chance": 5, "amount": "1d100"}, - CoinType.PLATINUM: {"percent_chance": 100, "amount": "0"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 5, "amount": "1d4"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 2, "description": "Any 1"}, - }, + TreasureType.U: { + CoinType.COPPER: TreasureDetail(chance=0.10, amount="1d100"), + CoinType.SILVER: TreasureDetail(chance=0.10, amount="1d100"), + CoinType.ELECTRUM: TreasureDetail(chance=0, amount="0"), + CoinType.GOLD: TreasureDetail(chance=0.05, amount="1d100"), + CoinType.PLATINUM: TreasureDetail(chance=1.00, amount="0"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.05, amount="1d4"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=2, amount="Any 1"), }, - { - TreasureType.V, - { - CoinType.COPPER: {"percent_chance": 0, "amount": "0"}, - CoinType.SILVER: {"percent_chance": 10, "amount": "1d100"}, - CoinType.ELECTRUM: {"percent_chance": 5, "amount": "1d100"}, - CoinType.GOLD: {"percent_chance": 10, "amount": "1d100"}, - CoinType.PLATINUM: {"percent_chance": 5, "amount": "1d100"}, - OtherType.GEMS_JEWELRY: {"percent_chance": 10, "amount": "1d4"}, - OtherType.MAGIC_ITEMS: {"percent_chance": 5, "description": "Any 1"}, - }, + TreasureType.V: { + CoinType.COPPER: TreasureDetail(chance=0, amount="0"), + CoinType.SILVER: TreasureDetail(chance=0.10, amount="1d100"), + CoinType.ELECTRUM: TreasureDetail(chance=0.05, amount="1d100"), + CoinType.GOLD: TreasureDetail(chance=0.10, amount="1d100"), + CoinType.PLATINUM: TreasureDetail(chance=0.05, amount="1d100"), + ItemType.GEMS_JEWELRY: TreasureDetail(chance=0.10, amount="1d4"), + ItemType.MAGIC_ITEM: TreasureDetail(chance=0.05, amount="Any 1"), }, -] +} def get_treasure(treasure_type: TreasureType) -> Treasure: @@ -313,15 +237,12 @@ def get_treasure(treasure_type: TreasureType) -> Treasure: Treasure: A named tuple containing the coins and other treasure items. """ treasure_details = treasure_types[treasure_type] - treasure_haul = { - "coins": {}, - "other": {} - } + treasure_haul = {"coins": {}, "other": {}} total_gp_value = 0 # Initialize the total gold pieces value for item_type, details in treasure_details.items(): # Use roll_dice for chance determination - chance_roll = roll_dice('1d100').total + chance_roll = roll_dice("1d100").total if chance_roll <= details["percent_chance"]: # Roll dice to determine the amount amount_roll = roll_dice(details["amount"]) @@ -338,6 +259,7 @@ def get_treasure(treasure_type: TreasureType) -> Treasure: return Treasure(**treasure_haul) + # Example usage: treasure = get_treasure(TreasureType.A) print(f"Treasure haul: {treasure}") From 0bcb4b97ac5da1ff51254e5d97e5e3df682c66f1 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sun, 31 Dec 2023 15:21:55 -0800 Subject: [PATCH 8/9] dice_roller cleanup + support hardcoded roll --- osrlib/osrlib/dice_roller.py | 84 ++++++++++++++---------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/osrlib/osrlib/dice_roller.py b/osrlib/osrlib/dice_roller.py index db55a5b..78d990e 100644 --- a/osrlib/osrlib/dice_roller.py +++ b/osrlib/osrlib/dice_roller.py @@ -1,9 +1,8 @@ """Dice roller module for rolling dice based on the nDn or Dn notation, supporting modifiers.""" -import random -import re +import random, re from collections import namedtuple -# TODO: Change total_with_modifier to total_without_modifier and make total the total_with_modifer + class DiceRoll( namedtuple( "RollResultBase", @@ -15,6 +14,7 @@ class DiceRoll( Args: namedtuple (RollResult): The named tuple containing the number of dice, number of sides, base roll, modifier, total roll with modifier, and the individual rolls. """ + def __str__(self): """ Returns a string representation of the dice roll based on the ndn notation, including modifiers if applicable. @@ -31,8 +31,7 @@ def __str__(self): return base def pretty_print(self): - """ - Returns a human-readable string representation of the dice roll, including the total roll and any modifiers. + """Returns a human-readable string representation of the dice roll, including the total roll and any modifiers. Returns: str: A string describing the dice roll and its outcome (e.g., 'Rolled 3d6 and got 11 (11)', 'Rolled 1d20+3 and got 9 (6 + 3)'). @@ -44,11 +43,15 @@ def pretty_print(self): def roll_dice(notation: str, modifier: int = 0, drop_lowest: bool = False): - """ - Rolls dice based on the nDn or Dn notation, supporting modifiers. + """Rolls dice based on the nDn or Dn notation and factors in optional modifiers. Also accepts a string representing a single integer value. + + To guarantee the result of the roll, specify a single string-formatted integer for ``notation``. For example, to + guarantee a roll of 20, pass "20" in the ``notation`` parameter. The ``RollResult`` that's returned will always be a + single roll on a die whose number of sides is the ``notation`` value as are its ``RollResult.total`` and + ``RollResult.total_with_modifier`` attribute values. Args: - notation (str): The dice notation in ndn format with optional modifiers (e.g., '3d6', '1d20+5', '2d8-4'). + notation (str): A string representation of a dice roll in ndn format with optional modifiers like '3d6', '1d20+5', or '2d8-4'. Or specify single integer as string like '1', '20', or '18'. modifier (int): An optional additional integer modifier to add to the roll. Defaults to 0. drop_lowest (bool): Whether to drop the lowest dice roll. Defaults to False. @@ -68,21 +71,31 @@ def roll_dice(notation: str, modifier: int = 0, drop_lowest: bool = False): >>> result = roll_dice('4d6', drop_lowest=True) >>> print(result.pretty_print()) """ - rand_gen = random.SystemRandom() - - notation = notation.replace(" ", "") - notation = _add_modifier_to_dice_notation(notation, modifier) + notation = notation.replace(" ", "").lower() + + try: + # First check to see if the notation string is a single integer passed as a string. + # We need to support calls that pass in a specific value in order to guarantee that + # the "roll" returns that value. You might do this in scenarios like specifying a + # set number of monsters in an encounter or number of gold pieces in a reward. This + # also enables unit tests need a consistent roll results for their test cases. + num_sides = int(notation) + return DiceRoll(1, num_sides, num_sides, 0, num_sides, [num_sides]) + except ValueError: + pass match = re.match(r"(\d*)d(\d+)([+-]\d+)?", notation, re.IGNORECASE) + if not match: + raise ValueError( + "Invalid number of dice and sides. Use dn or ndn format like 'd6', '3d6', '3d6+2', or '3d6-2'." + ) - num_dice, num_sides, modifier = match.groups() + num_dice, num_sides, notation_modifier = match.groups() num_dice = int(num_dice) if num_dice else 1 num_sides = int(num_sides) - modifier = int(modifier) if modifier else 0 - - if num_sides not in [1, 2, 3, 4, 6, 8, 10, 12, 20, 100]: - raise ValueError("Invalid number of dice sides. Choose from 1, 2, 3, 4, 6, 8, 10, 12, 20, 100.") + modifier += int(notation_modifier) if notation_modifier else 0 + rand_gen = random.SystemRandom() die_rolls = [rand_gen.randint(1, num_sides) for _ in range(num_dice)] if drop_lowest and len(die_rolls) > 1: @@ -91,37 +104,6 @@ def roll_dice(notation: str, modifier: int = 0, drop_lowest: bool = False): total = sum(die_rolls) total_with_modifier = total + modifier - return DiceRoll(num_dice, num_sides, total, modifier, total_with_modifier, die_rolls) - - -def _add_modifier_to_dice_notation(notation: str, modifier: int) -> str: - """ - Adds a modifier to a dice notation string. - - Args: - notation (str): Existing dice notation string, like '1d6' or '1d6+1'. - modifier (int): The integer modifier to add. - - Returns: - str: The modified dice notation string. - - Raises: - ValueError: If the input notation is invalid. - """ - match = re.match(r"(\d*)d(\d+)([+-]\d+)?", notation, re.IGNORECASE) - if not match: - raise ValueError( - "Invalid number of dice and sides. Use dn or ndn format like 'd6', '3d6', '3d6+2', or '3d6-2'." - ) - - num_dice, num_sides, existing_modifier = match.groups() - - existing_modifier = int(existing_modifier) if existing_modifier else 0 - - new_modifier = existing_modifier + modifier - if new_modifier == 0: - return f"{num_dice}d{num_sides}" - elif new_modifier > 0: - return f"{num_dice}d{num_sides}+{new_modifier}" - else: - return f"{num_dice}d{num_sides}{new_modifier}" + return DiceRoll( + num_dice, num_sides, total, modifier, total_with_modifier, die_rolls + ) From 3ec73719d43eb836189d58672a5f4560cda91e75 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Sun, 31 Dec 2023 19:07:19 -0800 Subject: [PATCH 9/9] support dice with any num sides --- tests/test_unit_dice_roller.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_unit_dice_roller.py b/tests/test_unit_dice_roller.py index f443b81..337f86b 100644 --- a/tests/test_unit_dice_roller.py +++ b/tests/test_unit_dice_roller.py @@ -28,16 +28,6 @@ def test_invalid_notation(): with pytest.raises(ValueError): roll_dice("3dd6") - -def test_invalid_sides(): - """Tests invalid number of sides. - - Checks whether the roll_dice function raises a ValueError when the number of sides is invalid, i.e., '3d7'. - """ - with pytest.raises(ValueError): - roll_dice("3d7") - - def test_with_modifier(): """Tests dice notation with a positive modifier.