# 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"", 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('
', unsafe_allow_html=True) st.markdown(f"

Target: JA${ss.mm_target}

", unsafe_allow_html=True) st.markdown(f"
JA${ss.mm_total}
", 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"${v}" for v in ss.mm_selected]) st.markdown(f"
{chips}
", unsafe_allow_html=True) else: st.markdown("
Selected money will appear here
", 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("
", unsafe_allow_html=True) # Money Collection st.markdown("

Money Collection

", 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"""

🏆 Stats

Current Level{ss.mm_level}
Total XP{ss.mm_xp}
Matches Made{ss.mm_matches}
""", unsafe_allow_html=True, ) st.markdown( """

How to Play

  1. Look at the target amount
  2. Click coins and notes to add them
  3. Match the target exactly to earn XP
  4. Level up with each successful match
""", unsafe_allow_html=True, )