File size: 9,358 Bytes
5d4959a
 
2377378
 
 
69c9e26
5d4959a
69c9e26
5d4959a
2377378
 
 
 
69c9e26
 
2377378
 
 
 
 
 
 
 
5d4959a
 
69c9e26
 
2377378
 
76b596f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2377378
 
76b596f
2377378
 
 
 
 
 
 
 
 
 
 
 
 
 
5d4959a
69c9e26
2377378
 
 
 
 
 
 
 
 
 
5d4959a
 
 
 
 
 
 
 
 
69c9e26
5d4959a
 
2377378
 
 
 
 
 
 
 
 
 
 
 
 
5d4959a
69c9e26
 
 
 
 
 
 
 
2377378
 
 
 
 
76b596f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69c9e26
 
76b596f
 
69c9e26
 
 
 
 
 
 
 
 
 
 
 
 
76b596f
 
 
69c9e26
76b596f
 
 
 
 
 
 
 
 
 
2377378
 
 
 
 
 
69c9e26
7e1ff1d
 
69c9e26
7e1ff1d
2377378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e1ff1d
69c9e26
2377378
 
69c9e26
2377378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69c9e26
5d4959a
 
 
 
69c9e26
5d4959a
 
 
69c9e26
5d4959a
 
 
2377378
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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()