Spaces:
Sleeping
Sleeping
| """ | |
| --------------------------------- | |
| Booster‑draft helper for Magic: The Gathering, built with Streamlit and ready | |
| for Hugging Face Spaces deployment. | |
| 🆕 UI tweaks in this revision | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| * **Set selection is now hidden** in an *expander* inside the sidebar—keeps the | |
| layout clean. | |
| * Added a **second tab – “Card rankings”**. When you pick a set, the tab shows a | |
| (stub) ranked list of cards from that set. Replace the `rank_cards()` stub | |
| with real logic later. | |
| """ | |
| from __future__ import annotations | |
| import random, os | |
| from pathlib import Path | |
| from typing import Dict, List | |
| import requests | |
| import streamlit as st | |
| from draft_model import DraftModel | |
| os.environ.setdefault("HF_HOME", os.path.expanduser("~/.cache/huggingface")) | |
| SUPPORTED_SETS_PATH = Path("supported_sets.txt") | |
| st.write("Running Streamlit", st.__version__) | |
| def get_supported_sets(path: Path = SUPPORTED_SETS_PATH) -> List[str]: | |
| """Return a list of legal set codes read from *supported_sets.txt*.""" | |
| if path.is_file(): | |
| return [ln.strip() for ln in path.read_text().splitlines() if ln.strip()] | |
| return [] | |
| def load_model(): | |
| return DraftModel() | |
| def rank_cards(set_code: str) -> List[Dict]: | |
| """Return a stubbed ranking list for *set_code*. | |
| Replace with your real evaluation logic. For now we just pull 30 random | |
| commons from the set and assign a dummy score. | |
| """ | |
| url = f"https://api.scryfall.com/cards/search?q=set%3A{set_code}+unique%3Aprints+is%3Acommon" | |
| cards: List[Dict] = [] | |
| while url and len(cards) < 60: # cap network use | |
| r = requests.get(url) | |
| r.raise_for_status() | |
| payload = r.json() | |
| cards += payload["data"] | |
| url = payload.get("next_page") if payload.get("has_more") else None | |
| sample = random.sample(cards, k=min(30, len(cards))) if cards else [] | |
| ranked = [ | |
| {"name": c["name"], "score": round(random.random(), 2)} for c in sample | |
| ] | |
| ranked.sort(key=lambda x: x["score"], reverse=True) | |
| return ranked | |
| model = load_model() | |
| # ----------------------------------------------------------------------------- | |
| # 2. Draft‑logic helpers (stubs) | |
| # ----------------------------------------------------------------------------- | |
| def suggest_pick(pack: List[Dict], picks: List[Dict]) -> Dict: | |
| 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: | |
| 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]: | |
| 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() | |
| # Hide control in an expander (collapsed by default) | |
| with st.expander("Set selection", expanded=False): | |
| 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 ------------------------------------------------------ | |
| st.session_state.setdefault("pack", []) | |
| st.session_state.setdefault("picks", []) | |
| # -------- Main content organised in tabs ------------------------------------ | |
| tabs = st.tabs(["Draft", "Card rankings"]) | |
| # --- Tab 1: Draft ------------------------------------------------------------ | |
| with tabs[0]: | |
| if not st.session_state["pack"]: | |
| st.info("Choose **Start new draft** in the sidebar to open pack 1.") | |
| else: | |
| 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']}") | |
| 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])) | |
| # --- Tab 2: Card rankings ---------------------------------------------------- | |
| with tabs[1]: | |
| st.header("Card rankings for set " + set_code) | |
| if set_code: | |
| ranking = rank_cards(set_code) | |
| if ranking: | |
| for idx, card in enumerate(ranking, start=1): | |
| st.write(f"{idx}. {card['name']} — **{card['score']:.2f}**") | |
| else: | |
| st.info("No cards found for this set (or Scryfall unavailable).") | |
| else: | |
| st.info("Select a set in the sidebar to view rankings.") | |