File size: 7,212 Bytes
0aa6283 | 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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | # phase\Student_view\games\MoneyMatch.py
import time
import os
import random
from pathlib import Path
import streamlit as st
from utils import db as dbapi
import time
from utils import db as db_util
from utils import api
USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1"
# ---------- paths ----------
PROJECT_ROOT = Path(__file__).resolve().parents[3]
def _asset(*parts: str) -> str:
# JMD image path
return str((PROJECT_ROOT / "assets" / "images" / Path(*parts)).resolve())
def _safe_image(path: str, *, caption: str = ""):
if not os.path.exists(path):
st.warning(f"Image not found: {Path(path).name}. Button still works.")
return False
st.image(path, use_container_width=True, caption=caption)
return True
# ---------- state helpers ----------
def _init_state():
ss = st.session_state
if "mm_level" not in ss: ss.mm_level = 1
if "mm_xp" not in ss: ss.mm_xp = 0
if "mm_matches" not in ss: ss.mm_matches = 0
if "mm_target" not in ss: ss.mm_target = random.randint(7, 10000) # randon goal generator
if "mm_selected" not in ss: ss.mm_selected = []
if "mm_total" not in ss: ss.mm_total = 0
if "mm_start_ts" not in ss: ss.mm_start_ts = time.perf_counter()
if "mm_saved" not in ss: ss.mm_saved = False
def _reset_round(new_target: int | None = None):
ss = st.session_state
ss.mm_selected = []
ss.mm_total = 0
ss.mm_target = new_target if new_target is not None else random.randint(7, 10000)
ss.mm_start_ts = time.perf_counter()
ss.mm_saved = False
def _award_xp(gained: int):
ss = st.session_state
ss.mm_xp += gained
ss.mm_matches += 1
while ss.mm_xp >= ss.mm_level * 100:
ss.mm_level += 1
def _persist_success(gained_xp: int):
user = st.session_state.get("user") or {}
user_id = int(user.get("user_id", 0))
if not user_id:
st.error("Not saving. No logged-in user_id in session.")
return
payload = dict(
user_id=user_id,
target=int(st.session_state.mm_target),
total=int(st.session_state.mm_total),
elapsed_ms=int((time.perf_counter() - st.session_state.mm_start_ts) * 1000),
matched=True,
gained_xp=int(gained_xp),
)
try:
if USE_LOCAL_DB and hasattr(dbapi, "record_money_match_play"):
# direct DB mode
dbapi.record_money_match_play(**payload)
st.toast(f"Saved to DB +{gained_xp} XP")
else:
# backend mode (DISABLE_DB=1)
api.record_money_match_play(**payload)
st.toast(f"Saved via backend +{gained_xp} XP")
st.session_state.mm_saved = True
except Exception as e:
st.error(f"Save failed: {e}")
# --- CSS injection (run every render) ---
def _inject_css():
css_path = PROJECT_ROOT / "assets" / "styles.css"
try:
css = css_path.read_text(encoding="utf-8")
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
except Exception:
# don't crash the page because of styling
pass
# ---------- denominations ----------
DENOMS = [
("JA$1", 1, _asset("jmd", "jmd_1.jpeg")),
("JA$5", 5, _asset("jmd", "jmd_5.jpeg")),
("JA$10", 10, _asset("jmd", "jmd_10.jpeg")),
("JA$20", 20, _asset("jmd", "jmd_20.jpeg")),
("JA$50", 50, _asset("jmd", "jmd_50.jpg")),
("JA$100", 100, _asset("jmd", "jmd_100.jpg")),
("JA$500", 500, _asset("jmd", "jmd_500.jpg")),
("JA$1000", 1000, _asset("jmd", "jmd_1000.jpeg")),
("JA$2000", 2000, _asset("jmd", "jmd_2000.jpeg")),
("JA$5000", 5000, _asset("jmd", "jmd_5000.jpeg")),
]
# ---------- main ----------
def show_page():
_init_state()
_inject_css() # <- keep this here so it runs on every rerun
ss = st.session_state
if st.button("← Back to Games"):
ss.current_game = None
st.rerun()
st.title("Money Match Challenge")
left, right = st.columns([1.75, 1])
with left:
st.markdown('<div class="mm-card">', unsafe_allow_html=True)
st.markdown(f"<h3>Target: <span class='mm-target'>JA${ss.mm_target}</span></h3>", unsafe_allow_html=True)
st.markdown(f"<div class='mm-total'>JA${ss.mm_total}</div>", unsafe_allow_html=True)
ratio = min(ss.mm_total / ss.mm_target, 1.0) if ss.mm_target else 0
st.progress(ratio)
diff = ss.mm_target - ss.mm_total
need_text = "Perfect match. Click Next round." if diff == 0 else (f"Need JA${diff} more" if diff > 0 else f"Overshot by JA${abs(diff)}")
st.caption(need_text)
# autosave stats if perfect match
if diff == 0 and not ss.mm_saved:
gained = 10
_persist_success(gained)
_award_xp(gained)
ss.mm_saved = True
# tray
if ss.mm_selected:
chips = " ".join([f"<span class='mm-chip'>${v}</span>" for v in ss.mm_selected])
st.markdown(f"<div class='mm-tray'>{chips}</div>", unsafe_allow_html=True)
else:
st.markdown("<div class='mm-tray mm-empty'>Selected money will appear here</div>", unsafe_allow_html=True)
c1, c2 = st.columns([1,1])
with c1:
if st.button("⟲ Reset"):
_reset_round(ss.mm_target)
st.rerun()
with c2:
if ss.mm_total == ss.mm_target:
if st.button("Next round ▶"):
gained = 10
# avoid double insert when autosave
if not ss.mm_saved:
_persist_success(gained)
_award_xp(gained)
_reset_round()
st.rerun()
st.markdown("</div>", unsafe_allow_html=True)
# Money Collection
st.markdown("<h4>Money Collection</h4>", unsafe_allow_html=True)
grid_cols = st.columns(4)
for i, (label, value, img) in enumerate(DENOMS):
with grid_cols[i % 4]:
_safe_image(img, caption=label)
if st.button(label, key=f"mm_add_{value}"):
ss.mm_selected.append(value)
ss.mm_total += value
st.rerun()
with right:
st.markdown(
f"""
<div class="mm-side-card">
<h4>🏆 Stats</h4>
<div class="mm-metric"><span>Current Level</span><b>{ss.mm_level}</b></div>
<div class="mm-metric"><span>Total XP</span><b>{ss.mm_xp}</b></div>
<div class="mm-metric"><span>Matches Made</span><b>{ss.mm_matches}</b></div>
</div>
""",
unsafe_allow_html=True,
)
st.markdown(
"""
<div class="mm-side-card">
<h4>How to Play</h4>
<ol class="mm-howto">
<li>Look at the target amount</li>
<li>Click coins and notes to add them</li>
<li>Match the target exactly to earn XP</li>
<li>Level up with each successful match</li>
</ol>
</div>
""",
unsafe_allow_html=True,
)
|