From 7354872aeb090396095402aa2fc5e605ef35f219 Mon Sep 17 00:00:00 2001 From: vesper-arch Date: Fri, 9 Feb 2024 02:58:54 +0000 Subject: [PATCH 01/18] Added support for the card's new format in a lot of places + some misc fixes --- definitions.py | 4 +- entities.py | 165 +++++++++++++------------------------------ game.py | 3 +- helper.py | 87 +++++++++-------------- items.py | 1 + message_bus_tools.py | 2 + 6 files changed, 89 insertions(+), 173 deletions(-) diff --git a/definitions.py b/definitions.py index 96901ed2..45357676 100644 --- a/definitions.py +++ b/definitions.py @@ -37,4 +37,6 @@ class PlayerClass(StrEnum): IRONCLAD = 'Ironclad' SILENT = 'Silent' DEFECT = 'Defect' - WATCHER = 'Watcher' \ No newline at end of file + WATCHER = 'Watcher' + COLORLESS = 'Colorless' + ANY = 'Any' diff --git a/entities.py b/entities.py index 9fd0124b..52331e0f 100644 --- a/entities.py +++ b/entities.py @@ -7,7 +7,7 @@ from copy import deepcopy from ansi_tags import ansiprint from helper import active_enemies, view, gen, ei -from definitions import CombatTier +from definitions import CombatTier, CardType, Rarity, PlayerClass from message_bus_tools import bus, Message, Registerable, Card class Player(Registerable): @@ -86,7 +86,7 @@ def __init__(self, health: int, block: int, max_energy: int, deck: list, powers: self.draw_shuffles = 0 # Used for the Sundial relic self.incense_turns = 0 # Used for the Incense Burner relic self.girya_charges = 3 # Stores how many times the player can gain Energy from Girya - self.plays_this_turn = 0 # Counts how many items.cards the played plays each turn + self.plays_this_turn = 0 # Counts how many cards the played plays each turn self.stone_calender = 0 self.choker_cards_played = 0 # Used for the Velvet Choker relic @@ -136,13 +136,13 @@ def use_card(self, card, target: 'Enemy', exhaust, pile) -> None: sleep(1.5) view.clear() self.use_card(card=card, target=target, exhaust=True, pile=None) - if card.get('Type') == 'Status' and items.relics['Medical Kit'] in player.items.relics: + if card.type == CardType.STATUS and items.relics['Medical Kit'] in player.relics: exhaust = True - elif card.get('Type') == 'Curse' and items.relics['Blue Candle'] in player.items.relics: + elif card.type == CardType.CURSE and items.relics['Blue Candle'] in player.relics: self.take_sourceless_dmg(1) exhaust = True if pile is not None: - if exhaust is True or card.get('Exhaust') is True: + if exhaust is True or getattr(card, 'exhaust') is True: ansiprint(f"{card['Name']} was Exhausted.") self.move_card(card=card, move_to=self.exhaust_pile, from_location=pile, cost_energy=True) else: @@ -180,13 +180,13 @@ def on_relic_pickup(self, relic): elif relic['Name'] == 'Astrolabe': while True: try: - view.view_piles(self.deck, self, False, 'Removable') - target_cards = literal_eval(f"({input('Choose 3 items.cards to Transform and Upgrade separated by colons > ')})") + view.view_piles(self.deck, validator=lambda card: card.removable is False) + target_cards = literal_eval(f"({input('Choose 3 cards to Transform and Upgrade separated by colons > ')})") if len(target_cards) != 3: raise TypeError("") for card in target_cards: self.deck[card] = self.card_actions(self.deck[card], 'Transform', items.cards) - self.deck[card] = self.card_actions(self.deck[card], "Upgrade", items.cards) + self.deck[card].upgrade() break except (TypeError, ValueError): ansiprint("Incorrect syntax, wrong length, or invalid card number Correct: '_, _, _'") @@ -201,11 +201,11 @@ def on_relic_pickup(self, relic): view.view_piles(self.deck, self, False, 'Removable') backup_counter = 2 # Used to account for wrong or invalid inputs for _ in range(backup_counter): - option = view.list_input("Choose a card to remove > ", self.deck, view.view_piles, lambda card: card.get("Removable") is False, "That card is not removable.") + option = view.list_input("Choose a card to remove > ", self.deck, view.view_piles, lambda card: card.removable is False, "That card is not removable.") self.deck[option] = self.card_actions(self.deck[option], 'Remove', items.cards) elif relic['Name'] == "Pandora's Box": for card in self.deck: - if card['Name'] in ('Strike', 'Defend'): + if card.name.replace('+', '') in ('Strike', 'Defend'): card = self.card_actions(card, 'Upgrade', items.cards) elif relic['Name'] == 'Sacred Bark': items.activate_sacred_bark() @@ -214,8 +214,8 @@ def on_relic_pickup(self, relic): self.gain_gold(50) self.health_actions(5, "Max Health") gen.card_rewards('Normal', True, self, items.cards) - upgrade_card = random.choice((index for index in range(len(self.deck)) if not self.deck[index].get("Upgraded") and (self.deck[index]['Name'] == "Burn" or self.deck[index]['Type'] not in ("Status", "Curse")))) - self.deck[upgrade_card] = self.card_actions(self.deck[upgrade_card], "Upgrade") + upgrade_card = random.choice((index for index in range(len(self.deck)) if not self.deck[index].upgraded and (self.deck[index].name == "Burn" or self.deck[index].type not in (CardType.STATUS, CardType.CURSE)))) + self.deck[upgrade_card].upgrade() elif relic['Name'] == 'Velvet Choker': self.max_energy += 1 elif relic['Name'] == 'Black Blood': @@ -227,29 +227,29 @@ def on_relic_pickup(self, relic): self.card_reward_choices += 1 if relic['Name'] == 'Question Card' else 0 def on_card_play(self, card: Card): - if items.relics['Kunai'] in self.relics and card.get('Type') == 'Attack': + if items.relics['Kunai'] in self.relics and card.type == CardType.ATTACK: self.kunai_attacks += 1 if self.kunai_attacks == 3: ansiprint("Kunai activated: ", end='') - ei.apply_effect(self, self, 'Dexterity', 1) + ei.apply_effect(self, None, 'Dexterity', 1) self.kunai_attacks = 0 - if items.relics['Ornamental Fan'] in self.relics and card.get('Type') == 'Attack': + if items.relics['Ornamental Fan'] in self.relics and card.type == CardType.ATTACK: self.ornament_fan_attacks += 1 if self.ornament_fan_attacks == 3: ansiprint("Ornamental Fan activated: ", end='') self.blocking(4, False) - if items.relics['Letter Opener'] in self.relics and card.get('Type') == 'Skill': + if items.relics['Letter Opener'] in self.relics and card.type == CardType.SKILL: self.letter_opener_skills += 1 if self.letter_opener_skills == 3: ansiprint("Letter Opener activated") for enemy in active_enemies: self.attack(5, enemy) self.letter_opener_skills = 0 - if items.relics['Mummified Hand'] in self.relics and card.get('Type') == 'Power': + if items.relics['Mummified Hand'] in self.relics and card.type == CardType.POWER: ansiprint('Mummified Hand activated: ', end='') target_card = random.choice(self.hand) target_card.modify_energy_cost(0, 'Set', True) - if items.relics['Shuriken'] in self.relics and card.get('Type') == 'Attack': + if items.relics['Shuriken'] in self.relics and card.type == CardType.Attack: self.shuriken_attacks += 1 if self.shuriken_attacks == 3: ansiprint('Shuriken activated: ', end='') @@ -260,10 +260,10 @@ def on_card_play(self, card: Card): ansiprint("Ink Bottle activated: ", end='') self.draw_cards(True, 1) self.inked_items.cards = 0 - if items.relics['Duality'] in self.relics and card.get('Type') == 'Attack': + if items.relics['Duality'] in self.relics and card.type == CardType.ATTACK: ansiprint('Duality activated: ', end='') ei.apply_effect(self, self, 'Dexterity', 1) - if items.relics['Bird-Faced Urn'] in self.relics and card.get('Type') == 'Power': + if items.relics['Bird-Faced Urn'] in self.relics and card.type == CardType.POWER: ansiprint('Bird-Faced Urn activated: ', end='') self.health_actions(2, 'Heal') if items.relics['Velvet Choker'] in self.relics: @@ -294,14 +294,14 @@ def draw_cards(self, middle_of_turn: bool, draw_cards: int = 0): self.draw_shuffles = 0 ansiprint("Discard pile shuffled into draw pile.") self.hand.extend(player.draw_pile[-draw_cards:]) - # Removes those items.cards + # Removes those cards player.draw_pile = player.draw_pile[:-draw_cards] print(f"Drew {draw_cards} items.cards.") # bus.publish(Message.ON_DRAW, (pl)) break def blocking(self,card: bool = True): - '''Gains [block] Block. items.Cards are affected by Dexterity and Frail.''' + '''Gains [block] Block. Cards are affected by Dexterity and Frail.''' bus.publish(Message.BEFORE_BLOCK, (self, card)) self.block += card.block ansiprint(f'{self.name} gained {card.block} Block from {card.block_affected_by}') @@ -347,22 +347,6 @@ def card_actions(self, subject_card: dict, action: str, card_pool: list[dict] = continue ansiprint(f"{new_card['Name']} | {new_card['Info']}") return new_card - elif action == 'Upgrade': - if "Searing Blow" not in subject_card['Name']: - effects_plus: dict = subject_card.pop('Effects+') - subject_card['Name'] += '+' - for stat, new_value in effects_plus.items(): - subject_card[stat] = new_value - subject_card['Upgraded'] = True - else: - n = subject_card['Upgrade Count'] - n += 1 - subject_card['Name'] += f"+{n}" - subject_card['Damage'] = n * (n + 7) / 2 + 12 - subject_card['Info'] = f"Deal Σ{subject_card['Damage']}. Can be upgraded any number of times." - - ansiprint(f"{subject_card['Name'].replace('+', '')} was upgraded to {subject_card['Name']}") - return subject_card def end_player_turn(self): self.discard_pile += self.hand @@ -375,7 +359,7 @@ def end_player_turn(self): def move_card(self, card, move_to, from_location, cost_energy, shuffle=False): if cost_energy is True: - self.energy -= card['Energy'] if isinstance(card['Energy'], int) else 0 + self.energy -= max(card.energy_cost, 0) from_location.remove(card) if shuffle is True: move_to.insert(random.randint(0, len(move_to) - 1), card) @@ -393,7 +377,7 @@ def attack(self, dmg, target: 'Enemy', card=None, ignore_block=False): # Check if already dead and skip if so if target.health <= 0: return - if card is not None and card.get('Type') not in ('Status', 'Curse'): + if card is not None and card.type not in (CardType.STATUS, CardType.CURSE): bus.publish(Message.BEFORE_ATTACK, (self, target, card)) if dmg <= target.block: target.block -= dmg @@ -403,8 +387,7 @@ def attack(self, dmg, target: 'Enemy', card=None, ignore_block=False): dmg -= target.block dmg = max(0, dmg) target.health -= dmg - ansiprint(f"You dealt {dmg} damage({target.block} Blocked) to {target.name}") - ansiprint(f"Affected by: \n{dmg_affected_by.rstrip(' | ') if dmg_affected_by else 'Nothing'}") + ansiprint(f"You dealt {dmg} damage({target.block} Blocked) to {target.name} with {' | '.join(card.damage_affected_by)}") target.block = 0 bus.publish(Message.AFTER_ATTACK, (self, target, card)) if target.health <= 0: @@ -421,44 +404,12 @@ def gain_gold(self, gold, dialogue=True): def bottle_card(self, card_type): while True: - valid_cards = [card for card in self.deck if card.get("Type") == card_type] - for possible_card in self.deck: - if possible_card in valid_cards: - ansiprint(f"{counter}: {possible_card['Name']} | {possible_card['Type']} | {possible_card['Energy']} Energy | {possible_card['Info']}") - counter += 1 - sleep(0.05) - else: - ansiprint(f"{counter}: {possible_card['Name']} | {possible_card['Type']} | {possible_card['Energy']} Energy | {possible_card['Info']}") - counter += 1 - sleep(0.05) - option = view.list_input('What card do you want to bottle? > ', self.deck, view.view_piles, lambda card: card['Type'] == card_type, f"That card is not {'an' if card_type == 'Attack' else 'a'} {card_type}.") - bottled_card = self.deck[option].copy() - bottled_card['Bottled'] = True - self.deck.insert(option, bottled_card) - del self.deck[option + 1] + option = view.list_input('What card do you want to bottle? > ', self.deck, view.view_piles, lambda card: card.name == card_type, f"That card is not {'an' if card_type == 'Attack' else 'a'} {card_type}.") + self.deck[option].bottled = True ansiprint(f"{self.deck[option]['Name']} is now bottled.") sleep(0.8) break - - def start_turn(self, turn): - ansiprint(f"{self.name}:") - self.energy = self.energy if items.relics['Ice Cream'] in self.relics else 0 - self.energy += self.energy_gain + self.buffs["Energized"] + self.buffs["Berzerk"] - if self.buffs["Energized"] > 0: - ansiprint(f"You gained {self.buffs['Energized']} extra energy because of Energized") - self.buffs["Energized"] = 0 - ansiprint("Energized wears off.") - if self.buffs["Berzerk"] > 0: - ansiprint(f"You gained {self.buffs['Berzerk']} extra energy because of Berzerk") - if self.buffs['Barricade']: - if turn > 1: - ansiprint('You kept your block because of Barriacade') - elif items.relics['Calipers'] in self.relics and self.block > 15: - self.block -= 15 - ansiprint(f'You kept {self.block} Block because of Calipers') - else: - self.block = 0 - + def end_of_turn_effects(self): if self.debuffs['Strength Down'] > 0: self.buffs['Strength'] -= self.debuffs['Strength Down'] @@ -579,7 +530,7 @@ def end_of_combat_effects(self, combat_type): ansiprint("Self Repair: ", end='') self.health_actions(self.buffs['Repair'], 'Heal') - def callback(self, message, data): + def callback(self, message, data: tuple): if message == Message.START_OF_COMBAT: self.in_combat = True self.draw_pile = random.sample(self.deck, len(self.deck)) @@ -590,6 +541,24 @@ def callback(self, message, data): self.hand.clear() self.exhaust_pile.clear() elif message == Message.START_OF_TURN: + turn = data + ansiprint(f"{self.name}:") + self.energy = self.energy if items.relics['Ice Cream'] in self.relics else 0 + self.energy += self.energy_gain + self.buffs["Energized"] + self.buffs["Berzerk"] + if self.buffs["Energized"] > 0: + ansiprint(f"You gained {self.buffs['Energized']} extra energy because of Energized") + self.buffs["Energized"] = 0 + ansiprint("Energized wears off.") + if self.buffs["Berzerk"] > 0: + ansiprint(f"You gained {self.buffs['Berzerk']} extra energy because of Berzerk") + if self.buffs['Barricade']: + if turn > 1: + ansiprint('You kept your block because of Barriacade') + elif items.relics['Calipers'] in self.relics and self.block > 15: + self.block -= 15 + ansiprint(f'You kept {self.block} Block because of Calipers') + else: + self.block = 0 self.draw_cards(False) self.plays_this_turn = 0 ei.tick_effects(self) @@ -785,18 +754,7 @@ def move_spam_check(self, target_move, max_count): def attack(self, dmg: int, times: int): dmg_affected_by = '' for _ in range(times): - if self.buffs['Strength'] != 0: - dmg += self.buffs['Strength'] - dmg_affected_by += f"<{'red' if self.buffs['Strength'] < 0 else 'light-cyan'}>Strength({'-' if self.buffs['Strength'] < 0 else '+'}{self.buffs['Strength']} dmg) | " - if player.debuffs['Vulnerable']: - dmg = math.floor(dmg * 1.5) - dmg_affected_by += "Vulnerable(x1.5 dmg) | " - if self.debuffs['Weak'] > 0: - dmg = math.floor(dmg * 0.75 - 0.15 if items.relics['Paper Krane'] in player.items.relics else 0) - dmg_affected_by += "Weak(x0.75 dmg) | " - if player.buffs['Intangible'] > 0: - dmg = 1 - dmg_affected_by = "Intangible(Reduce ALL damage to 1)" + bus.publish(Message.BEFORE_ATTACK, (self, dmg, player.block)) if dmg <= player.block: player.block -= dmg dmg = 0 @@ -804,35 +762,10 @@ def attack(self, dmg: int, times: int): elif dmg > player.block: dmg -= player.block dmg = max(0, dmg) - if items.relics['Torii'] in player.items.relics and dmg in range(2, 6): - dmg = min(dmg, 1) - dmg_affected_by += "Torii(dmg reduced to 1) | " - if items.relics['Tungsten Rod'] in player.items.relics: - dmg -= 1 - dmg_affected_by += "Tungsten Rod(dmg reduced by 1) | " - if items.relics['Red Skull'] in player.items.relics and player.health <= math.floor(player.health * 0.5) and not player.red_skull_active: - player.starting_strength += 3 - player.buffs['Strength'] += 3 - ansiprint("Red Skull gives you 3 Strength.") - if dmg > 0 and items.relics['Self-Forming Clay'] in player.items.relics: - ansiprint('Self-Forming Clay activated.') - player.buffs['Next Turn Block'] += 3 ansiprint(f"{self.name} dealt {dmg}({player.block} Blocked) damage to you.") player.block = 0 player.health -= dmg - if items.relics['Runic Cube'] in player.items.relics and dmg > 0: - player.draw_cards(True, 1) - if player.buffs['Flame Barrier'] > 0: - if self.block >= dmg: - self.block -= dmg - else: - dmg -= self.block - self.health -= dmg - self.block = 0 - ansiprint(f"{self.name} took {dmg}({self.block} Blocked) damage from Fire Breathing.") - if player.health <= math.floor(player.max_health * 0.5) and not player.meat_on_the_bone: - player.meat_on_the_bone = True - ansiprint("Meat on the Bone> activated.") + bus.publish(Message.AFTER_ATTACK, (self, dmg, player.block)) sleep(1) def remove_effect(self, effect_name, effect_type): diff --git a/game.py b/game.py index e540c4dc..2eaa9c4d 100644 --- a/game.py +++ b/game.py @@ -24,12 +24,11 @@ class Combat: def combat(self, current_map) -> None: """There's too much to say here.""" - global combat_turn boss_name = self.start_combat(self.tier) # Combat automatically ends when all enemies are dead. while len(active_enemies) > 0: # Draws cards, removes block, ticks debuffs, and activates start-of-turn buffs, debuffs, and relics. - bus.publish(Message.START_OF_TURN, (self.player, self.active_enemies, self.tier)) + bus.publish(Message.START_OF_TURN, (self.turn, )) while True: if len(active_enemies) == 0: self.end_combat(self.tier, killed_enemies=True) diff --git a/helper.py b/helper.py index 838e8618..1aa454bc 100644 --- a/helper.py +++ b/helper.py @@ -6,7 +6,8 @@ from copy import deepcopy from ansi_tags import ansiprint, strip from typing import Callable -from definitions import CombatTier +from definitions import CombatTier, Rarity, PlayerClass, CardType +from message_bus_tools import bus, Message active_enemies = [] @@ -24,7 +25,7 @@ def view_piles(self, pile: list[dict], shuffle=False, end=False, validator: Call return if shuffle is True: pile = random.sample(pile, len(pile)) - ansiprint("Cards are not shwon in order.") + ansiprint("Cards are not shown in order.") counter = 1 for card in pile: if validator(card): @@ -45,7 +46,7 @@ def view_relics(self, relic_pool, end=False, validator: Callable=lambda placehol for relic in relic_pool: if validator(relic): name_colors = {'Starter': 'starter', 'Common': 'white', 'Uncommon': 'uncommon', 'Rare': 'rare', 'Event': 'event'} - ansiprint(f"{counter}: <{name_colors[relic['Rarity']]}>{relic['Name']} | {relic['Class']} | {relic['Info']} | {relic['Flavor']}") + ansiprint(f"{counter}: <{name_colors[relic.rarity]}>{relic.name} | {relic.player_class} | {relic.info} | {relic.flavor_text}") counter += 1 sleep(0.05) if end: @@ -61,7 +62,7 @@ def view_potions(self, potion_pool, max_potions=3, numbered_list=True, validator if validator(potion): chosen_class_color = class_colors[potion['Class']] chosen_rarity_color = rarity_colors[potion['Rarity']] - ansiprint(f"{f'{counter}: ' if numbered_list else ''}<{chosen_rarity_color}>{potion['Name']} | <{chosen_class_color}>{potion['Class']} | {potion['Info']}") + ansiprint(f"{f'{counter}: ' if numbered_list else ''}<{chosen_rarity_color}>{potion.name} | <{chosen_class_color}>{potion.player_class} | {potion.info}") counter += 1 for _ in range(max_potions - len(potion_pool)): ansiprint(f"{f'{counter}: ' if numbered_list else ''}(Empty)") @@ -78,7 +79,7 @@ def display_ui(self, entity, enemies, combat=True): ansiprint("Relics: ") self.view_relics(entity.relics) ansiprint("Hand: ") - self.view_piles(entity.hand, entity, False, lambda card: (card.get("Energy", float('inf')) if card.get("Energy", float('inf')) != -1 else entity.energy) <= entity.energy) + self.view_piles(entity.hand, entity, False, lambda card: (card.energy_cost if card.energy_cost != -1 else entity.energy) <= entity.energy) if combat is True: counter = 1 ansiprint("\nEnemies:") @@ -175,9 +176,9 @@ def generate_card_rewards(self, reward_tier: CombatTier, amount: int, entity: ob Boss combat rewards: Rare: 100% | Uncommon: 0% | Common: 0% """ - common_cards = [card for card in card_pool.values() if card.get("Rarity") == "Common" and card.get("Type") not in ('Status', 'Curse') and card.get('Class') == entity.player_class] - uncommon_cards = [card for card in card_pool.values() if card.get("Rarity") == "Uncommon" and card.get("Type") not in ('Status', 'Curse') and card.get('Class') == entity.player_class] - rare_cards = [card for card in card_pool.values() if card.get("Rarity") == "Rare" and card.get("Type") not in ('Status', 'Curse') and card.get('Class') == entity.player_class] + common_cards = [card() for card in card_pool if card.rarity == Rarity.COMMON and card.type not in (CardType.STATUS, CardType.CURSE) and card.player_class == entity.player_class] + uncommon_cards = [card() for card in card_pool if card.rarity == Rarity.UNCOMMON and card.type not in (CardType.STATUS, CardType.CURSE) and card.player_class == entity.player_class] + rare_cards = [card() for card in card_pool if card.rarity == Rarity.RARE and card.type not in (CardType.STATUS, CardType.CURSE) and card.player_class == entity.player_class] assert len(common_cards) > 0, f"Common pool is empty." assert len(uncommon_cards) > 0, f"Uncommon pool is empty." assert len(rare_cards) > 0, f"Rare pool is empty." @@ -200,9 +201,9 @@ def generate_potion_rewards(self, amount: int, entity: object, potion_pool: dict """You have a 40% chance to get a potion at the end of combat. -10% when you get a potion. +10% when you don't get a potion.""" - common_potions: list[dict] = [potion for potion in potion_pool.values() if potion.get("Rarity") == "Common" and (potion.get("Class") == "Any" or entity.player_class in potion.get('Class'))] - uncommon_potions: list[dict] = [potion for potion in potion_pool.values() if potion.get("Rarity") == "Uncommon" and (potion.get("Class") == "Any" or entity.player_class in potion.get('Class'))] - rare_potions: list[dict] = [potion for potion in potion_pool.values() if potion.get("Rarity") == "Rare" and (potion.get("Class") == "Any" or entity.player_class in potion.get('Class'))] + common_potions: list[dict] = [potion() for potion in potion_pool if potion.rarity == Rarity.COMMON and (potion.player_class == PlayerClass.ANY or potion.player_class == entity.player_class)] + uncommon_potions: list[dict] = [potion() for potion in potion_pool if potion.rarity == Rarity.UNCOMMON and (potion.player_class == PlayerClass.ANY or potion.player_class == entity.player_class)] + rare_potions: list[dict] = [potion() for potion in potion_pool if potion.rarity == Rarity.RARE and (potion.player_class == PlayerClass.ANY or potion.player_class == entity.player_class)] assert len(common_potions) > 0, f"Common potions pool is empty." assert len(uncommon_potions) > 0, f"Uncommon potions pool is empty." assert len(rare_potions) > 0, f"Rare potions pool is empty." @@ -219,9 +220,10 @@ def generate_potion_rewards(self, amount: int, entity: object, potion_pool: dict def generate_relic_rewards(self, source: str, amount: int, entity, relic_pool: dict, chance_based=True) -> list[dict]: - common_relics = [relic for relic in relic_pool.values() if (relic.get('Rarity') == 'Common') and (relic.get('Class') == entity.player_class) and (relic not in entity.relics)] - uncommon_relics = [relic for relic in relic_pool.values() if (relic.get('Rarity') == 'Uncommon') and (relic.get('Class') == entity.player_class) and (relic not in entity.relics)] - rare_relics = [relic for relic in relic_pool.values() if (relic.get('Rarity') == 'Rare') and (relic.get('Class') == entity.player_class) and (relic not in entity.relics)] + claimed_relics = [relic.name for relic in entity.relics] + common_relics = [relic() for relic in relic_pool if relic.get('Rarity') == 'Common' and relic.get('Class') == entity.player_class and relic not in entity.relics and relic.name not in claimed_relics] + uncommon_relics = [relic() for relic in relic_pool if relic.get('Rarity') == 'Uncommon' and relic.get('Class') == entity.player_class and relic not in entity.relics and relic.name not in claimed_relics] + rare_relics = [relic() for relic in relic_pool if relic.get('Rarity') == 'Rare' and relic.get('Class') == entity.player_class and relic not in entity.relics and relic.name not in claimed_relics] all_relic_pool = common_relics + uncommon_relics + rare_relics rarities = [common_relics, uncommon_relics, rare_relics] assert len(common_relics) > 0, "Common relics pool is empty." @@ -256,11 +258,6 @@ def claim_relics(self, choice: bool, entity: object, relic_amount: int, relic_po sleep(0.5) sleep(0.5) while len(rewards) > 0 and choice: - counter = 1 - for relic in rewards: - ansiprint(f"{counter}: {relic['Name']} | {relic['Class']} | {relic['Rarity']} | {relic['Info']} | {relic['Flavor']}") - counter += 1 - sleep(0.05) option = view.list_input('What relic do you want? > ', rewards, view.view_relics) if not option: sleep(1.5) @@ -273,38 +270,29 @@ def claim_relics(self, choice: bool, entity: object, relic_amount: int, relic_po def claim_potions(self, choice: bool, potion_amount: int, entity, potion_pool: dict, rewards=None, chance_based=True): for relic in entity.relics: - if relic['Name'] == 'Sozu': + if relic.name == 'Sozu': return if not rewards: rewards = self.generate_potion_rewards(potion_amount, entity, potion_pool, chance_based) if not choice: for i in range(potion_amount): - potion_pool.append(rewards[i]) - print(f"{entity.name} obtained {rewards[i]['Name']} | {rewards[i]['Info']}") - rewards.remove(rewards[i]) + if len(entity.potions) <= entity.max_potions: + entity.potions.append(rewards[i]) + print(f"{entity.name} obtained {rewards[i].name} | {rewards[i].info}") + rewards.remove(rewards[i]) sleep(1.5) view.clear() while len(rewards) > 0: - counter = 1 print(f"Potion Bag: ({len(potion_pool)} / {entity.max_potions})") view.view_potions(entity, False) print() print("Potion reward(s):") - counter = 1 - for potion in rewards: - ansiprint(f"{counter}: {potion['Name']} | {potion['Rarity']} | {potion['Class']} | {potion['Info']}") - counter += 1 - print() option = view.list_input('What potion you want? >', rewards, view.view_potions) if len(potion_pool) == entity.max_potions: - ansiprint("Potion bag full!") + ansiprint("Potion bag full!") sleep(1) option = input("Discard a potion?(y|n) > ") if option == 'y': - counter = 1 - for potion in potion_pool: - ansiprint(f"{counter}: {potion['Rarity']} | {potion['Class']} | {potion['Name']} | {potion['Info']}") - counter += 1 option = view.list_input('What potion do you want to discard? > ', potion_pool, view.view_potions) print(f"Discarded {potion_pool[option]['Name']}.") potion_pool.remove(potion_pool[option]) @@ -325,38 +313,29 @@ def card_rewards(self, tier: str, choice: bool, entity, card_pool: dict, rewards while True: if choice: chosen_reward = view.list_input('What card do you want? > ', rewards, view.view_piles) - if (entity.upgrade_attacks or entity.upgrade_skills or entity.upgrade_powers) and rewards[chosen_reward]['Type'] in ['Attack', 'Skill', 'Power']: - entity.card_actions(rewards[chosen_reward], 'Upgrade') - for relic in entity.relics: - if relic['Name'] == 'Ceramic Fish': - ansiprint("From Ceramic Fish: ", end='') - entity.gain_gold(9) + # This could probably be condensed + if entity.upgrade_attacks and rewards[chosen_reward].type == 'Attack': + rewards[chosen_reward].upgrade() + elif entity.upgrade_skills and rewards[chosen_reward].type == 'Skill': + rewards[chosen_reward].upgrade() + elif entity.upgrade_powers and rewards[chosen_reward].type == 'Power': + rewards[chosen_reward].upgrade() entity.deck.append(rewards[chosen_reward]) - ansiprint(f"{entity.name} obtained {rewards[chosen_reward]['Name']}") + ansiprint(f"{entity.name} obtained {rewards[chosen_reward].name}") rewards.clear() break for card in rewards: - if card.get('Type') == 'Curse' and entity.block_curses > 0: - ansiprint(f"{card['Name']} was negated by Omamori.") - entity.block_curses -= 1 - if entity.block_curses == 0: - ansiprint('Omamori is depleted.') - continue - if card.get('Type') == 'Curse' and entity.darkstone_health: - ansiprint("Darkstone Periapt activated.") - entity.health_actions(6, "Max Health") + bus.publish(Message.ON_CARD_ADD, (entity, card)) entity.deck.append(card) - print(f"{entity.name} obtained {card['Name']}") + print(f"{entity.name} obtained {card.name}") rewards.remove(card) - if entity.gold_on_card_add: - entity.gold += 9 - ansiprint('You gained 9 Gold from Ceramic Fish.') break rewards.clear() sleep(1) class EffectInterface(): '''Responsible for applying effects, creating buff/debuff dictionaries, and counting down certain effects''' + # I'm gonna leave this alone for now because that's a lot of changes to be made def __init__(self): # Effects that wear off at the start of your turn despite not being duration effects self.REMOVE_ON_TURN = ('Next Turn Block', 'Energized', 'Amplify', 'Burst', 'Double Tap', 'Duplication', 'Flame Barrier', 'Rebound', 'Simmering Rage', diff --git a/items.py b/items.py index 0f72febe..4ce083a2 100644 --- a/items.py +++ b/items.py @@ -692,6 +692,7 @@ def use_reaper(enemies, using_card, entity): } cards = (IroncladStrike, IroncladDefend, Bash) +# Need to convert this to a method once I do the potions to eliminate this global variable. sacred_multi: int = 1 def activate_sacred_bark(): global sacred_multi diff --git a/message_bus_tools.py b/message_bus_tools.py index 1a1801db..50402cff 100644 --- a/message_bus_tools.py +++ b/message_bus_tools.py @@ -1,4 +1,5 @@ from enum import StrEnum +from typing import Any from ansi_tags import ansiprint from definitions import Rarity, CardType, PlayerClass @@ -16,6 +17,7 @@ class Message(StrEnum): ON_DRAW = 'on_draw' ON_EXHAUST = 'on_exhaust' ON_CARD_PLAY = 'on_card_play' + ON_CARD_ADD = 'on_card_add' class MessageBus(): '''This is a Pub/Sub, or Publish/Subscribe, message bus. It allows components to subscribe to messages, From 51c03aa2a23fa07bc2317cda401373b32ed0dfd1 Mon Sep 17 00:00:00 2001 From: vesper-arch Date: Sat, 10 Feb 2024 19:29:25 +0000 Subject: [PATCH 02/18] Whoops, forgot that --- entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entities.py b/entities.py index 52331e0f..a252e8b9 100644 --- a/entities.py +++ b/entities.py @@ -834,7 +834,7 @@ def end_of_turn_effects(self): def create_player(): - return Player(80, 0, 3, [deepcopy(card) for card in [items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladDefend, items.IroncladDefend, items.IroncladDefend, items.IroncladDefend, items.Bash]]) + return Player(80, 0, 3, [card() for card in [items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladStrike, items.IroncladDefend, items.IroncladDefend, items.IroncladDefend, items.IroncladDefend, items.Bash]]) # Characters player = create_player() From e48dc698f3994b7f8233e7c82f1b20ec6c1a1552 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 06:35:41 -0600 Subject: [PATCH 03/18] Add displayer test --- test_displayer.py | 31 +++++++++++++++++++++++++++++++ test_generators.py | 12 ------------ 2 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 test_displayer.py delete mode 100644 test_generators.py diff --git a/test_displayer.py b/test_displayer.py new file mode 100644 index 00000000..157fc334 --- /dev/null +++ b/test_displayer.py @@ -0,0 +1,31 @@ +import inspect + +import pytest + +import enemy_catalog +import entities +import helper +from ansi_tags import ansiprint + + +@pytest.fixture +def all_enemies(): + enemies = [] + for name, obj in inspect.getmembers(enemy_catalog): + if inspect.isclass(obj) and name not in ["Enemy", "Hexaghost", "Lagavulin", "Sentry"]: + enemies.append((name,obj)) + return enemies + + +class TestDisplayers(): + def test_display_actual_damage(self, all_enemies): + disp = helper.Displayer() + player = entities.Player(health=80, block=3, max_energy=3, deck=[]) + + for name, class_obj in all_enemies: + print(f"---> Testing: {name}") + enemy = class_obj() + enemy.set_intent() + result = disp.display_actual_damage(enemy.intent, target=player, entity=enemy) + ansiprint(result[0]) + ansiprint(result[1]) \ No newline at end of file diff --git a/test_generators.py b/test_generators.py deleted file mode 100644 index 5c0685af..00000000 --- a/test_generators.py +++ /dev/null @@ -1,12 +0,0 @@ -import entities -import helper -import pytest -import enemy_catalog -import random - -# tbd -# class TestGenerators(): -# def test_init_effects_player_debuffs(self, ei): -# gen = helper.Generators() -# output = gen.claim_potions() -# assert "Vulnerable" in output From 73247f7e820664439caa98414bcce89a18dda4cf Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 06:37:06 -0600 Subject: [PATCH 04/18] fix typo --- enemy_catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enemy_catalog.py b/enemy_catalog.py index 4c535744..0774c69f 100644 --- a/enemy_catalog.py +++ b/enemy_catalog.py @@ -179,7 +179,7 @@ def __init__(self): super().__init__([13, 17], 0, "Fat Gremlin") def set_intent(self): - self.next_move, self.intent = [("Smash", "Attack", (4,)), ("Debuff", ("Weak", 1))], "Attack Σ4 / Debuff" + self.next_move, self.intent = [("Smash", "Attack", (4,)), ("Debuff", ("Weak", 1))], "Attack Σ4 / Debuff" class MadGremlin(Enemy): def __init__(self): From f65998e5d2fbb8044cc9cd7bd0378dac35320e9d Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 14:19:30 +0000 Subject: [PATCH 05/18] Avoid trying to comment on forked PRs -- no permissions --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 543a929f..35925c92 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -43,4 +43,4 @@ jobs: with: pytest-coverage-path: ./pytest-coverage.txt junitxml-path: ./pytest.xml - if: github.ref != 'refs/heads/main' \ No newline at end of file + if: github.ref != 'refs/heads/main' && github.event_name == 'pull_request' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == 'false' \ No newline at end of file From d452b1f67fbdbd8c07dfa4571e95b71570c4a5fa Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 06:36:58 -0600 Subject: [PATCH 06/18] helpful script --- scripts/ptw.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 scripts/ptw.sh diff --git a/scripts/ptw.sh b/scripts/ptw.sh new file mode 100755 index 00000000..bbf02509 --- /dev/null +++ b/scripts/ptw.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# Convenience script for running tests continouosly. +poetry run -- ptw -v -- -s --tb=short \ No newline at end of file From e0fe5c5a42be5c92483b3eadb130076c0ee78601 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 06:37:42 -0600 Subject: [PATCH 07/18] better linting --- .devcontainer/devcontainer.json | 5 +++-- .vscode/settings.json | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b6535f64..f892fe52 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,13 +24,14 @@ "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "python.analysis.lintingEnabled": true, "python.analysis.linter": "pylint", - "python.analysis.linting.pylintArgs": ["--rcfile", "/path/to/your/.pylintrc"] + "python.analysis.linting.pylintArgs": ["--rcfile", "/workspaces/Slay-The-Spire-in-Python/.pylintrc"] }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-python.python", "ms-python.vscode-pylance", - "ms-python.pylint" + "ms-python.pylint", + "charliermarsh.ruff" ], // Use 'postCreateCommand' to run commands after the container is created. diff --git a/.vscode/settings.json b/.vscode/settings.json index 133ad1e8..44f4ed08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "editor.lineHeight": 23, "editor.cursorBlinking": "solid", "editor.cursorSmoothCaretAnimation": "on", - "editor.fontFamily": "Consolas, 'Courier New', monospace" + "editor.fontFamily": "Consolas, 'Courier New', monospace", + "audioCues.lineHasInlineSuggestion": "off" } \ No newline at end of file From 7603fac63e9514adade8d40ecc3e05d10b1dbcd8 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 22:21:20 +0000 Subject: [PATCH 08/18] Add colorless cards --- definitions.py | 1 + items.py | 261 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+) diff --git a/definitions.py b/definitions.py index 45357676..24ad05a8 100644 --- a/definitions.py +++ b/definitions.py @@ -11,6 +11,7 @@ class EncounterType(StrEnum): ELITE = auto() REST_SITE = auto() BOSS = auto() + SHOP = auto() UNKNOWN = auto() class Rarity(StrEnum): diff --git a/items.py b/items.py index 4ce083a2..196a13b2 100644 --- a/items.py +++ b/items.py @@ -499,6 +499,7 @@ def use_reaper(enemies, using_card, entity): sleep(0.5) sleep(1) view.clear() + relics: dict[str: dict] = { # Starter Relics 'Burning Blood': {'Name': 'Burning Blood', 'Class': 'Ironclad', 'Rarity': 'Starter', 'Health': 6, 'Info': 'At the end of combat, heal 6 HP', 'Flavor': "Your body's own blood burns with an undying rage."}, @@ -691,6 +692,266 @@ def use_reaper(enemies, using_card, entity): 'Circlet': {'Name': 'Circlet', 'Class': 'Any', 'Rarity': 'Special', 'Info': 'Looks pretty.', 'Flavor': 'You ran out of relics to find. Impressive!'} } cards = (IroncladStrike, IroncladDefend, Bash) +cards_old = { + # Ironclad cards + 'Strike': {'Name': 'Strike', 'Damage': 6, 'Energy': 1, 'Rarity': 'Basic', 'Target': 'Single', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ6 damage.', 'Effects+': {'Damage': 9, 'Info': 'Deal Σ9 damage.'}}, + + 'Defend': {'Name': 'Defend', 'Block': 5, 'Energy': 1, 'Target': 'Yourself', 'Rarity': 'Basic', 'Type': 'Skill', 'Class': 'Ironclad', 'Info': 'Gain Ω5 Block.', 'Effects+': {'Block': 8, 'Info': 'Gain Ω8 Block'}}, + + 'Bash': {'Name': 'Bash', 'Damage': 8, 'Vulnerable': 2, 'Energy': 2, 'Target': 'Single', 'Rarity': 'Basic', 'Class': 'Ironclad', 'Type': 'Attack', 'Info': 'Deal Σ8 damage. Apply 2 Vulnerable', 'Effects+': {'Damage': 10, 'Vulnerable': 3, 'Info': 'Deal Σ10 damage. Apply 3 Vulnerable'}}, + + 'Anger': {'Name': 'Anger', 'Damage': 6, 'Energy': 0, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ6 damage. Add a copy of this card to your discard pile.', 'Effects+': {'Damage': 8, 'Info': 'Deal Σ8 damage. Add a copy of this card to your discard pile.'}, 'Function': use_anger}, + + 'Armaments': {'Name': 'Armaments', 'Target': 'Yourself', 'Energy': 1, 'Rarity': 'Common', 'Type': 'Skill', 'Class': 'Ironclad', 'Info': 'Gain Ω5 Block. Upgrade a card in your hand for the rest of combat.', + 'Effects+': {'Info': 'Gain Ω5 Block. Upgrade ALL cards in your hand for the rest of combat.'}, 'Function': use_armaments}, + + 'Body Slam': {'Name': 'Body Slam', 'Energy': 1, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal damage equal to your Block(Σ0)', 'Effects+': {'Energy': 0}, 'Function': use_bodyslam}, + + 'Clash': {'Name': 'Clash', 'Damage': 14, 'Energy': 0, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Can only be played is every card in your hand is an Attack. Deal Σ18 damage.', + 'Effects+': {'Damage': 18, 'Info': 'Can only be played if every card in your hand is an Attack. Deal Σ18 damage.'}, 'Function': use_clash}, + + 'Cleave': {'Name': 'Cleave', 'Damage': 8, 'Target': 'Any', 'Energy': 1, 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ8 damage to ALL enemies', 'Effects+': {'Damage': 11, 'Info': 'Deal Σ11 damage to ALL enemies.'}, 'Function': use_cleave}, + + 'Clothesline': {'Name': 'Clothesline', 'Energy': 2, 'Damage': 12, 'Weak': 2, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ12 damage. Apply 2 Weak', 'Effects+': {'Damage': 14, 'Weak': 3, 'Info': 'Deal Σ14 damage. Apply 3 Weak.'}, 'Function': use_clothesline}, + + 'Flex': {'Name': 'Flex', 'Strength': 2, 'Energy': 0, 'Target': 'Yourself', 'Rarity': 'Common', 'Type': 'Skill', 'Class': 'Ironclad', 'Info': 'Gain 2 Strength. At the end of your turn, lose 2 Strength', + 'Effects+': {'Strength': 4, 'Info': 'Gain 4 Strength. At the end of your turn, lose 4 Strength.'}, 'Function': use_flex}, + + 'Havoc': {'Name': 'Havoc', 'Energy': 1, 'Target': 'Area', 'Rarity': 'Common', 'Type': 'Skill', 'Class': 'Ironclad', 'Info': 'Play the top card of your draw pile and Exhaust it.', 'Effects+': {'Energy': 0}, 'Function': use_havoc}, + + 'Headbutt': {'Name': 'Headbutt', 'Damage': 9, 'Energy': 1, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ9 damage. Place a card from your discard pile on top of your draw pile.', + 'Effects+': {'Damage': 12, 'Info': 'Deal Σ12 damage. Place a card from your discard pile on top of your draw pile.'}, 'Function': use_headbutt}, + + 'Heavy Blade': {'Name': 'Heavy Blade', 'Damage': 14, 'Strength Multi': 3, 'Energy': 2, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ14 damage. Strength affects this card 3 times.', + 'Effects+': {'Damage': 18, 'Strength Multi': 5, 'Info': 'Deal Σ14 damage. Strength affects this card 3 times.'}, 'Function': use_heavyblade}, + + 'Iron Wave': {'Name': 'Iron Wave', 'Damage': 5, 'Block': 5, 'Energy': 1, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Gain Ω5 Block. Deal Σ5 damage.', 'Effects+': {'Damage': 7, 'Block': 7, 'Info': 'Gain Ω7 Block. Deal Σ7 damage.'}, 'Function': use_ironwave}, + + 'Perfected Strike': {'Name': 'Perfected Strike', 'Damage Per "Strike"': 2, 'Energy': 2, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ6 damage. Deals 2 additional damage for ALL your cards containing "Strike".', + 'Effects+': {'Damage Per "Strike"': 3, 'Info': 'Deal Σ6 damage. Deals 3 additional damage for ALL your cards containing "Strike".'}, 'Function': use_perfectedstrike}, + + 'Pommel Strike': {'Name': 'Pommel Strike', 'Damage': 9, 'Cards': 1, 'Energy': 1, 'Target': 'Single', 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ9 damage. Draw 1 card.', 'Effects+': {'Damage': 10, 'Cards': 2, 'Info': 'Deal Σ10 damage. Draw 2 cards.'}, 'Function': use_pommelstrike}, + + 'Shrug it Off': {'Name': 'Shrug it Off', 'Block': 8, 'Cards': 1, 'Energy': 1, 'Rarity': 'Common', 'Target': 'Yourself', 'Type': 'Skill', 'Class': 'Ironclad', 'Info': 'Gain Ω8 Block. Draw 1 card.', 'Effects+': {'Block': 11, 'Info': 'Gain Ω11 Block. Draw 1 card.'}, 'Function': use_shrugitoff}, + + 'Sword Boomerang': {'Name': 'Sword Boomerang', 'Times': 3, 'Target': 'Random', 'Energy': 1, 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ3 damage to a random enemy 3 times.', 'Effects+': {'Times': 4, 'Info': 'Deal Σ3 damage to a random enemy 4 times.'}, 'Function': use_swordboomerang}, + + 'Thunderclap': {'Name': 'Thunderclap', 'Damage': 4, 'Target': 'Any', 'Energy': 1, 'Rarity': 'Common', 'Type': 'Attack', 'Class': 'Ironclad', 'Info': 'Deal Σ4 damage and apply 1 Vulnerable to ALL enemies.', + 'Effects+': {'Damage': 7, 'Info': 'Deal Σ7 damage and apply 1 Vulnerable to ALL enemies.'}, 'Function': use_thunderclap}, + + 'True Grit': {'Name': 'True Grit', 'Class': 'Ironclad', 'Rarity': 'Common', 'Target': 'Yourself', 'Type': 'Skill', 'Block': 7, 'Energy': 1, 'Info': 'Gain Ω7 Block. Exhaust a random card in your hand.', + 'Effects+': {'Block': 9, 'Info': 'Gain Ω9 Block. Exhaust a card in your hand.'}, 'Function': use_truegrit}, + + 'Twin Strike': {'Name': 'Twin Strike', 'Class': 'Ironclad', 'Rarity': 'Common', 'Type': 'Attack', 'Target': 'Single', 'Damage': 5, 'Energy': 1, 'Info': 'Deal Σ5 damage twice.', 'Effects+': {'Damage': 7, 'Info': 'Deal Σ7 damage twice.'}, 'Function': use_twinstrike}, + + 'Warcry': {'Name': 'Warcry', 'Class': 'Ironclad', 'Rarity': 'Common', 'Target': 'Yourself', 'Type': 'Skill', 'Exhaust': True, 'Cards': 1, 'Energy': 0, 'Info': 'Draw 1 card. Put a card from your hand on top of your draw pile. Exhaust.', + 'Effects+': {'Cards': 2, 'Info': 'Draw 2 cards. Put a card from your hand on top of your draw pile. Exhaust.'}, 'Function': use_warcry}, + + 'Wild Strike': {'Name': 'Wild Strike', 'Class': 'Ironclad', 'Rarity': 'Common', 'Type': 'Attack', 'Target': 'Single', 'Damage': 12, 'Energy': 1, 'Info': 'Deal Σ12 damage. Shuffle a Wound into your draw pile.', + 'Effects+': {'Damage': 17, 'Info': 'Deal Σ17 damage. Shuffle a Wound into your draw pile.'}, 'Function': use_wildstrike}, + + # Uncommon cards + 'Battle Trance': {'Name': 'Battle Trance', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Cards': 3, 'Energy': 0, 'Info': "Draw 3 cards. You can't draw additional cards this turn.", 'Effects+': {'Cards': 4, 'Info': "Draw 4 cards. You can't draw additional cards this turn."}, 'Function': use_battletrance}, + + 'Blood for Blood': {'Name': 'Blood for Blood', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 18, 'Energy': 4, 'Info': 'Costs 1 less Energy for each time you lose HP this combat. Deal Σ18 damage.', + 'Effects+': {'Damage': 22, 'Info': 'Costs 1 less Energy for each time you lose HP this combat. Deal Σ22 damage.'}, 'Function': use_bloodforblood}, + + 'Bloodletting': {'Name': 'Bloodletting', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Energy Gain': 2, 'Energy': 0, 'Info': 'Lose 3 HP. Gain 2 Energy.', 'Effects+': {'Energy Gain': 3, 'Info': 'Lose 3 HP. Gain 3 Energy.'}, 'Function': use_bloodletting}, + + 'Burning Pact': {'Name': 'Burning Pact', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Cards': 2, 'Energy': 1, 'Info': 'Exhaust 1 card. Draw 2 cards.', 'Effects+': {'Cards': 3, 'Info': 'Exhaust 1 card. Draw 3 cards.'}, 'Function': use_burningpact}, + + 'Carnage': {'Name': 'Carnage', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Target': 'Single', 'Type': 'Attack', 'Ethereal': True, 'Damage': 20, 'Energy': 2, 'Info': 'Ethereal. Deal Σ20 damage.', 'Effects+': {'Damage': 28, 'Info': 'Ethereal. Deal Σ28 damage.'}, 'Function': use_carnage}, + + 'Combust': {'Name': 'Combust', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Target': 'Yourself', 'Type': 'Power', 'Combust': 5, 'Energy': 1, 'Info': 'At the end of your turn, lose 1 HP and deal 5 damage to ALL enemies.', 'Effects+': {'Combust': 7, 'Info': 'At the end of your turn, lose 1 HP and deal 7 damage to ALL enemies'}, 'Function': use_combust}, + + 'Dark Embrace': {'Name': 'Dark Embrace', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Target': 'Yourself', 'Type': 'Power', 'Energy': 2, 'Info': 'Whenever a card is Exhausted, draw 1 card.', 'Effects+': {'Energy': 1}, 'Function': use_darkembrace}, + + 'Disarm': {'Name': 'Disarm', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Single', 'Exhaust': True, 'Strength Loss': 2, 'Energy': 1, 'Info': 'Enemy loses 2 Strength. Exhaust.', + 'Effects+': {'Strength Loss': 3, 'Info': 'Enemy loses 3 Strength. Exhaust.'}, 'Function': use_disarm}, + + 'Dropkick': {'Name': 'Dropkick', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 5, 'Energy': 1, 'Info': 'Deal Σ5 damage. If the enemy has Vulnerable, gain 1 Energy and draw 1 card.', + 'Effects+': {'Damage': 8, 'Info': 'Deal Σ8 damage. If the enemy has Vulnerable, gain 1 Energy and draw 1 card.'}, 'Function': use_dropkick}, + + 'Dual Wield': {'Name': 'Dual Wield', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Copies': 1, 'Energy': 1, 'Info': 'Create a copy of an Attack or Power card in your hand.', + 'Effects+': {'Copies': 2, 'Info': 'Create 2 copies of an Attack or Power card in your hand'}, 'Function': use_dualwield}, + + 'Entrench': {'Name': 'Entrench', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Energy': 2, 'Info': 'Double your Block.', 'Effects+': {'Energy': 1}, 'Function': use_entrench}, + + 'Evolve': {'Name': 'Evolve', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Power', 'Target': 'Yourself', 'Evolve': 1, 'Energy': 1, 'Info': 'Whenever you draw a Status card, draw 1 card.', 'Effects+': {'Evolve': 2, 'Info': 'Whenever you draw a Status cards, draw 2 cards.'}, 'Function': use_evolve}, + + 'Fire Breathing': {'Name': 'Fire Breathing', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Power', 'Target': 'Yourself', 'Fire Breathing': 6, 'Energy': 1, 'Info': 'Whenever you draw a Status or Curse, deal 6 damage to ALL enemies.', + 'Effects+': {'Fire Breathing': 10, 'Info': 'Whenever you draw a Status or Curse card, deal 10 damage to ALL enemies.'}, 'Function': use_firebreathing}, + + 'Flame Barrier': {'Name': 'Flame Barrier', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Block': 12, 'Energy': 2, 'Info': "Gain Ω12 Block. Whenever you're attacked this turn, deal 4 damage back.", + 'Effects+': {'Block': 16, 'Info': "Gain Ω16 Block. Whenever you're attacked this turn, deal 4 damage back."}, 'Function': use_flamebarrier}, + + 'Ghostly Armor': {'Name': 'Ghostly Armor', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Ethereal': True, 'Block': 10, 'Energy': 1, 'Info': 'Ethereal. Gain Ω10 Block.', + 'Effects+': {'Block': 13, 'Info': 'Ethereal. Gain Ω13 Block.'}, 'Function': use_ghostlyarmor}, + + 'Hemokinesis': {'Name': 'Hemokinesis', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 15, 'Energy': 1, 'Info': 'Lose 2 HP. Deal Σ15 damage.', 'Effects+': {'Damage': 20, 'Info': 'Lose 2 HP. Deal Σ20 damage.'}, 'Function': use_hemokinesis}, + + 'Infernal Blade': {'Name': 'Infernal Blade', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Energy': 1, 'Info': 'Add a random Attack into your hand. It costs 0 this turn. Exhaust.', 'Effects+': {'Energy': 0}, 'Function': use_infernalblade}, + + 'Inflame': {'Name': 'Inflame', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Power', 'Target': 'Yourself', 'Strength': 2, 'Energy': 1, 'Info': 'Gain 2 Strength.', 'Effects+': {'Strength': 3, 'Info': 'Gain 3 Strength.'}, 'Function': use_inflame}, + + 'Intimidate': {'Name': 'Intimidate', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Area', 'Exhaust': True, 'Weak': 1, 'Energy': 0, 'Info': 'Apply 1 Weak to ALL enemies. Exhaust.', + 'Effects+': {'Weak': 2, 'Info': 'Apply 2 Weak to ALL enemies. Exhaust.'}, 'Function': use_intimidate}, + + 'Metallicize': {'Name': 'Metallicize', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Power', 'Target': 'Yourself', 'Metallicize': 3, 'Energy': 1, 'Info': 'At the end of your turn, gain 3 Block.', + 'Effects+': {'Metallicize': 4, 'Info': 'At the end of your turn, gain 4 Block.'}, 'Function': use_metallicize}, + + 'Power Through': {'Name': 'Power Through', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Block': 15, 'Energy': 1, 'Info': 'Add 2 Wounds to your hand. Gain Ω15 Block.', + 'Effects+': {'Block': 20, 'Info': 'Add 2 Wounds to your hand. Gain Ω20 Block'}, 'Function': use_powerthrough}, + + 'Pummel': {'Name': 'Pummel', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Exhaust': True, 'Times': 4, 'Energy': 1, 'Info': 'Deal Σ2 damage 4 times.', 'Effects+': {'Times': 5, 'Info': 'Deal Σ2 damage 5 times.'}, 'Function': use_pummel}, + + 'Rage': {'Name': 'Rage', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Rage': 3, 'Energy': 0, 'Info': 'Whenever you play an Attack, gain 3 block.', + 'Effects+': {'Rage': 5, 'Info': 'Whenever you play an Attack, gain 5 Block.'}, 'Function': use_rage}, + + 'Rampage': {'Name': 'Rampage', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage+': 5, 'Damage': 8, 'Energy': 1, 'Info': "Deal Σ8 damage. Increase this card's damage by 5 this combat.", + 'Effects+': {'Damage+': 8, 'Info': "Deal Σ8 damage. Increase this card's damage by 8 this combat."}, 'Function': use_rampage}, + + 'Reckless Charge': {'Name': 'Reckless Charge', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 7, 'Energy': 0, 'Info': 'Deal Σ7 damage. Shuffle a Dazed into your draw pile.', + 'Effects+': {'Damage': 10, 'Info': 'Deal Σ10 damage. Shuffle a Dazed into your draw pile.'}, 'Function': use_recklesscharge}, + + 'Rupture': {'Name': 'Rupture', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Power', 'Target': 'Yourself', 'Rupture': 1, 'Energy': 1, 'Info': 'Whenever you lose HP from a card, gain 1 Strength.', + 'Effects+': {'Rupture': 2, 'Info': 'Whenever you lose HP from a card, gain 2 Strength.'}, 'Function': use_rupture}, + + 'Searing Blow': {'Name': 'Searing Blow', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 12, 'Upgrade Count': 0, 'Energy': 2, 'Info': 'Deal Σ12 damage. Can be upgraded any number of times.', 'Function': use_searingblow}, + + 'Second Wind': {'Name': 'Second Wind', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Block Per Card': 5, 'Energy': 1, 'Info': 'Exhaust all non-Attack cards in your hand and gain 5 Block for each card Exhausted.', + 'Effects+': {'Block Per Card': 7, 'Info': 'Exhaust all non-Attack cards in your hand and gain 7 Block for each card Exhausted.'}, 'Function': use_secondwind}, + + 'Seeing Red': {'Name': 'Seeing Red', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Exhaust': True, 'Energy': 1, 'Info': 'Gain 2 Energy. Exhaust.', 'Effects+': {'Energy': 0}, 'Function': use_seeingred}, + + 'Sentinel': {'Name': 'Sentinel', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Yourself', 'Block': 5, 'Energy Gain': 2, 'Energy': 1, 'Info': 'Gain Ω5 Block. If this card is Exhausted, gain 2 Energy', + 'Effects+': {'Block': 8, 'Energy Gain': 3, 'Info': 'Gain Ω8 Block. If this card is Exhausted, gain 3 Energy.'}, 'Function': use_sentinel}, + + 'Sever Soul': {'Name': 'Sever Soul', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 16, 'Energy': 2, 'Info': 'Exhaust all non-Attack cards in your hand. Deal Σ16 damage.', + 'Effects+': {'Damage': 22, 'Info': 'Exhaust all non-Attack cards in your hand. Deal Σ22 damage.'}, 'Function': use_seversoul}, + + 'Shockwave': {'Name': 'Shockwave', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Skill', 'Target': 'Area', 'Exhaust': True, 'Weak/Vulnerable': 3, 'Energy': 2, 'Info': 'Apply 3 Weak and Vulnerable to ALL enemies. Exhaust.', + 'Effects+': {'Weak/Vulnerable': 5, 'Info': 'Apply 5 Weak and Vulnerable to ALL enemies. Exhaust.'}, 'Function': use_shockwave}, + + # Ignore Spot Weakness because intent doesn't exist yet + + 'Uppercut': {'Name': 'Uppercut', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Single', 'Damage': 13, 'Weak/Vulnerable': 1, 'Energy': 2, 'Info': 'Deal Σ13 damage. Apply 1 Weak. Apply 1 Vulnerable.', + 'Effects+': {'Weak/Vulnerable': 2, 'Info': 'Deal Σ13 damage. Apply 2 Weak. Apply 2 Vulnerable.'}, 'Function': use_uppercut}, + + 'Whirlwind': {'Name': 'Whirlwind', 'Class': 'Ironclad', 'Rarity': 'Uncommon', 'Type': 'Attack', 'Target': 'Area', 'Damage': 5, 'Energy': -1, 'Info': 'Deal Σ5 damage X times.', 'Effects+': {'Damage': 8, 'Info': 'Deal Σ8 damage X times.'}, 'Function': use_whirlwind}, + + # Rare Cards + 'Barricade': {'Name': 'Barricade', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Energy': 3, 'Info': 'Block is not removed at the start of your turn.', 'Effects+': {'Energy': 2}, 'Function': use_barricade}, + + 'Berzerk': {'Name': 'Berzerk', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Self Vulnerable': 2, 'Energy': 0, 'Info': 'Gain 2 Vulnerable. At the start of your turn, gain 1 Energy.', + 'Effects+': {'Self Vulnerable': 1, 'Info': 'Gain 1 Vulnerable. At the start of your turn, gain 1 Energy.'}, 'Function': use_berzerk}, + + 'Bludgeon': {'Name': 'Bludgeon', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Attack', 'Target': 'Single', 'Damage': 32, 'Energy': 3, 'Info': 'Deal Σ32 damage.', 'Effects+': {'Damage': 42, 'Info': 'Deal Σ42 damage.'}, 'Function': use_bludgeon}, + + 'Brutality': {'Name': 'Brutality', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Energy': 0, 'Info': 'At the start of your turn, lose 1 HP and draw 1 card.', 'Effects+': {'Innate': True, 'Info': 'Innate. At the start of your turn, lose 1 HP and draw 1 card'}, 'Function': use_brutality}, + + 'Corruption': {'Name': 'Corruption', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Energy': 3, 'Info': 'Skills cost 0. Whenever you play a Skill, Exhaust it.', 'Effects+': {'Energy': 2}, 'Function': use_corruption}, + + 'Demon Form': {'Name': 'Demon Form', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Demon Form': 2, 'Energy': 3, 'Info': 'At the start of your turn, gain 2 Strength.', 'Effects+': {'Demon Form': 3, 'Info': 'At the start of your turn, gain 3 Strength.'}, 'Function': use_demonform}, + + 'Double Tap': {'Name': 'Double Tap', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Skill', 'Target': 'Yourself', 'Charges': 1, 'Energy': 1, 'Info': 'This turn, your next Attack is played twice.', 'Effects+': {'Charges': 2, 'Info': 'This turn, your next 2 Attacks are played twice'}, 'Function': use_doubletap}, + + 'Exhume': {'Name': 'Exhume', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Skill', 'Target': 'Yourself', 'Energy': 1, 'Info': 'Put a card from your exhaust pile into your hand. Exhaust.', 'Effects+': {'Energy': 0}, 'Function': use_exhume}, + + 'Feed': {'Name': 'Feed', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Attack', 'Target': 'Single', 'Exhaust': True, 'Damage': 10, 'Max HP': 3, 'Energy': 1, 'Info': 'Deal Σ10 damage. If Fatal, raise your Max HP by 3. Exhaust.', + 'Effects+': {'Damage': 12, 'Max HP': 4, 'Info': 'Deal Σ12 damage. If Fatal, raise your Max HP by 4. Exhaust.'}, 'Function': use_feed}, + + 'Fiend Fire': {'Name': 'Fiend Fire', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Attack', 'Target': 'Single', 'Energy': 2, 'Exhaust': True, 'Damage': 7, 'Info': 'Exhaust all cards in your hand. Deal Σ7 damage for each Exhausted. Exhaust.', + 'Effects+': {'Damage': 10, 'Info': 'Exhaust all cards in your hand. Deal Σ10 damage for each card Exhausted. Exhaust.'}, 'Function': use_fiendfire}, + + 'Immolate': {'Name': 'Immolate', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Attack', 'Target': 'Area', 'Damage': 21, 'Energy': 2, 'Info': 'Deal Σ21 damage to ALL enemies. Add a Burn to your discard pile', + 'Effects+': {'Damage': 28, 'Info': 'Deal Σ28 damage to ALL enemies. Add a Burn to your discard pile.'}, 'Function': use_immolate}, + + 'Impervious': {'Name': 'Impervious', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Skill', 'Target': 'Yourself', 'Block': 30, 'Energy': 2, 'Info': 'Gain Ω30 Block.', 'Effects+': {'Block': 40, 'Info': 'Gain Ω40 Block.'}, 'Function': use_impervious}, + + 'Juggernaut': {'Name': 'Juggernaut', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Power', 'Target': 'Yourself', 'Dmg On Block': 5, 'Energy': 2, 'Info': 'Whenever you gain Block, deal 5 damage to a random enemy.', + 'Effects+': {'Dmg On Block': 7, 'Info': 'Whenever you gain Block, deal 7 damage to a random enemy.'}, 'Function': use_juggernaut}, + + 'Limit Break': {'Name': 'Limit Break', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Skill', 'Target': 'Yourself', 'Exhaust': True, 'Energy': 1, 'Info': 'Double your Strength. Exhaust.', 'Effects+': {'Exhaust': False}, 'Function': use_limitbreak}, + + 'Offering': {'Name': 'Offering', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Skill', 'Target': 'Yourself', 'Exhaust': True, 'Cards': 3, 'Energy': 0, 'Info': 'Lose 6 HP. Gain 2 Energy. Draw 3 cards. Exhaust.', + 'Effects+': {'Cards': 5, 'Info': 'Lose 6 HP. Gain 2 Exhaust. Draw 5 cards. Exhaust.'}, 'Function': use_offering}, + + 'Reaper': {'Name': 'Reaper', 'Class': 'Ironclad', 'Rarity': 'Rare', 'Type': 'Attack', 'Target': 'Area', 'Exhaust': True, 'Damage': 4, 'Energy': 2, 'Info': 'Deal 4 damage to ALL enemies. Heal HP equal to unblocked damage. Exhaust.', + 'Effects+': {'Damage': 5, 'Info': 'Deal 5 damage to ALL enemies. Heal HP equal to unblocked damage. Exhaust.'}, 'Function': use_reaper}, + # Status cards + 'Slimed': {'Name': 'Slimed', 'Energy': 1, 'Target': 'Nothing', 'Rarity': 'Common', 'Type': 'Status', 'Info': 'Exhaust'}, + 'Burn': {'Name': 'Burn', 'Playable': False, 'Damage': 2, 'Rarity': 'Common', 'Type': 'Status', 'Info': 'Unplayable. At the end of your turn, take 2 damage.', 'Effects+': {'Damage': 4, 'Info': 'Unplayable. At the end of your turn, take 4 damage.'}}, + 'Dazed': {'Name': 'Dazed', 'Playable': False, 'Ethereal': True, 'Rarity': 'Common', 'Type': 'Status', 'Info': 'Unplayable. Ethereal.'}, + 'Wound': {'Name': 'Wound', 'Playable': False, 'Rarity': 'Common', 'Type': 'Status', 'Info': 'Unplayable.'}, + 'Void': {'Name': 'Void', 'Playable': False, 'Ethereal': True, 'Energy Loss': 1, 'Rarity': 'Common', 'Type': 'Status', 'Info': 'Unplayable. Ethereal. When this card is drawn, lose 1 Energy.'}, + + # Curses + 'Regret': {'Name': 'Regret', 'Playable': False, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. At the end of your turn, lose 1 HP for each card in your hand.'}, + "Ascender's Bane": {'Name': "Ascender's Bane", 'Playable': False, 'Ethereal': True, 'Removable': False, 'Rarity': 'Special', 'Type': 'Curse', 'Info': 'Unplayable. Ethereal. Cannot be removed from your deck'}, + 'Clumsy': {'Name': 'Clumsy', 'Playable': False, 'Ethereal': True, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. Ethereal.'}, + 'Curse of the Bell': {'Name': 'Curse of the Bell', 'Playable': False, 'Removable': False, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. Cannot be removed from your deck.'}, + 'Decay': {'Name': 'Decay', 'Playable': False, 'Damage': 2, 'Type': 'Curse', 'Rarity': 'Curse', 'Info': 'Unplayable. At the end of your turn, take 2 damage.'}, + 'Doubt': {'Name': 'Doubt', 'Playable': False, 'Weak': 1, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. At the end of your turn, gain 1 Weak.'}, + 'Injury': {'Name': 'Injury', 'Playable': False, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable.'}, + 'Necronomicurse': {'Name': 'Necronomicurse', 'Playable': False, 'Exhaustable': False, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. There is no escape from this Curse.'}, + 'Normality': {'Name': 'Normality', 'Playable': False, 'Cards Limit': 3, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. You cannot play more than 3 cards this turn.'}, + 'Pain': {'Name': 'Pain', 'Playable': False, 'Damage': 1, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. While in hand, lose 1 HP when other cards are played.'}, + 'Parasite': {'Name': 'Parasite', 'Playable': False, 'Max Hp Loss': 3, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. If transformed or removed from your deck, lose 3 Max HP.'}, + 'Pride': {'Name': 'Pride', 'Innate': True, 'Exhaust': True, 'Energy': 1, 'Rarity': 'Special', 'Type': 'Curse', 'Info': 'Innate. At the end of your turn, put a copy of this card on top of your draw pile. Exhaust.'}, + 'Shame': {'Name': 'Shame', 'Playable': False, 'Frail': 1, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. At the end of your turn, gain 1 Frail.'}, + 'Writhe': {'Name': 'Writhe', 'Playable': False, 'Innate': True, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. Innate.'}, + + # Colorless Cards + "Bandage Up": {"Name": "Bandage Up", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Heal 4(6) HP. Exhaust.", "Exhaust": True}, + "Blind": {"Name": "Blind", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Icon Weak Weak (to ALL enemies)."}, + "Dark Shackles": {"Name": "Dark Shackles", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Enemy loses 9(15) Icon Strength Strength for the rest of this turn. Exhaust.", "Exhaust": True}, + "Deep Breath": {"Name": "Deep Breath", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Shuffle your discard pile into your draw pile. Draw 1(2) card(s)."}, + "Discovery": {"Name": "Discovery", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Info": "Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)", "Exhaust": True}, + "Dramatic Entrance": {"Name": "Dramatic Entrance", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Innate. Deal 8(12) damage to ALL enemies. Exhaust.", "Exhaust": True}, + "Enlightenment": {"Name": "Enlightenment", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Reduce the cost of cards in your hand to 1 this turn(combat)."}, + "Finesse": {"Name": "Finesse", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 2(4) Icon Block Block. Draw 1 card."}, + "Flash of Steel": {"Name": "Flash of Steel", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 3(6) damage. Draw 1 card."}, + "Forethought": {"Name": "Forethought", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Place a card(any number of cards) from your hand on the bottom of your draw pile. It (They) costs 0 until played."}, + "Good Instincts": {"Name": "Good Instincts", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 6(9) Icon Block Block."}, + "Impatience": {"Name": "Impatience", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "If you have no Attack cards in your hand, draw 2(3) cards."}, + "Jack Of All Trades": {"Name": "Jack Of All Trades", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Add 1(2) random Colorless card(s) to your hand. Exhaust.", "Exhaust": True}, + "Madness": {"Name": "Madness", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Energy+": 0, "Info": "A random card in your hand costs 0 for the rest of combat. Exhaust.", "Exhaust": True}, + "Mind Blast": {"Name": "Mind Blast", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 2, "Energy+": 1, "Info": "Innate. Deal damage equal to the number of cards in your draw pile."}, + "Panacea": {"Name": "Panacea", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 1(2) Icon Artifact Artifact. Exhaust.", "Exhaust": True}, + "Panic Button": {"Name": "Panic Button", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 30(40) Icon Block Block. You cannot gain Icon Block Block from cards for the next 2 turns. Exhaust.", "Exhaust": True}, + "Purity": {"Name": "Purity", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose and Exhaust 3(5) cards in your hand. Exhaust.", "Exhaust": True}, + "Swift Strike": {"Name": "Swift Strike", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 7(10) damage."}, + "Trip": {"Name": "Trip", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Icon Vulnerable Vulnerable (to ALL enemies)."}, + "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True}, + "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, + "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold."}, + "Magnetism": {"Name": "Magnetism", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of each turn, add a random colorless card to your hand."}, + "Master Of Strategy": {"Name": "Master Of Strategy", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 3(4) cards. Exhaust.", "Exhaust": True}, + "Mayhem": {"Name": "Mayhem", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of your turn, play the top card of your draw pile."}, + "Metamorphosis": {"Name": "Metamorphosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Attacks into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, + "Panache": {"Name": "Panache", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Every time you play 5 cards in a single turn, deal 10(14) damage to ALL enemies."}, + "Sadistic Nature": {"Name": "Sadistic Nature", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Whenever you apply a Debuff to an enemy, they take 5(7) damage."}, + "Secret Technique": {"Name": "Secret Technique", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose a Skill from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, + "Secret Weapon": {"Name": "Secret Weapon", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose an Attack from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, + "The Bomb": {"Name": "The Bomb", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "At the end of 3 turns, deal 40(50) damage to ALL enemies."}, + "Thinking Ahead": {"Name": "Thinking Ahead", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 2 cards. Place a card from your hand on top of your draw pile. Exhaust. (Don't Exhaust.)", "Exhaust": True}, + "Transmutation": {"Name": "Transmutation", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": "X", "Info": "Add X random (Upgraded) colorless cards into your hand. They cost 0 this turn. Exhaust.", "Exhaust": True}, + "Violence": {"Name": "Violence", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Place 3(4) random Attack cards from your draw pile into your hand. Exhaust.", "Exhaust": True}, + "Apparition": {"Name": "Apparition", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Gain 1 Icon Intangible Intangible. Exhaust. Ethereal. (no longer Ethereal.) (Obtained from event: Council of Ghosts).", "Exhaust": True}, + "Beta": {"Name": "Beta", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Shuffle an Omega into your draw pile. Exhaust. (Obtained from Alpha).", "Exhaust": True}, + "Bite": {"Name": "Bite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 7(8) damage. Heal 2(3) HP. (Obtained from event: Vampires(?))."}, + "Expunger": {"Name": "Expunger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 9(15) damage X times. (Obtained from Conjure Blade)."}, + "Insight": {"Name": "Insight", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Icon Retain Retain. Draw 2(3) cards. Exhaust. (Obtained from Evaluate, Pray and Study).", "Exhaust": True}, + "J.A.X.": {"Name": "J.A.X.", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Lose 3 HP. Gain 2(3) Icon Strength Strength. (Obtained from event: Augmenter)."}, + "Miracle": {"Name": "Miracle", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Icon Retain Retain. Gain (2) Energy. Exhaust. (Obtained from Collect, Deus Ex Machina, PureWater-0 Pure Water, and Holy water Holy Water).", "Exhaust": True}, + "Omega": {"Name": "Omega", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.POWER, "Energy": 3, "Info": "At the end of your turn deal 50(60) damage to ALL enemies. (Obtained from Beta)."}, + "Ritual Dagger": {"Name": "Ritual Dagger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 15 damage. If this kills an enemy then permanently increase this card's damage by 3(5). Exhaust. (Obtained during event: The Nest)", "Exhaust": True}, + "Safety": {"Name": "Safety", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Icon Retain Retain. Gain 12(16) Icon Block Block. Exhaust. (Obtained from Deceive Reality).", "Exhaust": True}, + "Shiv": {"Name": "Shiv", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 4(6) damage. Exhaust. (Obtained from Blade Dance, Cloak and Dagger, Infinite Blades, Storm of Steel, and NinjaScroll Ninja Scroll).", "Exhaust": True}, + "Smite": {"Name": "Smite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Icon Retain Retain. Deal 12(16) damage. Exhaust. (Obtained from Carve Reality and Battle Hymn).", "Exhaust": True}, + "Through Violence": {"Name": "Through Violence", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Icon Retain Retain. Deal 20(30) damage. Exhaust. (Obtained from Reach Heaven).", "Exhaust": True}, +} # Need to convert this to a method once I do the potions to eliminate this global variable. sacred_multi: int = 1 From be49c83269ca3082bad8fcab366a82aaceeca3ce Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 16:34:51 -0600 Subject: [PATCH 09/18] rudimentary shop implementation --- game.py | 15 ++++-- game_map.py | 2 +- helper.py | 13 +++++- shop.py | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ test_shop.py | 18 ++++++++ 5 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 shop.py create mode 100644 test_shop.py diff --git a/game.py b/game.py index 2eaa9c4d..f15667a8 100644 --- a/game.py +++ b/game.py @@ -1,18 +1,23 @@ -from functools import partial -from time import sleep import math import random +from time import sleep from copy import copy, deepcopy from ansi_tags import ansiprint from events import choose_event from items import relics, potions, cards, activate_sacred_bark -from helper import active_enemies, view, gen, ei -from enemy_catalog import create_act1_normal_encounters, create_act1_elites, create_act1_boss from entities import player, Player, Enemy from definitions import CombatTier, EncounterType from message_bus_tools import Message, bus from dataclasses import dataclass import game_map +from ansi_tags import ansiprint +from enemy_catalog import ( + create_act1_boss, + create_act1_elites, + create_act1_normal_encounters, +) +from helper import active_enemies, combat_turn, ei, gen, potion_dropchance, view +from shop import Shop @dataclass @@ -291,6 +296,8 @@ def play(encounter: EncounterType, gm: game_map.GameMap): return Combat(player, CombatTier.ELITE).combat(gm) elif encounter.type == EncounterType.NORMAL: return Combat(player, CombatTier.NORMAL).combat(gm) + elif encounter.type == EncounterType.SHOP: + return Shop(player).loop() else: raise game_map.MapError(f"Encounter type {encounter} is not valid.") diff --git a/game_map.py b/game_map.py index d12e94da..58bdf306 100644 --- a/game_map.py +++ b/game_map.py @@ -121,7 +121,7 @@ def create_first_map(): start = Encounter(ET.START) F1a = Encounter(ET.NORMAL, parents=[start]) - F1b = Encounter(ET.NORMAL, parents=[start]) + F1b = Encounter(ET.SHOP, parents=[start]) F1c = Encounter(ET.NORMAL, parents=[start]) F1d = Encounter(ET.NORMAL, parents=[start]) diff --git a/helper.py b/helper.py index 1aa454bc..c40c4f57 100644 --- a/helper.py +++ b/helper.py @@ -91,13 +91,22 @@ def display_ui(self, entity, enemies, combat=True): ansiprint(str(entity)) print() - def list_input(self, input_string: str, choices: list, displayer: Callable, validator: Callable=lambda placehold: bool(placehold), message_when_invalid: str=None) -> int | None: + def list_input(self, input_string: str, choices: list, + displayer: Callable, + validator: Callable=lambda placehold: bool(placehold), + message_when_invalid: str=None, + extra_allowables=None) -> int | None: '''Allows the player to choose from a certain list of options. Includes validation.''' + if extra_allowables is None: + extra_allowables = [] while True: try: displayer(choices, validator=validator) ansiprint(input_string + " > ", end='') - option = int(input()) - 1 + response = input() + if response in extra_allowables: + return response + option = int(response) - 1 if not validator(choices[option]): ansiprint(f"\u001b[1A\u001b[1000D{message_when_invalid}", end='') sleep(1.5) diff --git a/shop.py b/shop.py new file mode 100644 index 00000000..06afcee9 --- /dev/null +++ b/shop.py @@ -0,0 +1,127 @@ + +# The shop contains: +# 5 Colored cards: +# - 2 Attack cards, 2 skill cards, and 1 Power card +# - Common: 45-55 Gold +# - Uncommon: 68-82 Gold +# - Rare: 135-165 Gold +# - One card is always on sale by 50% +# 2 Colorless Cards: +# - uncommmon on the left (81-99) +# - Rare on the right (162-198) +# Relics: +# Shop Relic on the right: +# Common: 143 - 157 Gold +# Uncommon: 238 - 262 Gold +# Rare: 285 - 315 Gold +# Shop: 143 - 157 Gold +# Potions: +# Common: 48 - 52 Gold +# Uncommon: 72 - 78 Gold +# Rare: 95 - 105 Gold +# Card Removal Service +# Can only be used once per shop. +# Costs 75 Gold initially, increases cost by 25 Gold every time it is used at any shop. +# Woo.... lots of stuff to do here. + +import random +import time + +from ansi_tags import ansiprint +from definitions import Rarity +from helper import Displayer +from items import cards, potions, relics + + +class SellableItem(): + def __init__(self, item): + self.item = item + self.price = self.set_price() + + def invalid_string(self): + card = self.item + return f"{self.price:3d} Gold : {card['Name']} | {card['Type']} | {card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') + + def valid_string(self): + card = self.item + changed_energy = 'light-red' if not card.get('Changed Energy') else 'green' + return f"{self.price:3d} Gold : <{card['Rarity'].lower()}>{card['Name']} | <{card['Type'].lower()}>{card['Type']} | <{changed_energy}>{card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') is not None else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') + + def __str__(self): + return self.valid_string() + + def set_price(self): + if self.item["Rarity"] in ("Basic", "Common"): + return random.randint(45, 55) + elif self.item["Rarity"] == "Uncommon": + return random.randint(68, 82) + elif self.item["Rarity"] == "Rare": + return random.randint(135, 165) + else: + raise ValueError(f"Item {self.item} has no rarity") + +class Shop(): + def __init__(self, player, items=None): + self.player = player + if items is None: + self.items = self.initialize_items() + else: + self.items = items + + def initialize_items(self) -> list[SellableItem]: + # TODO: Make this class-specific and include relics and potions + items = [] + all_cards = list(cards.values()) + attack_cards = [c for c in all_cards if c["Type"] == "Attack" and c["Class"] != "Colorless"] + skill_cards = [c for c in all_cards if c["Type"] == "Skill" and c["Class"] != "Colorless"] + power_cards = [c for c in all_cards if c["Type"] == "Power" and c["Class"] != "Colorless"] + items.extend(random.sample(attack_cards, 2)) + items.extend(random.sample(skill_cards, 2)) + items.extend(random.sample(power_cards, 1)) + colorless_cards = [c for c in all_cards if "Class" in c and c["Class"] == "Colorless"] + colorless_uncommon = [c for c in colorless_cards if c["Rarity"] == Rarity.UNCOMMON] + colorless_rare = [c for c in colorless_cards if c["Rarity"] == Rarity.RARE] + items.extend(random.sample(colorless_uncommon, 1)) + items.extend(random.sample(colorless_rare, 1)) + return [SellableItem(item) for item in items] + + def loop(self): + while True: + ansiprint(f"Welcome to the shop! You have {self.player.gold} gold.") + choice = Displayer().list_input( + input_string="Buy something?", + choices=self.items, + displayer=self.view_sellables, + validator=self.validator, + extra_allowables=['e']) + if choice == 'e': + break + self.buy(choice) + + def buy(self, choice): + self.player.gold -= self.items[choice].price + self.player.deck.append(self.items[choice].item) + ansiprint(f"You bought {self.items[choice].item['Name']} for {self.items[choice].price} gold.") + time.sleep(0.5) + input("Press Enter to continue...") + + def validator(self, item): + return item.price <= self.player.gold + + def view_sellables(self, items, validator): + for idx, item in enumerate(items): + if validator(item): + ansiprint(f"{idx+1}: {item.valid_string()}") + else: + ansiprint(f"{idx+1}: {item.invalid_string()}") + ansiprint("e: Exit Shop") + + def __str__(self): + return f"{self.name} has {self.stock} items in stock" + + def __add__(self, other): + new_stock = self.stock + other.stock + return Shop(f"{self.name} & {other.name}", new_stock) + + def __len__(self): + return self.stock \ No newline at end of file diff --git a/test_shop.py b/test_shop.py new file mode 100644 index 00000000..b7706132 --- /dev/null +++ b/test_shop.py @@ -0,0 +1,18 @@ +# test the shop +import pytest + +import entities +import items +from shop import SellableItem, Shop + + +@pytest.mark.only +def test_shop(monkeypatch): + player = entities.create_player() + cards = [SellableItem(items.cards[x]) for x in ("Strike","Body Slam","Heavy Blade","Warcry")] + shop = Shop(player, cards) + responses = iter(['1', '\n', 'e']) + with monkeypatch.context() as m: + m.setattr('builtins.input', lambda *a, **kw: next(responses)) + + shop.loop() \ No newline at end of file From e1589619578379f029adcb0d8017d3c39abc9f49 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 16:35:10 -0600 Subject: [PATCH 10/18] I don't care about long lines --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 104b5a81..f98aa8e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ exclude = [".cache"] [tool.ruff] # https://beta.ruff.rs/docs/configuration/ select = ['E', 'W', 'F', 'I', 'B', 'C4', 'ARG', 'SIM'] -ignore = ['W291', 'W292', 'W293'] +ignore = ['W291', 'W292', 'W293', 'E501'] [build-system] requires = ["poetry-core>=1.0.0"] From a354c38817d73699cdbcf9872e9879e9cf86c309 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 17:11:39 -0600 Subject: [PATCH 11/18] ok so it's going to take longer to add all these cards... --- definitions.py | 12 ++++++ items.py | 110 +++++++++++++++++++++++++++---------------------- test_shop.py | 1 - test_view.py | 4 +- 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/definitions.py b/definitions.py index 24ad05a8..ae3d49df 100644 --- a/definitions.py +++ b/definitions.py @@ -41,3 +41,15 @@ class PlayerClass(StrEnum): WATCHER = 'Watcher' COLORLESS = 'Colorless' ANY = 'Any' + ENTROPIC = 'Entropic' + SNECKO = 'Snecko' + DARK_ESSENCE = 'Dark Essence' + +class TargetType(StrEnum): + ANY = 'Any' + AREA = 'Area' + ENEMY = 'Enemy' + NOTHING = 'Nothing' + RANDOM = 'Random' + SINGLE = 'Single' + YOURSELF = 'Yourself' diff --git a/items.py b/items.py index 196a13b2..600514d9 100644 --- a/items.py +++ b/items.py @@ -4,7 +4,7 @@ from ansi_tags import ansiprint from helper import view, ei from message_bus_tools import Card, Message -from definitions import CardType, Rarity, PlayerClass +from definitions import CardType, Rarity, PlayerClass, TargetType class IroncladStrike(Card): @@ -242,6 +242,10 @@ def use_disarm(targeted_enemy, using_card, entity): '''Enemy loses 2(3) Strength. Exhaust.''' ei.apply_effect(targeted_enemy, entity, 'Strength', -using_card['Strength Loss']) +def use_darkshackles(targeted_enemy, using_card, entity): + '''Enemy loses 9(15) Strength for the rest of this turn.''' + ei.apply_effect(targeted_enemy, entity, 'Strength', -using_card['Magic Number']) + def use_dropkick(targeted_enemy, using_card, entity): '''Deal 5(8) damage. If the enemy has Vulnerable, gain 1 Energy and draw 1 card.''' entity.attack(using_card['Damage'], targeted_enemy, using_card) @@ -301,6 +305,10 @@ def use_infernalblade(using_card, entity): valid_cards = [card for card in cards.values() if card.get('Type') == 'Attack' and card.get('Class') == entity.player_class] entity.hand.append(random.choice(valid_cards).modify_energy_cost(0, 'Set')) +def use_discovery(using_card, entity): + '''Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)''' + raise NotImplementedError("Need to have a user interaction here") + def use_inflame(using_card, entity): '''Gain 2(3) Strength''' ei.apply_effect(entity, entity, 'Strength', using_card['Strength']) @@ -449,6 +457,10 @@ def use_exhume(using_card, entity): del entity.exhaust_pile[option] break +def use_bandageup(using_card, entity): + '''Heal 4(6) HP. Exhaust.''' + entity.health_actions(4, 'Heal') + def use_feed(targeted_enemy, using_card, entity): '''Deal 10(12) damage. If Fatal, raise your Max HP by 3(4). Exhaust.''' entity.attack(using_card['Damage'], targeted_enemy, using_card) @@ -903,54 +915,54 @@ def use_reaper(enemies, using_card, entity): 'Writhe': {'Name': 'Writhe', 'Playable': False, 'Innate': True, 'Rarity': 'Curse', 'Type': 'Curse', 'Info': 'Unplayable. Innate.'}, # Colorless Cards - "Bandage Up": {"Name": "Bandage Up", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Heal 4(6) HP. Exhaust.", "Exhaust": True}, - "Blind": {"Name": "Blind", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Icon Weak Weak (to ALL enemies)."}, - "Dark Shackles": {"Name": "Dark Shackles", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Enemy loses 9(15) Icon Strength Strength for the rest of this turn. Exhaust.", "Exhaust": True}, - "Deep Breath": {"Name": "Deep Breath", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Shuffle your discard pile into your draw pile. Draw 1(2) card(s)."}, - "Discovery": {"Name": "Discovery", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Info": "Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)", "Exhaust": True}, - "Dramatic Entrance": {"Name": "Dramatic Entrance", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Innate. Deal 8(12) damage to ALL enemies. Exhaust.", "Exhaust": True}, - "Enlightenment": {"Name": "Enlightenment", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Reduce the cost of cards in your hand to 1 this turn(combat)."}, - "Finesse": {"Name": "Finesse", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 2(4) Icon Block Block. Draw 1 card."}, - "Flash of Steel": {"Name": "Flash of Steel", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 3(6) damage. Draw 1 card."}, - "Forethought": {"Name": "Forethought", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Place a card(any number of cards) from your hand on the bottom of your draw pile. It (They) costs 0 until played."}, - "Good Instincts": {"Name": "Good Instincts", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 6(9) Icon Block Block."}, - "Impatience": {"Name": "Impatience", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "If you have no Attack cards in your hand, draw 2(3) cards."}, - "Jack Of All Trades": {"Name": "Jack Of All Trades", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Add 1(2) random Colorless card(s) to your hand. Exhaust.", "Exhaust": True}, - "Madness": {"Name": "Madness", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Energy+": 0, "Info": "A random card in your hand costs 0 for the rest of combat. Exhaust.", "Exhaust": True}, - "Mind Blast": {"Name": "Mind Blast", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 2, "Energy+": 1, "Info": "Innate. Deal damage equal to the number of cards in your draw pile."}, - "Panacea": {"Name": "Panacea", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 1(2) Icon Artifact Artifact. Exhaust.", "Exhaust": True}, - "Panic Button": {"Name": "Panic Button", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 30(40) Icon Block Block. You cannot gain Icon Block Block from cards for the next 2 turns. Exhaust.", "Exhaust": True}, - "Purity": {"Name": "Purity", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose and Exhaust 3(5) cards in your hand. Exhaust.", "Exhaust": True}, - "Swift Strike": {"Name": "Swift Strike", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 7(10) damage."}, - "Trip": {"Name": "Trip", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Icon Vulnerable Vulnerable (to ALL enemies)."}, - "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True}, - "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, - "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold."}, - "Magnetism": {"Name": "Magnetism", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of each turn, add a random colorless card to your hand."}, - "Master Of Strategy": {"Name": "Master Of Strategy", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 3(4) cards. Exhaust.", "Exhaust": True}, - "Mayhem": {"Name": "Mayhem", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of your turn, play the top card of your draw pile."}, - "Metamorphosis": {"Name": "Metamorphosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Attacks into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, - "Panache": {"Name": "Panache", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Every time you play 5 cards in a single turn, deal 10(14) damage to ALL enemies."}, - "Sadistic Nature": {"Name": "Sadistic Nature", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Whenever you apply a Debuff to an enemy, they take 5(7) damage."}, - "Secret Technique": {"Name": "Secret Technique", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose a Skill from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, - "Secret Weapon": {"Name": "Secret Weapon", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose an Attack from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, - "The Bomb": {"Name": "The Bomb", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "At the end of 3 turns, deal 40(50) damage to ALL enemies."}, - "Thinking Ahead": {"Name": "Thinking Ahead", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 2 cards. Place a card from your hand on top of your draw pile. Exhaust. (Don't Exhaust.)", "Exhaust": True}, - "Transmutation": {"Name": "Transmutation", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": "X", "Info": "Add X random (Upgraded) colorless cards into your hand. They cost 0 this turn. Exhaust.", "Exhaust": True}, - "Violence": {"Name": "Violence", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Place 3(4) random Attack cards from your draw pile into your hand. Exhaust.", "Exhaust": True}, - "Apparition": {"Name": "Apparition", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Gain 1 Icon Intangible Intangible. Exhaust. Ethereal. (no longer Ethereal.) (Obtained from event: Council of Ghosts).", "Exhaust": True}, - "Beta": {"Name": "Beta", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Shuffle an Omega into your draw pile. Exhaust. (Obtained from Alpha).", "Exhaust": True}, - "Bite": {"Name": "Bite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 7(8) damage. Heal 2(3) HP. (Obtained from event: Vampires(?))."}, - "Expunger": {"Name": "Expunger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 9(15) damage X times. (Obtained from Conjure Blade)."}, - "Insight": {"Name": "Insight", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Icon Retain Retain. Draw 2(3) cards. Exhaust. (Obtained from Evaluate, Pray and Study).", "Exhaust": True}, - "J.A.X.": {"Name": "J.A.X.", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Lose 3 HP. Gain 2(3) Icon Strength Strength. (Obtained from event: Augmenter)."}, - "Miracle": {"Name": "Miracle", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Icon Retain Retain. Gain (2) Energy. Exhaust. (Obtained from Collect, Deus Ex Machina, PureWater-0 Pure Water, and Holy water Holy Water).", "Exhaust": True}, - "Omega": {"Name": "Omega", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.POWER, "Energy": 3, "Info": "At the end of your turn deal 50(60) damage to ALL enemies. (Obtained from Beta)."}, - "Ritual Dagger": {"Name": "Ritual Dagger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 15 damage. If this kills an enemy then permanently increase this card's damage by 3(5). Exhaust. (Obtained during event: The Nest)", "Exhaust": True}, - "Safety": {"Name": "Safety", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Icon Retain Retain. Gain 12(16) Icon Block Block. Exhaust. (Obtained from Deceive Reality).", "Exhaust": True}, - "Shiv": {"Name": "Shiv", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 4(6) damage. Exhaust. (Obtained from Blade Dance, Cloak and Dagger, Infinite Blades, Storm of Steel, and NinjaScroll Ninja Scroll).", "Exhaust": True}, - "Smite": {"Name": "Smite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Icon Retain Retain. Deal 12(16) damage. Exhaust. (Obtained from Carve Reality and Battle Hymn).", "Exhaust": True}, - "Through Violence": {"Name": "Through Violence", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Icon Retain Retain. Deal 20(30) damage. Exhaust. (Obtained from Reach Heaven).", "Exhaust": True}, + "Bandage Up": {"Name": "Bandage Up", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Heal 4(6) HP. Exhaust.", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_bandageup}, + # "Blind": {"Name": "Blind", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Weak (to ALL enemies)."}, + "Dark Shackles": {"Name": "Dark Shackles", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Enemy loses 9(15) Strength for the rest of this turn. Exhaust.", "Exhaust": True, "Target": TargetType.ENEMY, "Function": use_darkshackles, "Magic Number": 9}, + # "Deep Breath": {"Name": "Deep Breath", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Shuffle your discard pile into your draw pile. Draw 1(2) card(s)."}, + # "Discovery": {"Name": "Discovery", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Info": "Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_discovery}, + # "Dramatic Entrance": {"Name": "Dramatic Entrance", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Innate. Deal 8(12) damage to ALL enemies. Exhaust.", "Exhaust": True}, + # "Enlightenment": {"Name": "Enlightenment", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Reduce the cost of cards in your hand to 1 this turn(combat)."}, + # "Finesse": {"Name": "Finesse", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 2(4) Block. Draw 1 card."}, + # "Flash of Steel": {"Name": "Flash of Steel", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 3(6) damage. Draw 1 card."}, + # "Forethought": {"Name": "Forethought", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Place a card(any number of cards) from your hand on the bottom of your draw pile. It (They) costs 0 until played."}, + # "Good Instincts": {"Name": "Good Instincts", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 6(9) Block."}, + # "Impatience": {"Name": "Impatience", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "If you have no Attack cards in your hand, draw 2(3) cards."}, + # "Jack Of All Trades": {"Name": "Jack Of All Trades", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Add 1(2) random Colorless card(s) to your hand. Exhaust.", "Exhaust": True}, + # "Madness": {"Name": "Madness", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Energy+": 0, "Info": "A random card in your hand costs 0 for the rest of combat. Exhaust.", "Exhaust": True}, + # "Mind Blast": {"Name": "Mind Blast", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 2, "Energy+": 1, "Info": "Innate. Deal damage equal to the number of cards in your draw pile."}, + # "Panacea": {"Name": "Panacea", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 1(2) Artifact. Exhaust.", "Exhaust": True}, + # "Panic Button": {"Name": "Panic Button", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 30(40) Block. You cannot gain Block from cards for the next 2 turns. Exhaust.", "Exhaust": True}, + # "Purity": {"Name": "Purity", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose and Exhaust 3(5) cards in your hand. Exhaust.", "Exhaust": True}, + # "Swift Strike": {"Name": "Swift Strike", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 7(10) damage."}, + # "Trip": {"Name": "Trip", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Vulnerable (to ALL enemies)."}, + # "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True}, + # "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, + # "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold."}, + # "Magnetism": {"Name": "Magnetism", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of each turn, add a random colorless card to your hand."}, + # "Master Of Strategy": {"Name": "Master Of Strategy", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 3(4) cards. Exhaust.", "Exhaust": True}, + # "Mayhem": {"Name": "Mayhem", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of your turn, play the top card of your draw pile."}, + # "Metamorphosis": {"Name": "Metamorphosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Attacks into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, + # "Panache": {"Name": "Panache", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Every time you play 5 cards in a single turn, deal 10(14) damage to ALL enemies."}, + # "Sadistic Nature": {"Name": "Sadistic Nature", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Whenever you apply a Debuff to an enemy, they take 5(7) damage."}, + # "Secret Technique": {"Name": "Secret Technique", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose a Skill from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, + # "Secret Weapon": {"Name": "Secret Weapon", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose an Attack from your draw pile and place it into your hand. Exhaust. (Don't Exhaust)", "Exhaust": True}, + # "The Bomb": {"Name": "The Bomb", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "At the end of 3 turns, deal 40(50) damage to ALL enemies."}, + # "Thinking Ahead": {"Name": "Thinking Ahead", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 2 cards. Place a card from your hand on top of your draw pile. Exhaust. (Don't Exhaust.)", "Exhaust": True}, + # "Transmutation": {"Name": "Transmutation", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": -1, "Info": "Add X random (Upgraded) colorless cards into your hand. They cost 0 this turn. Exhaust.", "Exhaust": True}, + # "Violence": {"Name": "Violence", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Place 3(4) random Attack cards from your draw pile into your hand. Exhaust.", "Exhaust": True}, + # "Apparition": {"Name": "Apparition", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Gain 1 Intangible. Exhaust. Ethereal. (no longer Ethereal.) (Obtained from event: Council of Ghosts).", "Exhaust": True}, + # "Beta": {"Name": "Beta", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Shuffle an Omega into your draw pile. Exhaust. (Obtained from Alpha).", "Exhaust": True}, + # "Bite": {"Name": "Bite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 7(8) damage. Heal 2(3) HP. (Obtained from event: Vampires(?))."}, + # "Expunger": {"Name": "Expunger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 9(15) damage X times. (Obtained from Conjure Blade)."}, + # "Insight": {"Name": "Insight", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Retain. Draw 2(3) cards. Exhaust. (Obtained from Evaluate, Pray and Study).", "Exhaust": True}, + # "J.A.X.": {"Name": "J.A.X.", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Lose 3 HP. Gain 2(3) Strength. (Obtained from event: Augmenter)."}, + # "Miracle": {"Name": "Miracle", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 0, "Info": "Retain. Gain (2) Energy. Exhaust. (Obtained from Collect, Deus Ex Machina, PureWater-0 Pure Water, and Holy water Holy Water).", "Exhaust": True}, + # "Omega": {"Name": "Omega", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.POWER, "Energy": 3, "Info": "At the end of your turn deal 50(60) damage to ALL enemies. (Obtained from Beta)."}, + # "Ritual Dagger": {"Name": "Ritual Dagger", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Deal 15 damage. If this kills an enemy then permanently increase this card's damage by 3(5). Exhaust. (Obtained during event: The Nest)", "Exhaust": True}, + # "Safety": {"Name": "Safety", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.SKILL, "Energy": 1, "Info": "Retain. Gain 12(16) Block. Exhaust. (Obtained from Deceive Reality).", "Exhaust": True}, + # "Shiv": {"Name": "Shiv", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 4(6) damage. Exhaust. (Obtained from Blade Dance, Cloak and Dagger, Infinite Blades, Storm of Steel, and NinjaScroll Ninja Scroll).", "Exhaust": True}, + # "Smite": {"Name": "Smite", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 1, "Info": "Retain. Deal 12(16) damage. Exhaust. (Obtained from Carve Reality and Battle Hymn).", "Exhaust": True}, + "Through Violence": {"Name": "Through Violence", "Class": "Colorless", "Rarity": Rarity.SPECIAL, "Type": CardType.ATTACK, "Energy": 0, "Info": "Retain. Deal 20(30) damage. Exhaust. (Obtained from Reach Heaven).", "Exhaust": True, "Target": TargetType.ENEMY}, } # Need to convert this to a method once I do the potions to eliminate this global variable. diff --git a/test_shop.py b/test_shop.py index b7706132..2573a2b5 100644 --- a/test_shop.py +++ b/test_shop.py @@ -6,7 +6,6 @@ from shop import SellableItem, Shop -@pytest.mark.only def test_shop(monkeypatch): player = entities.create_player() cards = [SellableItem(items.cards[x]) for x in ("Strike","Body Slam","Heavy Blade","Warcry")] diff --git a/test_view.py b/test_view.py index 7db488fb..b9473541 100644 --- a/test_view.py +++ b/test_view.py @@ -7,8 +7,8 @@ def test_view_piles_on_all_cards(monkeypatch): entity = entities.create_player() entity.energy = 3 all_conditions = [(lambda card: card.get("Upgraded") is True, "Upgraded"), (lambda card: not card.get("Upgraded") and (card['Type'] not in ("Status", "Curse") or card['Name'] == 'Burn'), "Upgradeable"), - (lambda card: card.get("Removable") is False, "Removable"), (lambda card: card['Type'] == 'Skill', "Skill"), (lambda card: card['Type'] == 'Attack', "Attack"), (lambda card: card['Type'] == 'Power', "Power"), - (lambda card: card.get("Energy", float('inf')) <= entity.energy, "Playable")] + (lambda card: card.get("Removable") is False, "Removable"), (lambda card: card['Type'] == 'Skill', "Skill"), (lambda card: card['Type'] == 'Attack', "Attack"), (lambda card: card['Type'] == 'Power', "Power"), + (lambda card: (card.get("Energy", float('inf')) if card.get("Energy", float('inf')) != -1 else entity.energy) <= entity.energy, "Playable")] with monkeypatch.context() as m: # Patch sleeps so it's faster From 30838a8cc920e394ce248543a81e984038ff92d5 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 23:15:02 +0000 Subject: [PATCH 12/18] protection against not having enough cards --- shop.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/shop.py b/shop.py index 06afcee9..b2d7850a 100644 --- a/shop.py +++ b/shop.py @@ -75,14 +75,20 @@ def initialize_items(self) -> list[SellableItem]: attack_cards = [c for c in all_cards if c["Type"] == "Attack" and c["Class"] != "Colorless"] skill_cards = [c for c in all_cards if c["Type"] == "Skill" and c["Class"] != "Colorless"] power_cards = [c for c in all_cards if c["Type"] == "Power" and c["Class"] != "Colorless"] - items.extend(random.sample(attack_cards, 2)) - items.extend(random.sample(skill_cards, 2)) - items.extend(random.sample(power_cards, 1)) + if len(attack_cards) >= 2: + items.extend(random.sample(attack_cards, 2)) + if len(skill_cards) >= 2: + items.extend(random.sample(skill_cards, 2)) + if len(power_cards) >= 1: + items.extend(random.sample(power_cards, 1)) + colorless_cards = [c for c in all_cards if "Class" in c and c["Class"] == "Colorless"] colorless_uncommon = [c for c in colorless_cards if c["Rarity"] == Rarity.UNCOMMON] colorless_rare = [c for c in colorless_cards if c["Rarity"] == Rarity.RARE] - items.extend(random.sample(colorless_uncommon, 1)) - items.extend(random.sample(colorless_rare, 1)) + if len(colorless_uncommon) >= 1: + items.extend(random.sample(colorless_uncommon, 1)) + if len(colorless_rare) >= 1: + items.extend(random.sample(colorless_rare, 1)) return [SellableItem(item) for item in items] def loop(self): From 9c8143a851788c2b162baffa4406dc7b3ebb65bd Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sat, 3 Feb 2024 23:21:07 +0000 Subject: [PATCH 13/18] cleanup --- shop.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/shop.py b/shop.py index b2d7850a..258d046f 100644 --- a/shop.py +++ b/shop.py @@ -120,14 +120,4 @@ def view_sellables(self, items, validator): ansiprint(f"{idx+1}: {item.valid_string()}") else: ansiprint(f"{idx+1}: {item.invalid_string()}") - ansiprint("e: Exit Shop") - - def __str__(self): - return f"{self.name} has {self.stock} items in stock" - - def __add__(self, other): - new_stock = self.stock + other.stock - return Shop(f"{self.name} & {other.name}", new_stock) - - def __len__(self): - return self.stock \ No newline at end of file + ansiprint("e: Exit Shop") \ No newline at end of file From ff7081d6ce47df24de1602a73ddbb548bbabf209 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Sun, 4 Feb 2024 16:21:25 -0600 Subject: [PATCH 14/18] Add some more cards --- items.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/items.py b/items.py index 600514d9..de353852 100644 --- a/items.py +++ b/items.py @@ -85,6 +85,16 @@ def use_cleave(enemies, using_card, entity): for enemy in enemies: entity.attack(using_card['Damage'], enemy, using_card) +def use_dramaticentrance(enemies, using_card, entity): + '''Deal 8(12) damage to ALL enemies. Exhaust.''' + for enemy in enemies: + entity.attack(using_card['Damage'], enemy, using_card) + +def use_blind(enemies, using_card, entity): + '''Apply 2 Weak (to ALL enemies).''' + for enemy in enemies: + ei.apply_effect(enemy, entity, 'Weak', using_card['Weak']) + def use_perfectedstrike(targeted_enemy, using_card, entity): '''Deal 6 damage. Deals 2(3) additional damage for ALL your cards containing "Strike"''' @@ -98,6 +108,12 @@ def use_anger(targeted_enemy, using_card, entity): entity.attack(using_card['Damage'], targeted_enemy, using_card) entity.discard_pile.append(deepcopy(using_card)) +def use_apotheosis(using_card, entity): + '''Upgrade ALL of your cards for the rest of combat. Exhaust.''' + for card in entity.hand: + card = entity.card_actions(card, 'Upgrade') + sleep(0.3) + def use_armaments(using_card, entity): '''Gain 5 Block. Upgrade a(ALL) card(s) in your hand for the rest of combat.''' entity.blocking(5, False) @@ -350,6 +366,12 @@ def use_recklesscharge(targeted_enemy, using_card, entity): entity.draw_pile.insert(random.randint(0, len(entity.draw_pile) - 1), deepcopy(cards['Dazed'])) ansiprint("A Dazed was shuffled into your draw pile.") +def use_deepbreath(using_card, entity): + '''Shuffle your discard pile into your draw pile. Draw 1(2) cards.''' + entity.draw_pile.extend(entity.discard_pile) + entity.discard_pile = [] + entity.draw_cards(True, using_card['Cards']) + def use_rupture(using_card, entity): '''Whenever you lose HP from a card, gain 1(2) Strength.''' ei.apply_effect(entity, entity, 'Rupture', using_card['Rupture']) @@ -916,11 +938,11 @@ def use_reaper(enemies, using_card, entity): # Colorless Cards "Bandage Up": {"Name": "Bandage Up", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Heal 4(6) HP. Exhaust.", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_bandageup}, - # "Blind": {"Name": "Blind", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Weak (to ALL enemies)."}, + "Blind": {"Name": "Blind", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Weak (to ALL enemies).", "Target": TargetType.AREA, "Function": use_blind}, "Dark Shackles": {"Name": "Dark Shackles", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Enemy loses 9(15) Strength for the rest of this turn. Exhaust.", "Exhaust": True, "Target": TargetType.ENEMY, "Function": use_darkshackles, "Magic Number": 9}, - # "Deep Breath": {"Name": "Deep Breath", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Shuffle your discard pile into your draw pile. Draw 1(2) card(s)."}, + "Deep Breath": {"Name": "Deep Breath", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Shuffle your discard pile into your draw pile. Draw 1(2) card(s).", "Target": TargetType.NOTHING, "Function": use_deepbreath, "Cards": 1}, # "Discovery": {"Name": "Discovery", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 1, "Info": "Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_discovery}, - # "Dramatic Entrance": {"Name": "Dramatic Entrance", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Innate. Deal 8(12) damage to ALL enemies. Exhaust.", "Exhaust": True}, + "Dramatic Entrance": {"Name": "Dramatic Entrance", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Innate. Deal 8(12) damage to ALL enemies. Exhaust.", "Exhaust": True, "Target": TargetType.AREA, "Function": use_dramaticentrance, "Damage": 8}, # "Enlightenment": {"Name": "Enlightenment", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Reduce the cost of cards in your hand to 1 this turn(combat)."}, # "Finesse": {"Name": "Finesse", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Gain 2(4) Block. Draw 1 card."}, # "Flash of Steel": {"Name": "Flash of Steel", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 3(6) damage. Draw 1 card."}, @@ -935,7 +957,8 @@ def use_reaper(enemies, using_card, entity): # "Purity": {"Name": "Purity", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Choose and Exhaust 3(5) cards in your hand. Exhaust.", "Exhaust": True}, # "Swift Strike": {"Name": "Swift Strike", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.ATTACK, "Energy": 0, "Info": "Deal 7(10) damage."}, # "Trip": {"Name": "Trip", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Vulnerable (to ALL enemies)."}, - # "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True}, + + "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_apotheosis}, # "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, # "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold."}, # "Magnetism": {"Name": "Magnetism", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of each turn, add a random colorless card to your hand."}, From 354646dea96cb56f7ed6afe0eca31e087228c1e0 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Mon, 5 Feb 2024 05:46:03 +0000 Subject: [PATCH 15/18] a few more cards --- items.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/items.py b/items.py index de353852..d73098e9 100644 --- a/items.py +++ b/items.py @@ -1,6 +1,7 @@ import random -from time import sleep from copy import deepcopy +from time import sleep + from ansi_tags import ansiprint from helper import view, ei from message_bus_tools import Card, Message @@ -102,7 +103,6 @@ def use_perfectedstrike(targeted_enemy, using_card, entity): print() entity.attack(total_damage, targeted_enemy, using_card) - def use_anger(targeted_enemy, using_card, entity): '''Deal 6(8) damage. Add a copy of this card to your discard pile.''' entity.attack(using_card['Damage'], targeted_enemy, using_card) @@ -152,6 +152,13 @@ def use_headbutt(targeted_enemy, using_card, entity): entity.move_card(card=entity.discard_pile[choice], move_to=entity.draw_pile, from_location=entity.discard_pile, cost_energy=False) break +def use_handofgreed(targeted_enemy, using_card, entity): + '''Deal 20(25) damage. If Fatal, gain 20(25) Gold.''' + entity.attack(using_card['Damage'], targeted_enemy, using_card) + if targeted_enemy.health <= 0: + entity.gold += using_card['Gold'] + ansiprint(f"You gained {using_card['Gold']} Gold.") + def use_shrugitoff(using_card, entity): '''Gain 8(11) Block. Draw 1 card.''' entity.blocking(using_card['Block']) @@ -181,6 +188,10 @@ def use_pommelstrike(targeted_enemy, using_card, entity): entity.attack(using_card['Damage'], targeted_enemy, using_card) entity.draw_cards(True, using_card['Cards']) +def use_masterofstrategy(using_card, entity): + '''Draw 3(4) cards. Exhaust.''' + entity.draw_cards(True, using_card['Cards']) + def use_truegrit(using_card, entity): '''Gain 7(9) Block. Exhaust a random(not random) card in your hand.''' entity.blocking(using_card['Block']) @@ -321,6 +332,11 @@ def use_infernalblade(using_card, entity): valid_cards = [card for card in cards.values() if card.get('Type') == 'Attack' and card.get('Class') == entity.player_class] entity.hand.append(random.choice(valid_cards).modify_energy_cost(0, 'Set')) +def use_chrysalis(using_card, entity): + '''Add 3(5) random Skills into your hand. They cost 0 this turn. Exhaust.''' + valid_cards = [card() for card in cards if card.type == CardType.SKILL and card.player_class == entity.player_class] + entity.hand.append(random.choice(valid_cards).modify_energy_cost(0, 'Set')) + def use_discovery(using_card, entity): '''Choose 1 of 3 random cards to add to your hand. It costs 0 this turn. Exhaust. (Don't Exhaust.)''' raise NotImplementedError("Need to have a user interaction here") @@ -959,10 +975,10 @@ def use_reaper(enemies, using_card, entity): # "Trip": {"Name": "Trip", "Class": "Colorless", "Rarity": Rarity.UNCOMMON, "Type": CardType.SKILL, "Energy": 0, "Info": "Apply 2 Vulnerable (to ALL enemies)."}, "Apotheosis": {"Name": "Apotheosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Energy+": 1, "Info": "Upgrade ALL of your cards for the rest of combat. Exhaust.", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_apotheosis}, - # "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, - # "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold."}, + "Chrysalis": {"Name": "Chrysalis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Skills into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True, "Target": TargetType.YOURSELF, "Function": use_chrysalis}, + "Hand of Greed": {"Name": "Hand of Greed", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.ATTACK, "Energy": 2, "Info": "Deal 20(25) damage. If this kills a non-minion enemy, gain 20(25) Gold.", "Damage": 20, "Gold": 20, "Target": TargetType.ENEMY, "Function": use_handofgreed}, # "Magnetism": {"Name": "Magnetism", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of each turn, add a random colorless card to your hand."}, - # "Master Of Strategy": {"Name": "Master Of Strategy", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 3(4) cards. Exhaust.", "Exhaust": True}, + "Master Of Strategy": {"Name": "Master Of Strategy", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 0, "Info": "Draw 3(4) cards. Exhaust.", "Exhaust": True, "Cards": 3, "Target": TargetType.YOURSELF, "Function": use_masterofstrategy}, # "Mayhem": {"Name": "Mayhem", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 2, "Energy+": 1, "Info": "At the start of your turn, play the top card of your draw pile."}, # "Metamorphosis": {"Name": "Metamorphosis", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.SKILL, "Energy": 2, "Info": "Add 3(5) random Attacks into your Draw Pile. They cost 0 this combat. Exhaust.", "Exhaust": True}, # "Panache": {"Name": "Panache", "Class": "Colorless", "Rarity": Rarity.RARE, "Type": CardType.POWER, "Energy": 0, "Info": "Every time you play 5 cards in a single turn, deal 10(14) damage to ALL enemies."}, From 034531077a124da2cc88f8aacc1bac97d4dcf29a Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Mon, 5 Feb 2024 16:55:35 +0000 Subject: [PATCH 16/18] loads of typo fixes --- items.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/items.py b/items.py index d73098e9..09d05d5d 100644 --- a/items.py +++ b/items.py @@ -573,7 +573,7 @@ def use_reaper(enemies, using_card, entity): 'Lantern': {'Name': 'Lantern', 'Class': 'Any', 'Rarity': 'Common', 'Energy': 1, 'Info': 'Gain 1 Energy on the first turn of each combat.', 'Flavor': 'An eerie lantern which illuminates only for the user.'}, 'Max Bank': {'Name': 'Maw Bank', 'Class': 'Any', 'Rarity': 'Common', 'Gold': 12, 'Info': 'Whenever you climb a floor, gain 12 Gold. No longer works when you spend any gold at the shop.', 'Flavor': 'Suprisingly popular, despite maw attacks being a common occurence.'}, 'Meal Ticket': {'Name': 'Meal Ticket', 'Class': 'Any', 'Rarity': 'Common', 'Health': 15, 'Info': 'Whenever you enter a shop, heal 15 HP.', 'Flavor': 'Complementary meatballs with every visit!'}, - 'Nunchaku': {'Name': 'Nunchaku', 'Class': 'Any', 'Rarity': 'Common', 'Energy': 1, 'Info': 'Every time you play 10 AttacksEnergy', 'Flavor': 'A good training tool. Inproves the posture and agility of the user.'}, + 'Nunchaku': {'Name': 'Nunchaku', 'Class': 'Any', 'Rarity': 'Common', 'Energy': 1, 'Info': 'Every time you play 10 Attacks, gain 1 Energy', 'Flavor': 'A good training tool. Inproves the posture and agility of the user.'}, 'Oddly Smooth Stone': {'Name': 'Oddly Smooth Stone', 'Class': 'Any', 'Rarity': 'Common', 'Dexterity': 1, 'Info': 'At the start of each combat, gain 1 Dexterity.', 'Flavor': 'You have never seen smething so smooth and pristine. This must be the work of the Ancients.'}, 'Omamori': {'Name': 'Omamori', 'Class': 'Any', 'Rarity': 'Common', 'Curses': 2, 'Info': 'Negate the next 2 Curses you obtain.', 'Flavor': 'A common charm for staving off vile spirits. This one seems to possess a spark of divine energy.'}, 'Orichaicum': {'Name': 'Orichaicum', 'Class': 'Any', 'Rarity': 'Common', 'Block': 6, 'Info': 'If you end your turn without Block, gain 6 Block.', 'Flavor': 'A green tinted metal from an unknown origin.'}, @@ -582,7 +582,7 @@ def use_reaper(enemies, using_card, entity): 'Preserved Insect': {'Name': 'Preserved Insect', 'Class': 'Any', 'Rarity': 'Common', 'Hp Percent Loss': 25, 'Info': 'Enemies in Elite rooms have 20% less health.', 'Flavor': 'The insect seems to create a shrinking aura that targets particularly large enemies.'}, 'Regal Pillow': {'Name': 'Regal Pillow', 'Class': 'Any', 'Rarity': 'Common', 'Heal HP': 15, 'Info': 'Heal an additional 15 HP when you Rest.', 'Flavor': "Now you can get a proper night's rest."}, 'Smiling Mask': {'Name': 'Smiling Mask', 'Class': 'Any', 'Rarity': 'Common', 'Info': "The merchant's card removal service now always costs 50 Gold.", 'Flavor': 'Mask worn by the merchant. He must have spares...'}, - 'Strawberry': {'Name': 'Strawberry', 'Class': 'Any', 'Rarity': 'Common', 'Max HP': 7, 'Flavor': "'Delicious! Haven't seen any of these since the blight.' - Ranwid"}, + 'Strawberry': {'Name': 'Strawberry', 'Class': 'Any', 'Rarity': 'Common', 'Max HP': 7, 'Info': "Upon pickup, raise your Max HP by 7.", 'Flavor': "'Delicious! Haven't seen any of these since the blight.' - Ranwid"}, 'The Boot': {'Name': 'The Boot', 'Class': 'Any', 'Rarity': 'Common', 'Info': 'When you would deal 4 or less unblocked Attack damage, increase it to 5.', 'Flavor': 'When wound up, the boot grows larger in size.'}, 'Tiny Chest': {'Name': 'Tiny Chest', 'Class': 'Any', 'Rarity': 'Common', 'Info': 'Every 4th ? room is a Treasure room.', 'Flavor': '"A fine prototype." - The Architect'}, # Class specific common relics @@ -600,13 +600,13 @@ def use_reaper(enemies, using_card, entity): 'Molten Egg': {'Name': 'Molten Egg', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever you add an Attack card to your deck, it is Upgraded. ', 'Flavor': 'The egg of a Pheonix. It glows red hot with a simmering lava.'}, 'Toxic Egg': {'Name': 'Toxic Egg', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever you add a Skill card to your deck, it is Upgraded. ', 'Flavor': '"What a marvelous discovery! This appears to be the inert egg of some magical creature. Who or what created this?" - Ranwid'}, 'Frozen Egg': {'Name': 'Frozen Egg', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever you add a Power card to your deck, it is Upgraded. ', 'Flavor': 'The egg lies inert and frozen, never to hatch'}, - 'Gremlin Horn': {'Name': 'Gremlin Horn', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever an enemy dies, gain 1 Energy and draw 1 card.'}, + 'Gremlin Horn': {'Name': 'Gremlin Horn', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever an enemy dies, gain 1 Energy and draw 1 card.', 'Flavor': '"Gremlin Nobs are capable of growing until the day they die. Remarkable." - Ranwid'}, 'Horn Cleat': {'Name': 'Horn Cleat', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'At the start of your 2nd trun, gain 14 Block.', 'Flavor': 'Pleasant to hold in the hand. What was it for?'}, 'Ink Bottle': {'Name': 'Ink Bottle', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever you play 10 cards, draw 1 card.', 'Flavor': 'Once exhausted, it appears to refil itself in a different color.'}, 'Kunai': {'Name': 'Kunai', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Every time you play 3 Attacks in a single turn, gain 1 Dexterity.', 'Flavor': 'A blade favored by assasins for its lethality at range.'}, 'Letter Opener': {'Name': 'Letter Opener', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Every time you play 3 Skills in a single turn, deal 5 damage to ALL enemies.', 'Flavor': 'Unnaturally sharp.'}, 'Matryoshka': {'Name': 'Matryoshka', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'The next 2 non-boss chests you open contain 2 relics.', 'Flavor': 'A stackable set of dolls. The paint depicts an unknown bird with white eyes and blue feathers.'}, - 'Meat on the Bone': {'Name': 'Meat on the Bone', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'If your HP is at 50% or lower at the end of combat, heal 12 HP.'}, + 'Meat on the Bone': {'Name': 'Meat on the Bone', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'If your HP is at 50% or lower at the end of combat, heal 12 HP.', 'Flavor': 'The meat keeps replenishing, never seeming to fully run out.' }, 'Mercury Hourglass': {'Name': 'Mercury Hourglass', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'At the start of your turn, deal 3 damage to ALL enemies.', 'Flavor': 'An enchanted hourglass that endlessly drips.'}, 'Mummified Hand': {'Name': 'Mummified Hand', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Whenever you play a Power card, a random card in your hand costs 0 that turn.', 'Flavor': 'Frequently twitches, especially when your pulse is high.'}, 'Ornamental Fan': {'Name': 'Ornamental Fan', 'Class': 'Any', 'Rarity': 'Uncommon', 'Info': 'Every time you play 3 Attacks in a single turn, gain 4 Block.', 'Flavor': 'The fan seems to extend and harden as blood is spilled.'}, @@ -677,7 +677,7 @@ def use_reaper(enemies, using_card, entity): 'Orange Pellets': {'Name': 'Orange Pellets', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Whenever you play a Power, Attack, and Skill in the same turn, remove ALL of your debuffs.', 'Flavor': 'Made from various fungi found throughout the Spire, they will stave off any affliction.'}, 'Orrery': {'Name': 'Orrery', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Choose and add 5 to your deck.', 'Flavor': '"Once you understand the universe..." - Zoroth'}, 'Prismatic Shard': {'Name': 'Prismatic Shard', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Combat reward screens now contain colorless cards and cards from other colors.', 'Flavor': 'Looking through the shard, you are able to see entirely new perspectives.'}, - 'Sling of Courage': {'Name': 'Sling of Courage', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Start each Elite combat with 2 Strength.', 'Flavor': 'A handy tool for dealing with particalarly tough opponents.'}, + 'Sling of Courage': {'Name': 'Sling of Courage', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Start each Elite combat with 2 Strength.', 'Flavor': 'A handy tool for dealing with particalarly tough opponents.'}, 'Strange Spoon': {'Name': 'Strange Spoon', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Cards that Exhaust will instead discard 50% of the time.', 'Flavor': 'Staring at the spoon, it appears to bend and twist around before your eyes.'}, 'The Abacus': {'Name': 'The Abacus', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'Gain 6 Block when you shuffle your draw pile.', 'Flavor': 'One...Two...Three...'}, 'Toolbox': {'Name': 'Toolbox', 'Class': 'Any', 'Rarity': 'Shop', 'Info': 'At the start of each combat, choose 1 of 3 Colorless cards to add to your hand.', 'Flavor': 'A tool for every job.'}, @@ -687,7 +687,7 @@ def use_reaper(enemies, using_card, entity): 'Runic Capacitor': {'Name': 'Runic Capacitor', 'Class': 'Defect', 'Rarity': 'Shop', 'Info': 'Start each combat with 3 additional Orb slots.', 'Flavor': 'More is better.'}, 'Melange': {'Name': 'Melange', 'Class': 'Watcher', 'Rarity': 'Shop', 'Info': 'Whenever you shuffle your draw pile, Scry 3.', 'Flavor': 'Mysterious sands from an unknown origin, smells of cinnamon.'}, # Boss relics - 'Astrolabe': {'Name': 'Astrolabe', 'Class': 'Any', 'Rarity': 'Boss', 'Info': 'Upon pickup, choose and Transform 3 cards, then Upgrade them.', 'Flavor': 'A tool to glean inavluable knowledge from the stars.'}, + 'Astrolabe': {'Name': 'Astrolabe', 'Class': 'Any', 'Rarity': 'Boss', 'Info': 'Upon pickup, choose and Transform 3 cards, then Upgrade them.', 'Flavor': 'A tool to glean inavluable knowledge from the stars.'}, 'Black Star': {'Name': 'Black Star', 'Class': 'Any', 'Rarity': 'Boss', 'Info': 'Elites now drop 2 relics when defeated.', 'Flavor': 'Originally discovered in the town of the serpent, aside a solitary candle.'}, 'Busted Crown': {'Name': 'Busted Crown', 'Class': 'Any', 'Rarity': 'Boss', 'Info': 'Gain 1 Energy at the start of each turn. On card reward screens, you have 2 less cards to choose from.', 'Flavor': "The Champ's crown... or a pale imitation?"}, 'Calling Bell': {'Name': 'Calling Bell', 'Class': 'Any', 'Rarity': 'Boss', 'Info': 'Obtain a special Curse and 3 relics.', 'Flavor': 'The dark iron bell rang 3 times when you found it, but now it remains silent.'}, @@ -1016,7 +1016,7 @@ def activate_sacred_bark(): 'Skill Potion': {'Name': 'Skill Potion', 'Cards': 1 * sacred_multi, 'Card Type': 'Skill', 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Add {"1" if sacred_multi < 2 else "2 copies"} of 3 random Skill cards to your hand, {"it" if sacred_multi < 2 else "they"} costs 0 this turn'}, 'Colorless Potion': {'Name': 'Colorless Potion', 'Cards': 1 * sacred_multi, 'Card Type': 'Colorless', 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Choose {"1" if sacred_multi < 2 else "2 copies"} of 3 random Colorless cards to add to your hand, {"it" if sacred_multi < 2 else "they"} costs 0 this turn'}, 'Block Potion': {'Name': 'Block Potion', 'Block': 12 * sacred_multi, 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Gain {12 * sacred_multi} Block'}, - 'Dexterity Potion': {'Name': 'Dexterity Potion', 'Dexterity': 2 * sacred_multi, 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Gain {2 * sacred_multi} Dexterity'}, + 'Dexterity Potion': {'Name': 'Dexterity Potion', 'Dexterity': 2 * sacred_multi, 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Gain {2 * sacred_multi} Dexterity'}, 'Energy Potion': {'Name': 'Energy Potion', 'Energy': 2 * sacred_multi, 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Gain {2 * sacred_multi} Energy'}, 'Explosive Potion': {'Name': 'Explosive Potion', 'Damage': 10 * sacred_multi, 'Target': 'Any', 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Deal {10 * sacred_multi} damage to ALL enemies'}, 'Fear Potion': {'Name': 'Fear Potion', 'Vulnerable': 3 * sacred_multi, 'Target': 'Enemy', 'Class': 'Any', 'Rarity': 'Common', 'Info': f'Apply {3 * sacred_multi} Vulnerable'}, From 4857cfb0170c30f74f48a504454497b543da94b8 Mon Sep 17 00:00:00 2001 From: Michael Ricks-Aherne Date: Mon, 5 Feb 2024 16:56:03 +0000 Subject: [PATCH 17/18] lots more tests so we can for sure display everything --- definitions.py | 5 +++ shop.py | 100 +++++++++++++++++++++++++++++++++++++++++-------- test_shop.py | 29 ++++++++++++++ 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/definitions.py b/definitions.py index ae3d49df..9cd5fec9 100644 --- a/definitions.py +++ b/definitions.py @@ -53,3 +53,8 @@ class TargetType(StrEnum): RANDOM = 'Random' SINGLE = 'Single' YOURSELF = 'Yourself' + +class CardCategory(StrEnum): + CARD = 'Card' + POTION = 'Potion' + RELIC = 'Relic' diff --git a/shop.py b/shop.py index 258d046f..64432d06 100644 --- a/shop.py +++ b/shop.py @@ -28,37 +28,107 @@ import time from ansi_tags import ansiprint -from definitions import Rarity +from definitions import CardCategory, Rarity from helper import Displayer from items import cards, potions, relics +# Helper functions for displaying cards, potions, and relics. +# These really should be inside the classes for those items +#------------------------------------------------------------ +def card_pretty_string(card, valid): + if valid: + changed_energy = 'light-red' if not card.get('Changed Energy') else 'green' + return f"<{card['Rarity'].lower()}>{card['Name']} | <{card['Type'].lower()}>{card['Type']} | <{changed_energy}>{card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') is not None else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') + else: + return f"{card['Name']} | {card['Type']} | {card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') + +def relic_pretty_string(relic, valid): + if valid: + name_colors = { + 'Starter': 'starter', + 'Common': 'white', + 'Uncommon': 'uncommon', + 'Rare': 'rare', + 'Event': 'event', + 'Shop': 'rare', + 'Boss': 'rare', + 'Special': 'rare'} + return f"<{name_colors[relic['Rarity']]}>{relic['Name']} | {relic['Class']} | {relic['Info']} | {relic['Flavor']}" + else: + return f"{relic['Name']} | {relic['Class']} | {relic['Info']} | {relic['Flavor']}" + +def potion_pretty_string(potion, valid): + if valid: + class_colors = {'Ironclad': 'red', 'Silent': 'dark-green', 'Defect': 'true-blue', 'Watcher': 'watcher-purple', 'Any': 'white'} + rarity_colors = {'Common': 'white', 'Uncommon': 'uncommon', 'Rare': 'rare'} + chosen_class_color = class_colors[potion['Class']] + chosen_rarity_color = rarity_colors[potion['Rarity']] + return f"<{chosen_rarity_color}>{potion['Name']} | <{chosen_class_color}>{potion['Class']} | {potion['Info']}" + else: + return f"{potion['Name']} | {potion['Class']} | {potion['Info']}" + +def determine_item_category(item): + # A massive hack to try to figure out if we've got a card, potion, or relic + try: + name = item['Name'] + except KeyError as e: + raise KeyError(f'The following item has no Name: {item}') from e + card_names = [c['Name'] for c in cards.values()] + potion_names = [p['Name'] for p in potions.values()] + relic_names = [r['Name'] for r in relics.values()] + if name in card_names: + return CardCategory.CARD + elif name in potion_names: + return CardCategory.POTION + elif name in relic_names: + return CardCategory.RELIC + else: + raise ValueError(f"Item {item} not found in any category") + +def category_to_pretty_string(item, valid): + category = determine_item_category(item) + if category == CardCategory.CARD: + return card_pretty_string(item, valid) + elif category == CardCategory.POTION: + return potion_pretty_string(item, valid) + elif category == CardCategory.RELIC: + return relic_pretty_string(item, valid) + else: + raise ValueError(f"Category {category} not understood.") +#------------------------------------------------------------ + class SellableItem(): - def __init__(self, item): + '''A class to represent an item that can be sold in the shop. This is a wrapper around the actual item, and includes a price.''' + def __init__(self, item, price=None): self.item = item - self.price = self.set_price() + if price is not None: + self.price = price + else: + self.price = self.set_price() def invalid_string(self): - card = self.item - return f"{self.price:3d} Gold : {card['Name']} | {card['Type']} | {card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') + pretty_string = category_to_pretty_string(self.item, valid=False) + return f"{self.price:3d} Gold : {pretty_string}" def valid_string(self): - card = self.item - changed_energy = 'light-red' if not card.get('Changed Energy') else 'green' - return f"{self.price:3d} Gold : <{card['Rarity'].lower()}>{card['Name']} | <{card['Type'].lower()}>{card['Type']} | <{changed_energy}>{card.get('Energy', 'Unplayable')}{' Energy' if card.get('Energy') is not None else ''} | {card['Info']}".replace('Σ', '').replace('Ω', '') - - def __str__(self): - return self.valid_string() + pretty_string = category_to_pretty_string(self.item, valid=True) + return f"{self.price:3d} Gold : {pretty_string}" def set_price(self): - if self.item["Rarity"] in ("Basic", "Common"): + '''Set the price of the item based on its rarity.''' + assert "Rarity" in self.item, f"Item {self.item} has no rarity." + if self.item["Rarity"] in (Rarity.BASIC, Rarity.COMMON, Rarity.STARTER): return random.randint(45, 55) - elif self.item["Rarity"] == "Uncommon": + elif self.item["Rarity"] == Rarity.UNCOMMON: return random.randint(68, 82) - elif self.item["Rarity"] == "Rare": + elif self.item["Rarity"] == Rarity.RARE: return random.randint(135, 165) + elif self.item["Rarity"] in (Rarity.CURSE, Rarity.SHOP, Rarity.SPECIAL, Rarity.EVENT, Rarity.BOSS): + # Unsure what to do with these. We'll set to some high bogus value for now. + return 999 else: - raise ValueError(f"Item {self.item} has no rarity") + raise ValueError(f"Item rarity broken") class Shop(): def __init__(self, player, items=None): diff --git a/test_shop.py b/test_shop.py index 2573a2b5..ff5787c2 100644 --- a/test_shop.py +++ b/test_shop.py @@ -3,9 +3,38 @@ import entities import items +from ansi_tags import ansiprint from shop import SellableItem, Shop +@pytest.mark.only +class TestSellableItems(): + def test_cards(self): + '''See that we can make sellable items out of all cards and that they display correctly''' + all_sellable_cards = list(items.cards.values()) + # all_sellable_cards = [card for card in all_sellable_cards if card["Type"] in ("Attack", "Skill", "Power") and card["Rarity"] not in ("Special")] + for card in all_sellable_cards: + sellable = SellableItem(card) + ansiprint(sellable.valid_string()) + ansiprint(sellable.invalid_string()) + + def test_potions(self): + '''See that we can make sellable items out of all potions and that they display correctly''' + all_sellable_potions = list(items.potions.values()) + for potion in all_sellable_potions: + sellable = SellableItem(potion) + ansiprint(sellable.valid_string()) + ansiprint(sellable.invalid_string()) + + def test_relics(self): + '''See that we can make sellable items out of all relics and that they display correctly''' + all_sellable_relics = list(items.relics.values()) + for relic in all_sellable_relics: + sellable = SellableItem(relic) + ansiprint(sellable.valid_string()) + ansiprint(sellable.invalid_string()) + + def test_shop(monkeypatch): player = entities.create_player() cards = [SellableItem(items.cards[x]) for x in ("Strike","Body Slam","Heavy Blade","Warcry")] From 268296cd764058faf57467af097a312eba77a849 Mon Sep 17 00:00:00 2001 From: vesper-arch Date: Fri, 9 Feb 2024 02:58:54 +0000 Subject: [PATCH 18/18] Added support for the card's new format in a lot of places + some misc fixes --- definitions.py | 5 +---- entities.py | 7 +++++-- game.py | 2 +- message_bus_tools.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/definitions.py b/definitions.py index 9cd5fec9..f7b9f547 100644 --- a/definitions.py +++ b/definitions.py @@ -41,9 +41,6 @@ class PlayerClass(StrEnum): WATCHER = 'Watcher' COLORLESS = 'Colorless' ANY = 'Any' - ENTROPIC = 'Entropic' - SNECKO = 'Snecko' - DARK_ESSENCE = 'Dark Essence' class TargetType(StrEnum): ANY = 'Any' @@ -57,4 +54,4 @@ class TargetType(StrEnum): class CardCategory(StrEnum): CARD = 'Card' POTION = 'Potion' - RELIC = 'Relic' + RELIC = 'Relic' \ No newline at end of file diff --git a/entities.py b/entities.py index a252e8b9..93f94531 100644 --- a/entities.py +++ b/entities.py @@ -6,10 +6,14 @@ from ast import literal_eval from copy import deepcopy from ansi_tags import ansiprint -from helper import active_enemies, view, gen, ei +from helper import active_enemies, view, gen, ei, combat_turn from definitions import CombatTier, CardType, Rarity, PlayerClass from message_bus_tools import bus, Message, Registerable, Card +relics = items.relics +cards = items.cards +potions = items.potions + class Player(Registerable): """ Attributes::: @@ -373,7 +377,6 @@ def curse_status_effects(self): self.take_sourceless_dmg(2) def attack(self, dmg, target: 'Enemy', card=None, ignore_block=False): - dmg_affected_by: str = '' # Check if already dead and skip if so if target.health <= 0: return diff --git a/game.py b/game.py index f15667a8..3a806246 100644 --- a/game.py +++ b/game.py @@ -238,7 +238,7 @@ def unknown() -> None: normal_combat = 0.1 treasure_room += 0.02 merchant += 0.03 - Combat.combat(CombatTier.NORMAL) + Combat(player, CombatTier.NORMAL).combat() else: ansiprint(player) chosen_event = choose_event() diff --git a/message_bus_tools.py b/message_bus_tools.py index 50402cff..aa48a4b1 100644 --- a/message_bus_tools.py +++ b/message_bus_tools.py @@ -102,4 +102,4 @@ def modify_energy_cost(self, amount, modify_type='Adjust', one_turn=False): ansiprint(f"{self.name} got its energy set to {amount}.") if one_turn: self.reset_energy_next_turn = True -bus = MessageBus(debug=True) \ No newline at end of file +bus = MessageBus(debug=True)