File size: 5,543 Bytes
c8f1b74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
971af66
 
c8f1b74
 
971af66
c8f1b74
971af66
 
 
c8f1b74
971af66
 
b743918
971af66
c8f1b74
 
 
971af66
c8f1b74
971af66
c8f1b74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
971af66
c8f1b74
971af66
 
 
 
c8f1b74
971af66
 
 
c8f1b74
971af66
 
c8f1b74
971af66
 
 
c8f1b74
 
 
971af66
c8f1b74
 
971af66
 
 
 
 
 
c8f1b74
 
971af66
 
c8f1b74
 
 
 
971af66
 
 
 
 
 
 
c8f1b74
971af66
 
c8f1b74
 
971af66
 
 
c8f1b74
 
 
 
 
 
 
 
 
 
 
 
 
971af66
 
 
c8f1b74
 
971af66
 
 
 
c8f1b74
971af66
 
c8f1b74
 
971af66
c8f1b74
971af66
c8f1b74
971af66
 
c8f1b74
971af66
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
MTG Draft Assistant Streamlit App
---------------------------------
A booster-draft helper written with Streamlit and deployable on Hugging Face Spaces.

Changes in this revision
~~~~~~~~~~~~~~~~~~~~~~~~
* **supported_sets.txt** – one set code per line.
* Sidebar now shows a **single-choice list** (radio buttons) sourced from that
  file instead of a free-text box, so users can only draft sets you actually
  support.
* Fallback to the old text input if the text-file is missing or empty.

Replace the three stub functions (`load_model`, `suggest_pick`,
`generate_booster`) with your real model-/API-calls when you are ready.
"""

from __future__ import annotations

import os
import random
from pathlib import Path
from typing import Dict, List

import requests
import streamlit as st

# -----------------------------------------------------------------------------
# 0. Constants & helpers
# -----------------------------------------------------------------------------

SUPPORTED_SETS_PATH = Path("helper_files/supported_sets.txt")

@st.cache_data(show_spinner="Reading supported sets …")
def get_supported_sets(path: Path = SUPPORTED_SETS_PATH) -> List[str]:
    """Return a list of legal set codes read from *supported_sets.txt*.

    The file should contain **one set tag per line**, e.g.::

        WOE
        LCI
        MKM

    If the file is missing we fall back to an empty list so the UI
    degrades gracefully.
    """
    if path.is_file():
        return [ln.strip() for ln in path.read_text().splitlines() if ln.strip()]
    return []

# -----------------------------------------------------------------------------
# 1. Model loading (stub)
# -----------------------------------------------------------------------------

@st.cache_resource(show_spinner="Loading draft model …")
def load_model():
    """Load and return the trained drafting model.

    Adapt to your own pipeline; see previous revision for an example that pulls
    an artefact from *huggingface_hub*.  Returning *None* leaves us in demo
    mode and will pick random cards.
    """
    return None

model = load_model()

# -----------------------------------------------------------------------------
# 2. Draft-logic helpers (stubs)
# -----------------------------------------------------------------------------

def suggest_pick(pack: List[Dict], picks: List[Dict]) -> Dict:
    """Return the card the model recommends from *pack*."""
    if model is None:
        return random.choice(pack)
    return model.predict(pack=pack, picks=picks)  # type: ignore[attr-defined]


def fetch_card_image(card_name: str) -> str:
    """Fetch card art URL from Scryfall (normal size)."""
    r = requests.get(
        "https://api.scryfall.com/cards/named", params={"exact": card_name, "format": "json"}
    )
    r.raise_for_status()
    data = r.json()
    if "image_uris" in data:
        return data["image_uris"]["normal"]
    return data["card_faces"][0]["image_uris"]["normal"]


def generate_booster(set_code: str) -> List[Dict]:
    """Return a pseudo-random 15-card booster using Scryfall search."""
    url = f"https://api.scryfall.com/cards/search?q=set%3A{set_code}+is%3Abooster+unique%3Aprints"
    cards: List[Dict] = []
    while url:
        resp = requests.get(url)
        resp.raise_for_status()
        payload = resp.json()
        cards += payload["data"]
        url = payload.get("next_page") if payload.get("has_more") else None
    return random.sample(cards, 15)

# -----------------------------------------------------------------------------
# 3. Streamlit UI
# -----------------------------------------------------------------------------

st.set_page_config(page_title="MTG Draft Assistant", page_icon="πŸƒ")

st.title("πŸƒ MTG Draft Assistant")

# ---------------- Sidebar -----------------------------------------------------

with st.sidebar:
    st.header("Draft setup")

    supported_sets = get_supported_sets()

    if supported_sets:
        set_code = st.radio("Choose a set to draft", supported_sets, index=0)
    else:
        st.warning(
            "*supported_sets.txt* not found or empty. Using free-text input instead.",
            icon="⚠️",
        )
        set_code = st.text_input("Set code", value="WOE")

    if st.button("Start new draft", type="primary"):
        st.session_state["pack"] = generate_booster(set_code)
        st.session_state["picks"] = []

# ---------------- Session state guards ---------------------------------------

st.session_state.setdefault("pack", [])
st.session_state.setdefault("picks", [])

if not st.session_state["pack"]:
    st.info("Choose **Start new draft** in the sidebar to open pack 1.")
    st.stop()

pack: List[Dict] = st.session_state["pack"]
picks: List[Dict] = st.session_state["picks"]

st.subheader(f"Pack {len(picks) // 15 + 1} β€” Pick {len(picks) % 15 + 1}")
suggested = suggest_pick(pack, picks)

st.success(f"**Model suggests:** {suggested['name']}")

# Display current pack as a 5-column grid of card images
cols = st.columns(5)
for idx, card in enumerate(pack):
    col = cols[idx % 5]
    col.image(fetch_card_image(card["name"]), use_column_width=True)
    if col.button(f"Pick {card['name']}", key=f"pick-{idx}"):
        picks.append(card)
        pack.remove(card)
        if not pack:  # end of pack β‡’ open a fresh booster
            st.session_state["pack"] = generate_booster(set_code)
        st.experimental_rerun()

with st.expander("Current picks", expanded=False):
    st.write("\n".join([c["name"] for c in picks]))