161 lines
6.3 KiB
Python
161 lines
6.3 KiB
Python
"""
|
|
Prompt templates for the three deck modes.
|
|
|
|
Output contract — Claude returns a single JSON object:
|
|
{
|
|
"deck_name": "string",
|
|
"strategy_summary": "string",
|
|
"cards": [
|
|
{
|
|
"name": "Card Name", // append [UNOWNED] when prefer_owned=True
|
|
"slot": "creature|instant|sorcery|enchantment|artifact|planeswalker|land|battle",
|
|
"quantity": 1,
|
|
"reasoning": "1-2 sentences"
|
|
}
|
|
],
|
|
"cuts": [ // CULL mode only
|
|
{ "name": "Card Name", "reasoning": "..." }
|
|
]
|
|
}
|
|
"""
|
|
|
|
SYSTEM_PROMPT = (
|
|
"You are an expert Magic: The Gathering deck builder specialising in the Commander "
|
|
"(EDH) format. You have deep knowledge of card synergies, mana curves, colour "
|
|
"identity rules, staples, budget alternatives, and current metagame trends.\n\n"
|
|
"You always respond with a single valid JSON object — no markdown fences, no "
|
|
"preamble, no commentary outside the JSON. Your card names must exactly match "
|
|
"official Magic card names (English). Every card must be legal in Commander "
|
|
"and within the commander's colour identity.\n\n"
|
|
"CRITICAL: Your response must use EXACTLY this JSON structure:\n"
|
|
"{\n"
|
|
' "deck_name": "string",\n'
|
|
' "strategy_summary": "string",\n'
|
|
' "cards": [\n'
|
|
' {"name": "Card Name", "slot": "creature|instant|sorcery|enchantment|artifact|planeswalker|land|battle", "quantity": 1, "reasoning": "..."}\n'
|
|
" ],\n"
|
|
' "cuts": []\n'
|
|
"}\n"
|
|
"Do NOT use any other key names. The cards array must contain exactly the non-commander cards."
|
|
)
|
|
|
|
|
|
def generate_prompt(
|
|
commander: str,
|
|
playstyle: str | None,
|
|
constraint_text: str,
|
|
owned_list_text: str,
|
|
) -> tuple[str, str]:
|
|
playstyle_line = (
|
|
f"Playstyle preference: {playstyle}"
|
|
if playstyle
|
|
else "Playstyle preference: not specified — choose the strongest strategy for this commander."
|
|
)
|
|
|
|
user_message = (
|
|
f"Build a complete Commander deck for the following commander.\n\n"
|
|
f"COMMANDER: {commander}\n"
|
|
f"{playstyle_line}\n\n"
|
|
f"CONSTRAINTS:\n{constraint_text}\n"
|
|
f"{owned_list_text}\n"
|
|
f"DECK REQUIREMENTS:\n"
|
|
f"- Exactly 99 cards (not counting the commander)\n"
|
|
f"- All cards must be legal in Commander and within {commander}'s colour identity\n"
|
|
f"- Include a balanced mana base (35-40 lands for most strategies)\n"
|
|
f"- Include ramp (8-12 pieces), card draw (8-10 pieces), removal (8-10 pieces), "
|
|
f"and win conditions appropriate to the playstyle\n"
|
|
f"- Slot values: creature, instant, sorcery, enchantment, artifact, planeswalker, land, battle\n"
|
|
f"- Quantity for basic lands may be >1; all other cards quantity = 1\n"
|
|
f"- The 'reasoning' field must explain why the card fits THIS specific deck\n\n"
|
|
f"Respond with the JSON object only."
|
|
)
|
|
return SYSTEM_PROMPT, user_message
|
|
|
|
|
|
def complete_prompt(
|
|
commander: str,
|
|
playstyle: str | None,
|
|
existing_cards: list[dict],
|
|
constraint_text: str,
|
|
owned_list_text: str,
|
|
) -> tuple[str, str]:
|
|
existing_count = sum(c.get("quantity", 1) for c in existing_cards)
|
|
slots_needed = 99 - existing_count
|
|
|
|
playstyle_line = (
|
|
f"Playstyle preference: {playstyle}"
|
|
if playstyle
|
|
else "Playstyle preference: infer from existing cards."
|
|
)
|
|
|
|
existing_formatted = "\n".join(
|
|
f"- {c['card_name']} ({c.get('slot', 'unknown')})"
|
|
+ (f" x{c['quantity']}" if c.get('quantity', 1) > 1 else "")
|
|
for c in existing_cards
|
|
)
|
|
|
|
user_message = (
|
|
f"The user has a partial Commander deck and needs suggestions to complete it.\n\n"
|
|
f"COMMANDER: {commander}\n"
|
|
f"{playstyle_line}\n"
|
|
f"SLOTS NEEDED: {slots_needed} more cards to reach 99\n\n"
|
|
f"EXISTING CARDS ({existing_count} cards):\n{existing_formatted}\n\n"
|
|
f"CONSTRAINTS:\n{constraint_text}\n"
|
|
f"{owned_list_text}\n"
|
|
f"INSTRUCTIONS:\n"
|
|
f"- Suggest exactly {slots_needed} new cards to fill remaining slots\n"
|
|
f"- Do not repeat any card already in the existing list\n"
|
|
f"- All cards must be legal in Commander and within {commander}'s colour identity\n"
|
|
f"- Analyse existing cards to infer strategy and fill gaps (ramp, draw, removal, win-cons)\n"
|
|
f"- The 'cards' array contains ONLY the new cards you are recommending\n"
|
|
f"- strategy_summary should describe how the completed deck plays\n\n"
|
|
f"Respond with the JSON object only."
|
|
)
|
|
return SYSTEM_PROMPT, user_message
|
|
|
|
|
|
def cull_prompt(
|
|
commander: str,
|
|
existing_cards: list[dict],
|
|
target_count: int,
|
|
constraint_text: str,
|
|
owned_list_text: str,
|
|
prefer_owned: bool,
|
|
) -> tuple[str, str]:
|
|
current_count = sum(c.get("quantity", 1) for c in existing_cards)
|
|
cuts_needed = current_count - target_count
|
|
|
|
existing_formatted = "\n".join(
|
|
f"- {c['card_name']} ({c.get('slot', 'unknown')})"
|
|
+ (" [OWNED]" if c.get("is_owned") else "")
|
|
for c in existing_cards
|
|
)
|
|
|
|
ownership_note = (
|
|
"\n- IMPORTANT: Prioritise cutting cards NOT marked [OWNED] first — "
|
|
"this saves the user money on cards they would have to buy."
|
|
if prefer_owned
|
|
else ""
|
|
)
|
|
|
|
user_message = (
|
|
f"The user's Commander deck is oversized and needs to be culled.\n\n"
|
|
f"COMMANDER: {commander}\n"
|
|
f"CURRENT SIZE: {current_count} cards\n"
|
|
f"TARGET SIZE: {target_count} cards\n"
|
|
f"CUTS NEEDED: {cuts_needed} cards\n\n"
|
|
f"CURRENT DECKLIST:\n{existing_formatted}\n\n"
|
|
f"CONSTRAINTS:\n{constraint_text}\n"
|
|
f"{owned_list_text}\n"
|
|
f"INSTRUCTIONS:\n"
|
|
f"- Recommend exactly {cuts_needed} cards to cut\n"
|
|
f"- Identify redundancy, weak synergy, overcosted cards, and cards that "
|
|
f"don't advance the primary strategy\n"
|
|
f"- Order the 'cuts' array from most-recommended to least-recommended cut{ownership_note}\n"
|
|
f"- The 'cards' array contains all {target_count} REMAINING cards after cuts\n"
|
|
f"- Provide specific reasoning for each cut explaining why it's weaker than what stays\n"
|
|
f"- strategy_summary describes the refined deck after cuts\n\n"
|
|
f"Respond with the JSON object only."
|
|
)
|
|
return SYSTEM_PROMPT, user_message
|