diff --git a/questhelper/map/map_generator.py b/questhelper/map/map_generator.py index 35c5907..bf5a275 100644 --- a/questhelper/map/map_generator.py +++ b/questhelper/map/map_generator.py @@ -2,7 +2,8 @@ import random from typing import List import map.quest_map -from map.quest_map import QuestMap, MapEncounter, EncounterType as ET, QuestPath as QP +import map.treasure_hoard_generator +from map.quest_map import QuestMap, MapEncounter, BattleEncounter, EncounterType as ET, QuestPath as QP # 27 cards (21 are drawn) @@ -26,9 +27,10 @@ def generate_encounter_inaccessible() -> MapEncounter: return MapEncounter(ET.INACCESSIBLE) -def generate_encounter_battle(battle_type: ET): +def generate_encounter_battle(battle_type: ET, level: int): assert battle_type in map.quest_map.ENCOUNTER_TYPES_BATTLE - return MapEncounter(battle_type) + hoard = map.treasure_hoard_generator.generate_hoard(battle_type, level) + return BattleEncounter(battle_type, hoard) def generate_encounter_town(town_type: ET): @@ -36,7 +38,7 @@ def generate_encounter_town(town_type: ET): return MapEncounter(town_type) -def generate_map() -> QuestMap: +def generate_map(level: int) -> QuestMap: quest_map = QuestMap() # Generate encounters @@ -46,13 +48,13 @@ def generate_map() -> QuestMap: row: List[MapEncounter] = [] if r == 10: row.append(generate_encounter_inaccessible()) - row.append(generate_encounter_battle(ET.BATTLE_BOSS)) + row.append(generate_encounter_battle(ET.BATTLE_BOSS, level)) row.append(generate_encounter_inaccessible()) elif r in (1, 4, 7): elite_column = random.randrange(4) # 75% chance of elite for c in range(3): battle_type = ET.BATTLE_ELITE if c == elite_column else ET.BATTLE_REGULAR - row.append(generate_encounter_battle(battle_type)) + row.append(generate_encounter_battle(battle_type, level)) else: for c in range(3): town_type = next(town_deck_it) diff --git a/questhelper/map/map_renderer.py b/questhelper/map/map_renderer.py index 702e45d..6c6c01b 100644 --- a/questhelper/map/map_renderer.py +++ b/questhelper/map/map_renderer.py @@ -37,10 +37,12 @@ class RenderedElement: h: int +@dataclasses.dataclass(frozen=True) class RenderedEncounter(RenderedElement): pass +@dataclasses.dataclass(frozen=True) class RenderedPath(RenderedElement): pass diff --git a/questhelper/map/quest_map.py b/questhelper/map/quest_map.py index 2a0cf8b..fd33025 100644 --- a/questhelper/map/quest_map.py +++ b/questhelper/map/quest_map.py @@ -3,6 +3,8 @@ import enum from typing import List +from map.treasure_hoard import TreasureHoard + class EncounterType(enum.Enum): INACCESSIBLE = 'inaccessible' @@ -60,6 +62,15 @@ class MapEncounter: return str(self.type) +@dataclasses.dataclass +class BattleEncounter(MapEncounter): + treasure_hoard: TreasureHoard + + @property + def description(self) -> str: + return f'{self.type} [{self.treasure_hoard.description}]' + + class QuestMap: def __init__(self): # List of rows of encounters, each row has 3 encounters. diff --git a/questhelper/map/templates/map/index.html b/questhelper/map/templates/map/index.html index 3ccbb0f..0ed830f 100644 --- a/questhelper/map/templates/map/index.html +++ b/questhelper/map/templates/map/index.html @@ -1,7 +1,7 @@ {% load static %} -

Map

+

Map of {{level_ordinal}} quest

{% for e in map.encounters %} diff --git a/questhelper/map/treasure_hoard.py b/questhelper/map/treasure_hoard.py new file mode 100644 index 0000000..c488ffb --- /dev/null +++ b/questhelper/map/treasure_hoard.py @@ -0,0 +1,38 @@ +import dataclasses +import enum + + +class TreasureType(enum.Enum): + GOLD = 'gold' + INK = 'ink' + COG = 'cog' + DEED = 'deed' + GEMS = 'bag of gems' + ART_OBJECT = 'art object' + CONSUMABLE = 'consumable' + LIFE = 'life' + BLESSING = 'blessing' + NOBILITY_TITLE = 'nobility title' + RELIC = 'relic' + + +@dataclasses.dataclass(frozen=True) +class Treasure: + amount: int + treasure_type: TreasureType + + @property + def description(self) -> str: + word = str(self.treasure_type.value) + word = word.capitalize() + + return f'{self.amount} {word}' + + +@dataclasses.dataclass(frozen=True) +class TreasureHoard: + revealed: Treasure + + @property + def description(self) -> str: + return f'{self.revealed.description} + ?' \ No newline at end of file diff --git a/questhelper/map/treasure_hoard_generator.py b/questhelper/map/treasure_hoard_generator.py new file mode 100644 index 0000000..c6e6032 --- /dev/null +++ b/questhelper/map/treasure_hoard_generator.py @@ -0,0 +1,65 @@ +import random +from typing import List, Tuple + +from map.quest_map import EncounterType +from map.treasure_hoard import TreasureHoard, Treasure, TreasureType as TT + + +class HoardTable: + def __init__(self, input: List[Tuple[int, int, TT]]): + self.input = input + + def roll(self) -> Treasure: + dice_total = random.randint(1, 6) + random.randint(1, 6) + for top_of_band, amount, treasure_type in self.input: + if dice_total <= top_of_band: + return Treasure(amount, treasure_type) + + +_TABLES = { + 1: { + EncounterType.BATTLE_REGULAR: HoardTable([ + (2, 200, TT.GOLD), + (4, 2, TT.COG), + (5, 1, TT.CONSUMABLE), + (6, 1, TT.DEED), + (8, 50, TT.GOLD), + (9, 1, TT.GEMS), + (11, 1, TT.ART_OBJECT), + (12, 1, TT.LIFE), + ]), + EncounterType.BATTLE_ELITE: HoardTable([ + (2, 300, TT.GOLD), + (4, 4, TT.COG), + (5, 2, TT.CONSUMABLE), + (6, 2, TT.DEED), + (8, 100, TT.GOLD), + (9, 2, TT.GEMS), + (10, 2, TT.ART_OBJECT), + (11, 1, TT.NOBILITY_TITLE), + (12, 1, TT.LIFE), + ]), + EncounterType.BATTLE_BOSS: HoardTable([ + (2, 400, TT.GOLD), + (4, 6, TT.COG), + (5, 3, TT.CONSUMABLE), + (6, 3, TT.DEED), + (8, 150, TT.GOLD), + (9, 3, TT.GEMS), + (11, 1, TT.NOBILITY_TITLE), + (12, 1, TT.RELIC), + ]), + } +} + + +def generate_hoard(battle_type: EncounterType, level: int): + return TreasureHoard(_roll_treasure(battle_type, level)) + + +def _roll_treasure(battle_type: EncounterType, level: int): + # TODO: Handle more quest levels + assert level == 1 + + table: HoardTable = _TABLES[level][battle_type] + return table.roll() diff --git a/questhelper/map/views.py b/questhelper/map/views.py index b0036e2..fec3de6 100644 --- a/questhelper/map/views.py +++ b/questhelper/map/views.py @@ -7,10 +7,15 @@ from map.map_renderer import RenderedMap from map.quest_map import QuestMap +def _ordinal(level): + return {1: '1st', 2: '2nd', 3: '3rd', 4: '4th', 5: '5th'}[level] + + def index(request): - quest_map: QuestMap = map_generator.generate_map() + level = 1 + quest_map: QuestMap = map_generator.generate_map(level) rendered_map: RenderedMap = map_renderer.render_map(quest_map) template = loader.get_template('map/index.html') - context = {'map': rendered_map} + context = {'level_ordinal': _ordinal(level), 'map': rendered_map} return HttpResponse(template.render(context, request))