import altair as alt import pandas as pd import streamlit as st def compute_cumulative(players, scores): cumsum = {p: 0 for p in players} history = [] for round_data in scores: for p in players: cumsum[p] += round_data.get(p, 0) history.append(dict(cumsum)) return cumsum, history def render_scoreboard_transposed(players, scores, winning_score): header_cols = ["Player"] + [f"R{i + 1}" for i in range(len(scores))] md = "| " + " | ".join(header_cols) + " |\n" md += "| " + " | ".join(["---"] * len(header_cols)) + " |\n" for p in players: row = [f"**{p}**"] running = 0 for round_data in scores: pts = round_data.get(p, 0) prev = running running += pts change = running - prev if change > 0: indicator = f' ▲ +{change}' elif change < 0: indicator = f' ▼ {change}' else: indicator = "" winner_flag = " ★" if running >= winning_score else "" row.append(f"**{running}**{indicator}{winner_flag}") md += "| " + " | ".join(row) + " |\n" st.markdown(md, unsafe_allow_html=True) def render_leaderboard(players, cumsum): st.subheader("🏆 Leaderboard") if not players: return medals = ["🥇", "🥈", "🥉"] cols = st.columns(len(players)) for i, p in enumerate(players): label = f"{medals[i]} {p}" if i < len(medals) else f"#{i + 1} {p}" with cols[i]: st.metric(label, cumsum[p]) def render_chart(players, scores): if not scores: return cumsum = {p: 0 for p in players} rows = [{"Round": 0, **{p: 0 for p in players}}] for i, round_data in enumerate(scores): for p in players: cumsum[p] += round_data.get(p, 0) rows.append({"Round": i + 1, **dict(cumsum)}) df = pd.DataFrame(rows) df_long = df.melt(id_vars="Round", var_name="Player", value_name="Points") chart = ( alt.Chart(df_long) .mark_line(point=True) .encode( x=alt.X("Round:Q", axis=alt.Axis(tickMinStep=1)), y=alt.Y("Points:Q"), color=alt.Color("Player:N"), tooltip=["Round", "Player", "Points"], ) .properties(height=300) .interactive() ) st.altair_chart(chart, use_container_width=True) def rows_to_players_and_scores(rows: list[dict]) -> tuple[list[str], list[dict]]: """Converts raw games_played rows (one row per player per round) into the same (players, scores) shape the live game view uses, so historical sessions can reuse the exact same rendering functions. Uses player_name (not player_id) since that's guaranteed present on both pre- and post-migration rows -- id lookups are for future robustness, not a requirement for basic display. """ players_seen: list[str] = [] by_round: dict[int, dict[str, int]] = {} for row in rows: name = row.get("player_name") or row.get("player_id") or "Unknown" if name not in players_seen: players_seen.append(name) try: rnd = int(row.get("round_number") or 0) except (TypeError, ValueError): rnd = 0 try: score = int(row.get("score") or 0) except (TypeError, ValueError): score = 0 by_round.setdefault(rnd, {})[name] = score scores = [by_round[r] for r in sorted(by_round)] return players_seen, scores