Timo commited on
Commit
f4bf513
Β·
1 Parent(s): 892a8b7
Files changed (1) hide show
  1. src/streamlit_app.py +160 -0
src/streamlit_app.py CHANGED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MTG Draft Assistant Streamlit App
3
+ ---------------------------------
4
+ A booster-draft helper written with Streamlit and deployable on Hugging Face Spaces.
5
+
6
+ Changes in this revision
7
+ ~~~~~~~~~~~~~~~~~~~~~~~~
8
+ * **supported_sets.txt** – one set code per line.
9
+ * Sidebar now shows a **single-choice list** (radio buttons) sourced from that
10
+ file instead of a free-text box, so users can only draft sets you actually
11
+ support.
12
+ * Fallback to the old text input if the text-file is missing or empty.
13
+
14
+ Replace the three stub functions (`load_model`, `suggest_pick`,
15
+ `generate_booster`) with your real model-/API-calls when you are ready.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ import random
22
+ from pathlib import Path
23
+ from typing import Dict, List
24
+
25
+ import requests
26
+ import streamlit as st
27
+
28
+ # -----------------------------------------------------------------------------
29
+ # 0. Constants & helpers
30
+ # -----------------------------------------------------------------------------
31
+
32
+ SUPPORTED_SETS_PATH = Path("src/helper_files/supported_sets.txt")
33
+
34
+ @st.cache_data(show_spinner="Reading supported sets …")
35
+ def get_supported_sets(path: Path = SUPPORTED_SETS_PATH) -> List[str]:
36
+ """Return a list of legal set codes read from *supported_sets.txt*.
37
+
38
+ The file should contain **one set tag per line**, e.g.::
39
+
40
+ WOE
41
+ LCI
42
+ MKM
43
+
44
+ If the file is missing we fall back to an empty list so the UI
45
+ degrades gracefully.
46
+ """
47
+ if path.is_file():
48
+ return [ln.strip() for ln in path.read_text().splitlines() if ln.strip()]
49
+ return []
50
+
51
+ # -----------------------------------------------------------------------------
52
+ # 1. Model loading (stub)
53
+ # -----------------------------------------------------------------------------
54
+
55
+ @st.cache_resource(show_spinner="Loading draft model …")
56
+ def load_model():
57
+ """Load and return the trained drafting model.
58
+
59
+ Adapt to your own pipeline; see previous revision for an example that pulls
60
+ an artefact from *huggingface_hub*. Returning *None* leaves us in demo
61
+ mode and will pick random cards.
62
+ """
63
+ return None
64
+
65
+ model = load_model()
66
+
67
+ # -----------------------------------------------------------------------------
68
+ # 2. Draft-logic helpers (stubs)
69
+ # -----------------------------------------------------------------------------
70
+
71
+ def suggest_pick(pack: List[Dict], picks: List[Dict]) -> Dict:
72
+ """Return the card the model recommends from *pack*."""
73
+ if model is None:
74
+ return random.choice(pack)
75
+ return model.predict(pack=pack, picks=picks) # type: ignore[attr-defined]
76
+
77
+
78
+ def fetch_card_image(card_name: str) -> str:
79
+ """Fetch card art URL from Scryfall (normal size)."""
80
+ r = requests.get(
81
+ "https://api.scryfall.com/cards/named", params={"exact": card_name, "format": "json"}
82
+ )
83
+ r.raise_for_status()
84
+ data = r.json()
85
+ if "image_uris" in data:
86
+ return data["image_uris"]["normal"]
87
+ return data["card_faces"][0]["image_uris"]["normal"]
88
+
89
+
90
+ def generate_booster(set_code: str) -> List[Dict]:
91
+ """Return a pseudo-random 15-card booster using Scryfall search."""
92
+ url = f"https://api.scryfall.com/cards/search?q=set%3A{set_code}+is%3Abooster+unique%3Aprints"
93
+ cards: List[Dict] = []
94
+ while url:
95
+ resp = requests.get(url)
96
+ resp.raise_for_status()
97
+ payload = resp.json()
98
+ cards += payload["data"]
99
+ url = payload.get("next_page") if payload.get("has_more") else None
100
+ return random.sample(cards, 15)
101
+
102
+ # -----------------------------------------------------------------------------
103
+ # 3. Streamlit UI
104
+ # -----------------------------------------------------------------------------
105
+
106
+ st.set_page_config(page_title="MTG Draft Assistant", page_icon="πŸƒ")
107
+
108
+ st.title("πŸƒ MTG Draft Assistant")
109
+
110
+ # ---------------- Sidebar -----------------------------------------------------
111
+
112
+ with st.sidebar:
113
+ st.header("Draft setup")
114
+
115
+ supported_sets = None #get_supported_sets()
116
+
117
+ if supported_sets:
118
+ set_code = st.radio("Choose a set to draft", supported_sets, index=0)
119
+ else:
120
+ st.warning(
121
+ "*supported_sets.txt* not found or empty. Using free-text input instead.",
122
+ icon="⚠️",
123
+ )
124
+ set_code = st.text_input("Set code", value="WOE")
125
+
126
+ if st.button("Start new draft", type="primary"):
127
+ st.session_state["pack"] = generate_booster(set_code)
128
+ st.session_state["picks"] = []
129
+
130
+ # ---------------- Session state guards ---------------------------------------
131
+
132
+ st.session_state.setdefault("pack", [])
133
+ st.session_state.setdefault("picks", [])
134
+
135
+ if not st.session_state["pack"]:
136
+ st.info("Choose **Start new draft** in the sidebar to open pack 1.")
137
+ st.stop()
138
+
139
+ pack: List[Dict] = st.session_state["pack"]
140
+ picks: List[Dict] = st.session_state["picks"]
141
+
142
+ st.subheader(f"Pack {len(picks) // 15 + 1} β€” Pick {len(picks) % 15 + 1}")
143
+ suggested = suggest_pick(pack, picks)
144
+
145
+ st.success(f"**Model suggests:** {suggested['name']}")
146
+
147
+ # Display current pack as a 5-column grid of card images
148
+ cols = st.columns(5)
149
+ for idx, card in enumerate(pack):
150
+ col = cols[idx % 5]
151
+ col.image(fetch_card_image(card["name"]), use_column_width=True)
152
+ if col.button(f"Pick {card['name']}", key=f"pick-{idx}"):
153
+ picks.append(card)
154
+ pack.remove(card)
155
+ if not pack: # end of pack β‡’ open a fresh booster
156
+ st.session_state["pack"] = generate_booster(set_code)
157
+ st.experimental_rerun()
158
+
159
+ with st.expander("Current picks", expanded=False):
160
+ st.write("\n".join([c["name"] for c in picks]))