Spaces:
Sleeping
Sleeping
| 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' <span style="color:green">▲ +{change}</span>' | |
| elif change < 0: | |
| indicator = f' <span style="color:red">▼ {change}</span>' | |
| 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 | |