Spaces:
Sleeping
Sleeping
| import uuid | |
| import streamlit as st | |
| import pandas as pd | |
| import scoreboard | |
| import sheets_backend as sheets | |
| from workbook_access import get_workbook | |
| def _init_state(): | |
| if "pt_players" not in st.session_state: | |
| st.session_state.pt_players = [] | |
| if "pt_player_ids" not in st.session_state: | |
| st.session_state.pt_player_ids = {} | |
| if "pt_game_started" not in st.session_state: | |
| st.session_state.pt_game_started = False | |
| if "pt_scores" not in st.session_state: | |
| st.session_state.pt_scores = [] | |
| if "pt_winning_score" not in st.session_state: | |
| st.session_state.pt_winning_score = 100 | |
| if "pt_game_name" not in st.session_state: | |
| st.session_state.pt_game_name = "" | |
| if "pt_session_id" not in st.session_state: | |
| st.session_state.pt_session_id = None | |
| if "pt_game_id" not in st.session_state: | |
| st.session_state.pt_game_id = None | |
| _NEW_GAME_NAME_SENTINEL = "+ New game name" | |
| def _game_name_picker(wb): | |
| past_names = sheets.list_game_names(wb) if wb is not None else [] | |
| if not past_names: | |
| st.session_state.pt_game_name = st.text_input( | |
| "Game name (optional)", | |
| value=st.session_state.pt_game_name, | |
| placeholder="e.g. Catan, Ticket to Ride…", | |
| ) | |
| return | |
| options = past_names + [_NEW_GAME_NAME_SENTINEL] | |
| current = st.session_state.pt_game_name | |
| default_index = options.index(current) if current in options else len(options) - 1 | |
| choice = st.selectbox("Game name (optional)", options, index=default_index) | |
| if choice == _NEW_GAME_NAME_SENTINEL: | |
| st.session_state.pt_game_name = st.text_input( | |
| "New game name", | |
| value="" if current in past_names else current, | |
| placeholder="e.g. Catan, Ticket to Ride…", | |
| ) | |
| else: | |
| st.session_state.pt_game_name = choice | |
| def _render_new_game_tab(wb): | |
| st.subheader("Game Setup") | |
| _game_name_picker(wb) | |
| st.session_state.pt_winning_score = st.number_input( | |
| "Winning score (points to win)", | |
| min_value=1, | |
| value=st.session_state.pt_winning_score, | |
| step=10, | |
| ) | |
| st.subheader("Players") | |
| def _add_player(): | |
| name = st.session_state.pt_new_player.strip() | |
| if name and name not in st.session_state.pt_players: | |
| st.session_state.pt_players.append(name) | |
| if wb is not None: | |
| st.session_state.pt_player_ids[name] = sheets.add_player(wb, name) | |
| st.session_state.pt_new_player = "" | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| st.text_input("Player name", key="pt_new_player") | |
| with col2: | |
| st.write("") | |
| st.write("") | |
| st.button("Add Player", key="pt_add_btn", on_click=_add_player) | |
| if wb is not None: | |
| saved_names = [p["name"] for p in sheets.list_players(wb) if p.get("name")] | |
| remaining = [n for n in saved_names if n not in st.session_state.pt_players] | |
| if remaining: | |
| st.caption("Quick-add from your saved players:") | |
| qcols = st.columns(min(len(remaining), 6)) | |
| for i, name in enumerate(remaining): | |
| if qcols[i % len(qcols)].button(name, key=f"pt_quickadd_{name}"): | |
| st.session_state.pt_players.append(name) | |
| st.session_state.pt_player_ids[name] = sheets.add_player(wb, name) | |
| st.rerun() | |
| if st.session_state.pt_players: | |
| st.markdown("**Players:**") | |
| for i, p in enumerate(st.session_state.pt_players): | |
| col_name, col_del = st.columns([4, 1]) | |
| col_name.write(f"{i + 1}. {p}") | |
| if col_del.button("Remove", key=f"pt_rm_{i}"): | |
| st.session_state.pt_players.pop(i) | |
| st.rerun() | |
| if len(st.session_state.pt_players) >= 2: | |
| if st.button("Start Game", type="primary"): | |
| st.session_state.pt_game_started = True | |
| st.session_state.pt_scores = [] | |
| st.session_state.pt_session_id = uuid.uuid4().hex | |
| if wb is not None: | |
| game_name = st.session_state.pt_game_name or "Points Tracker" | |
| st.session_state.pt_game_id = sheets.get_or_create_game(wb, game_name) | |
| # Covers players already in the list before a workbook was | |
| # available (e.g. added while logged out then logged in). | |
| for name in st.session_state.pt_players: | |
| if name not in st.session_state.pt_player_ids: | |
| st.session_state.pt_player_ids[name] = sheets.add_player(wb, name) | |
| st.rerun() | |
| else: | |
| st.info("Add at least 2 players to start.") | |
| def _render_saved_players_tab(wb): | |
| st.subheader("Saved Players") | |
| if wb is None: | |
| st.info("Log in with Google to see your saved players.") | |
| return | |
| players = sheets.list_players(wb) | |
| if not players: | |
| st.caption("No saved players yet -- add one in the New Game tab.") | |
| return | |
| df = pd.DataFrame(players)[["name", "created_at"]] | |
| st.dataframe(df, hide_index=True, width="stretch") | |
| def _render_game_history_tab(wb): | |
| st.subheader("Game History") | |
| if wb is None: | |
| st.info("Log in with Google to see your game history.") | |
| return | |
| sessions = sheets.list_game_sessions(wb) | |
| if not sessions: | |
| st.caption("No games recorded yet.") | |
| return | |
| for session in sessions: | |
| col_info, col_link = st.columns([4, 1]) | |
| with col_info: | |
| st.write( | |
| f"**{session['game_name']}** -- {session['timestamp']} -- " | |
| f"{session['num_players']} players, {session['num_rounds']} rounds" | |
| ) | |
| with col_link: | |
| if st.button("View", key=f"pt_view_{session['session_id']}"): | |
| st.switch_page( | |
| "app_pages/game_detail.py", | |
| query_params={"session_id": session["session_id"]}, | |
| ) | |
| def _setup_phase(): | |
| wb = get_workbook() | |
| tab_new, tab_players, tab_history = st.tabs(["New Game", "Saved Players", "Game History"]) | |
| with tab_new: | |
| _render_new_game_tab(wb) | |
| with tab_players: | |
| _render_saved_players_tab(wb) | |
| with tab_history: | |
| _render_game_history_tab(wb) | |
| def _game_phase(): | |
| players = st.session_state.pt_players | |
| scores = st.session_state.pt_scores | |
| winning_score = st.session_state.pt_winning_score | |
| game_name = st.session_state.pt_game_name | |
| cumsum, _ = scoreboard.compute_cumulative(players, scores) | |
| sorted_players = sorted(players, key=lambda p: cumsum[p], reverse=True) | |
| scoreboard.render_leaderboard(sorted_players, cumsum) | |
| if game_name: | |
| st.subheader(f"Tracking: {game_name}") | |
| col_reset, col_undo = st.columns([1, 1]) | |
| with col_reset: | |
| if st.button("Reset Game"): | |
| st.session_state.pt_game_started = False | |
| st.session_state.pt_scores = [] | |
| st.rerun() | |
| with col_undo: | |
| if scores and st.button("Undo Last Round"): | |
| st.session_state.pt_scores.pop() | |
| st.rerun() | |
| winners = [p for p in players if cumsum[p] >= winning_score] | |
| if scores: | |
| with st.expander("Scoreboard", expanded=True): | |
| scoreboard.render_scoreboard_transposed(sorted_players, scores, winning_score) | |
| with st.expander("Points progression chart", expanded=False): | |
| scoreboard.render_chart(players, scores) | |
| if winners: | |
| st.success(f"Game over! Winner{'s' if len(winners) > 1 else ''}: **{', '.join(winners)}** reached {winning_score} points!") | |
| st.subheader("Final Standings") | |
| ranked = sorted(cumsum.items(), key=lambda x: x[1], reverse=True) | |
| for i, (player, total) in enumerate(ranked): | |
| medal = ["1st", "2nd", "3rd"][i] if i < 3 else f"{i + 1}th" | |
| st.write(f"**{medal}** - {player}: **{total}** points") | |
| return | |
| next_round = len(scores) + 1 | |
| st.subheader(f"Round {next_round}") | |
| st.caption(f"Target: {winning_score} points to win.") | |
| round_scores = {} | |
| cols = st.columns(len(players)) | |
| for i, player in enumerate(players): | |
| with cols[i]: | |
| current = cumsum[player] | |
| round_scores[player] = st.number_input( | |
| f"{player} ({current} pts)", | |
| value=0, | |
| step=1, | |
| key=f"pt_input_r{next_round}_{player}", | |
| ) | |
| if st.button("Save Round", type="primary", key=f"pt_save_r{next_round}"): | |
| st.session_state.pt_scores.append(round_scores) | |
| wb = get_workbook() | |
| if wb is not None: | |
| for player, score in round_scores.items(): | |
| sheets.record_round( | |
| wb, | |
| game_id=st.session_state.pt_game_id or "", | |
| game_name=game_name or "Points Tracker", | |
| session_id=st.session_state.pt_session_id, | |
| round_number=next_round, | |
| player_id=st.session_state.pt_player_ids.get(player, ""), | |
| player_name=player, | |
| score=score, | |
| ) | |
| st.rerun() | |
| # Page entry point | |
| st.title("Points Tracker") | |
| _init_state() | |
| if not st.session_state.pt_game_started: | |
| _setup_phase() | |
| else: | |
| _game_phase() | |