Added more deck builder logic, including asking prelim questions and adding a land 'starter pack'

This commit is contained in:
mwisnowski 2024-12-06 12:04:39 -08:00
parent aee44190a9
commit 0ecf34210b
3 changed files with 313 additions and 25 deletions

View file

@ -3,10 +3,19 @@ from __future__ import annotations
import inquirer.prompt # type: ignore
import pandas as pd # type: ignore
import pprint # type: ignore
import random
from fuzzywuzzy import fuzz, process # type: ignore
from fuzzywuzzy import fuzz, process, utils # type: ignore
from IPython.display import display
import settings
from setup import determine_legendary
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
pd.set_option('display.max_colwidth', 5)
# Basic deck builder, initial plan will just be for kindred support.
# Would like to add logic for other themes, as well as automatically go
# through the commander and find suitable themes.
@ -23,6 +32,11 @@ class DeckBuilder:
# Commander
self.commander = ''
self.commander_info = {}
self.color_identity = ''
self.colors = []
self.creature_types = []
self.info_tags = []
self.commander_df = pd.DataFrame()
# Library (99 cards total)
self.library = []
@ -92,7 +106,11 @@ class DeckBuilder:
card_choice = answer['card_prompt']
# Logic to find the card in the legendary_cards csv, then display it's information
df = pd.read_csv('csv_files/legendary_cards.csv', low_memory=False)
try:
df = pd.read_csv('csv_files/legendary_cards.csv')
except FileNotFoundError:
determine_legendary()
df = pd.read_csv('csv_files/legendary_cards.csv')
fuzzy_card_choice = process.extractOne(card_choice, df['name'], scorer=fuzz.ratio)
fuzzy_card_choice = fuzzy_card_choice[0]
filtered_df = df[df['name'] == fuzzy_card_choice]
@ -101,6 +119,7 @@ class DeckBuilder:
df_dict = filtered_df.to_dict('list')
print('Is this the card you chose?')
pprint.pprint(df_dict, sort_dicts=False)
self.commander_df = pd.DataFrame(df_dict)
# Confirm if card entered was correct
correct_commander = [
@ -114,18 +133,259 @@ class DeckBuilder:
if commander_confirmed:
commander_chosen = True
self.commander_info = df_dict
first_key = list(self.commander_info.keys())[0]
self.commander = str(self.commander_info[first_key])
self.commander = self.commander_df.at[0, 'name']
#print(self.commander)
else:
commander_chosen = False
# Send commander info to setup commander, including extracting info on colors, color identity,
# creature types, and other information, like keywords, abilities, etc...
self.commander_setup()
def commander_setup(self):
# Load commander info into a dataframe
df = self.commander_df
self.commander_keywords = df.at[0, 'keywords']
self.commander_power = int(df.at[0, 'power'])
self.commander_toughness = int(df.at[0, 'toughness'])
self.commander_mana_cost = df.at[0, 'manaCost']
# Run the color setup
self.set_color_identity(df)
# Run the creature type setup
self.set_creature_types(df)
# Setup deck theme tags
self.setup_deck_tags(df)
def set_color_identity(self, df):
# Set color identity
self.color_identity = df.at[0, 'colorIdentity'].split(', ')
# Set creature colors
self.colors = df.at[0, 'colors'].split(', ')
def set_creature_types(self, df):
# Set creature types
types = df.at[0, 'type']
print(types)
split_types = types.split()
for type in split_types:
if type not in settings.non_creature_types:
self.creature_types.append(type)
def setup_deck_tags(self, df):
# Determine card tags, such as counters theme
"""keywords = df.at[0, 'keywords'].split()
for keyword in keywords:
settings.theme_tags.append(keyword.lower())
print(settings.theme_tags)"""
self.check_tags(df.at[0, 'text'], settings.theme_tags)
#print(card_tags)
def check_tags(self, string, word_list, threshold=80):
card_tags = []
for word in word_list:
#print(word)
if word == '+1/+1 counter' or word == '-1/-1 counter':
threshold += 20
if fuzz.partial_ratio(string.lower(), word.lower()) >= threshold:
card_tags.append(word)
#return True
#return False
self.commander_tags = card_tags
def determine_ideals(self):
# "Free" slots that can be used for anything that isn't the ideals
self.free_slots = 99
# Determine ideal land count
print('How many lands would you like to include?\n'
'Before ramp is taken into account, 38-40 would be "normal" for a deck.\n'
'Broadly speaking, for every mana produced per 3 mana spent on ramp could reduce land count by 1.\n'
'If you\'re playing landfall, probably consider 40 as baseline before ramp.')
question = [
inquirer.Text
]
inquirer.Text(
'land_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_land_count = int(answer['land_prompt'])
self.free_slots -= self.ideal_land_count
# Determine ideal creature count
print('How many creatures would you like to include?\n'
'Something like 25-30 would be a good starting point.\n'
'If you\'re going for a kindred theme, going past 30 is likely normal.\n'
'Also be sure to take into account token generation, but remember you\'ll want enough to stay safe')
question = [
inquirer.Text(
'creature_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_creature_count = int(answer['creature_prompt'])
self.free_slots -= self.ideal_creature_count
# Determine spot/targetted removal
print('How many spot removal pieces would you like to include?\n'
'A good starting point is about 8-12 pieces of spot removal.\n'
'Counterspells can be consisdered proactive removal and protection.\n'
'If you\'re going spellslinger, more would be a good idea as you might have less cretaures.')
question = [
inquirer.Text(
'removal_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_removal = int(answer['removal_prompt'])
self.free_slots -= self.ideal_removal
# Determine board wipes
print('How many board wipesyou like to include?\n'
'Somewhere around 2-3 is good to help eliminate threats, but also prevent the game from running long\n.'
'This can include damaging wipes like \'Blasphemous Act\' or toughness reduction like \'Meathook Massacre\'.')
question = [
inquirer.Text(
'board_wipe_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_wipes = int(answer['board_wipe_prompt'])
self.free_slots -= self.ideal_wipes
# Determine card advantage
print('How many pieces of card advantage would you like to include?\n'
'10 pieces of card advantage is good, up to 14 is better.\n'
'Try to have a majority of it be non-conditional, and only have a couple of \'Rhystic Study\' style effects.')
question = [
inquirer.Text(
'draw_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_card_advantage = int(answer['draw_prompt'])
self.free_slots -= self.ideal_card_advantage
# Determine ramp
print('How many pieces of ramp would you like to include?\n'
'You\'re gonna want a decent amount of ramp, both getting lands or mana rocks/dorks.\n'
'A good baseline is 8-12, scaling up with average CMC.')
question = [
inquirer.Text(
'ramp_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_ramp = int(answer['ramp_prompt'])
self.free_slots -= self.ideal_ramp
# Determine how many protection spells
print('How protection spells would you like to include?\n'
'This can be individual protection, board protection, fogs, or similar effects.\n'
'Things that grant indestructible, hexproof, phase out, or event just counterspells.\n'
'This can be a widely variable ideal count, and can be as low as 5, and up past 15,\n'
'it depends on your commander and how important your wincons are.')
question = [
inquirer.Text(
'protection_prompt',
message=''
)
]
answer = inquirer.prompt(question)
self.ideal_protection = int(answer['protection_prompt'])
self.free_slots -= self.ideal_protection
print(f'Free slots that aren\'t part of the ideals: {self.free_slots}')
print('Keep in mind that many of the ideals can also cover multiple roles, but this will give a baseline POV.')
def add_lands(self):
# Begin the process to add lands, the number will depend on ideal land count, ramp,
# and if any utility lands may be helpful.
# By default, ({self.ideal_land_count} - 5) basic lands will be added, distributed
# across the commander color identity. These will be removed for utility lands,
# multi-color producing lands, fetches, and any MDFCs added later
print(f'Adding {self.ideal_land_count} - 5 basic lands.')
for color in self.color_identity:
if color == 'W':
basic = 'Plains'
elif color == 'U':
basic = 'Island'
elif color == 'B':
basic = 'Swamp',
elif color == 'R':
basic = 'Mountain'
elif color == 'G':
basic = 'Forest'
"""if color =='':
basic = 'Wastes'"""
num_basics = self.ideal_land_count - 5
for _ in range(num_basics // len(self.color_identity)):
self.land_cards.append(basic)
#print(self.land_cards)
# Add lands that are good in most any commander deck
print('Adding \'standard\' non-basics')
self.land_cards.append('Reliquary Tower')
if 'landfall' not in self.commander_tags:
self.land_cards.append('Ash Barrens')
if len(self.color_identity) > 1:
self.land_cards.append('Command Tower')
self.land_cards.append('Exotic Orchard')
self.land_cards.append('Evolving Wilds')
if len(self.color_identity) <= 2:
self.land_cards.append('War Room')
if self.commander_power >= 5:
self.land_cards.append('Rogue\'s Passage')
# If over ideal land count, remove random basics until ideal land count
while len(self.land_cards) > self.ideal_land_count:
self.remove_basic()
#if self.land_cards < self.ideal_land_count:
# pass
print(*self.land_cards, sep='\n')
print(len(self.land_cards))
def remove_basic(self):
basic_lands = []
for color in self.color_identity:
if color == 'W':
basic = 'Plains'
elif color == 'U':
basic = 'Island'
elif color == 'B':
basic = 'Swamp',
elif color == 'R':
basic = 'Mountain'
elif color == 'G':
basic = 'Forest'
if basic not in basic_lands:
basic_lands.append(basic)
basic_land = random.choice(basic_lands)
#print(basic_land)
self.land_cards.remove(basic_land)
def add_creatures(self):
# Begin the process to add creatures, the number added will depend on what the
# deck plan is, the commander, creature types, etc...
print(f'Adding the creatures to deck, a baseline based on the ideal creature count of {self.ideal_creature_count} will be used.')
build_deck = DeckBuilder()
"""build_deck.determine_commander()
print(f'Commander: {build_deck.commander}')
print(f'Color Identity: {build_deck.color_identity}')
print(f'Commander Colors: {build_deck.colors}')
print(f'Commander Creature Types: {build_deck.creature_types}')
print(f'Commander tags: {build_deck.commander_tags}')"""
build_deck.determine_commander()
print(build_deck.commander)
build_deck.ideal_land_count = 35
build_deck.add_lands()

44
settings.py Normal file
View file

@ -0,0 +1,44 @@
banned_cards = ['Ancestral Recall', 'Balance', 'Biorhythm', 'Black Lotus',
'Braids, Cabal Minion', 'Chaos Orb', 'Coalition Victory',
'Channel', 'Dockside Extortionist', 'Emrakul, the Aeons Torn',
'Erayo, Soratami Ascendant', 'Falling Star', 'Fastbond',
'Flash', 'Gifts Ungiven', 'Golos, Tireless Pilgrim',
'Griselbrand', 'Hullbreacher', 'Iona, Shield of Emeria',
'Karakas', 'Jeweled Lotus', 'Leovold, Emissary of Trest',
'Library of Alexandria', 'Limited Resources', 'Lutri, the Spellchaser',
'Mana Crypt', 'Mox Emerald', 'Mox Jet', 'Mox Pearl', 'Mox Ruby',
'Mox Sapphire', 'Nadu, Winged Wisdom', 'Panoptic Mirror',
'Paradox Engine', 'Primeval Titan', 'Prophet of Kruphix',
'Recurring Nightmare', 'Rofellos, Llanowar Emissary', 'Shahrazad',
'Sundering Titan', 'Sway of the Stars', 'Sylvan Primordial',
'Time Vault', 'Time Walk', 'Tinker', 'Tolarian Academy',
'Trade Secrets', 'Upheaval', 'Yawgmoth\'s Bargain']
non_creature_types = ['Legendary', 'Creature', 'Enchantment', 'Artifact',
'Battle', 'Sorcery', 'Instant', 'Land', '-', '',
'Blood', 'Clue', 'Food', 'Gold', 'Incubator',
'Junk', 'Map', 'Powerstone', 'Treasure',
'Equipment', 'Fortification', 'vehicle',
'Bobblehead', 'Attraction', 'Contraption',
'Siege',
'Aura', 'Background', 'Saga', 'Role', 'Shard',
'Cartouche', 'Case', 'Class', 'Curse', 'Rune',
'Shrine',
'Plains', 'Island', 'Swamp', 'Forest', 'Mountain',
'Cave', 'Desert', 'Gate', 'Lair', 'Locus', 'Mine',
'Power-Plant', 'Sphere', 'Tower', 'Urza\'s']
theme_tags = ['+1/+1 counter', 'one or more counters', 'tokens', 'gain life', 'one or more creature tokens',
'creature token', 'treasure', 'create token', 'draw a card', 'flash', 'choose a creature type',
'play land', 'artifact you control enters', 'enchantment you control enters', 'poison counter',
'from graveyard', 'mana value', 'from exile', 'mana of any color', 'attacks', 'total power',
'greater than starting life', 'lose life', 'whenever you sacrifice', 'creature dying',
'creature enters', 'creature leaves', 'creature dies', 'put into graveyard', 'sacrifice',
'sacricifice creature', 'sacrifice artifact', 'sacrifice another creature', '-1/-1 counter',
'control get +1/+1', 'control dies', 'experience counter', 'triggered ability', 'token']
board_wipe_tags = ['destroy all', 'destroy each', 'return all', 'return each', 'deals damage to each',
'exile all', 'exile each', 'creatures get -X/-X', 'sacrifices all', 'sacrifices each',
'sacrifices the rest']
targetted_removal_tags = ['exile target', 'destroy target', 'return target', 'shuffles target', 'you control',
'deals damage to target','loses all abilities']

View file

@ -3,29 +3,14 @@ from __future__ import annotations
import pandas as pd # type: ignore
import requests # type: ignore
from settings import banned_cards
staple_lists = ['Colorless', 'White', 'Blue', 'Black']
colorless_staples = [] # type: ignore
white_staples = [] # type: ignore
blue_staples = [] # type: ignore
black_staples = [] # type: ignore
banned_cards = ['Ancestral Recall', 'Balance', 'Biorhythm', 'Black Lotus',
'Braids, Cabal Minion', 'Chaos Orb', 'Coalition Victory',
'Channel', 'Dockside Extortionist', 'Emrakul, the Aeons Torn',
'Erayo, Soratami Ascendant', 'Falling Star', 'Fastbond',
'Flash', 'Gifts Ungiven', 'Golos, Tireless Pilgrim',
'Griselbrand', 'Hullbreacher', 'Iona, Shield of Emeria',
'Karakas', 'Jeweled Lotus', 'Leovold, Emissary of Trest',
'Library of Alexandria', 'Limited Resources', 'Lutri, the Spellchaser',
'Mana Crypt', 'Mox Emerald', 'Mox Jet', 'Mox Pearl', 'Mox Ruby',
'Mox Sapphire', 'Nadu, Winged Wisdom', 'Panoptic Mirror',
'Paradox Engine', 'Primeval Titan', 'Prophet of Kruphix',
'Recurring Nightmare', 'Rofellos, Llanowar Emissary', 'Shahrazad',
'Sundering Titan', 'Sway of the Stars', 'Sylvan Primordial',
'Time Vault', 'Time Walk', 'Tinker', 'Tolarian Academy',
'Trade Secrets', 'Upheaval', 'Yawgmoth\'s Bargain']
def filter_by_color(df, column_name, value, new_csv_name):
# Filter dataframe
filtered_df = df[df[column_name] == value]
@ -653,4 +638,3 @@ def generate_staple_lists():
for items in staples:
f.write('%s\n' %items)
determine_legendary()