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()