RandomCatLover commited on
Commit
2377378
·
1 Parent(s): f7b1a7b

new tracker

Browse files
Files changed (3) hide show
  1. app.py +2 -1
  2. pages/home.py +1 -0
  3. pages/points_tracker.py +204 -0
app.py CHANGED
@@ -5,6 +5,7 @@ st.set_page_config(page_title="Board Game Tracker", layout="wide")
5
  home_page = st.Page("pages/home.py", title="Home", url_path="home", default=True)
6
  skull_king_page = st.Page("pages/skull_king.py", title="Skull King", url_path="skull_king")
7
  dungeon_draft_page = st.Page("pages/dungeon_draft.py", title="Dungeon Draft", url_path="dungeon_draft")
 
8
 
9
- pg = st.navigation([home_page, skull_king_page, dungeon_draft_page])
10
  pg.run()
 
5
  home_page = st.Page("pages/home.py", title="Home", url_path="home", default=True)
6
  skull_king_page = st.Page("pages/skull_king.py", title="Skull King", url_path="skull_king")
7
  dungeon_draft_page = st.Page("pages/dungeon_draft.py", title="Dungeon Draft", url_path="dungeon_draft")
8
+ points_tracker_page = st.Page("pages/points_tracker.py", title="Points Tracker", url_path="points_tracker")
9
 
10
+ pg = st.navigation([home_page, skull_king_page, dungeon_draft_page, points_tracker_page])
11
  pg.run()
pages/home.py CHANGED
@@ -17,6 +17,7 @@ no data collection. Your game data lives in your browser session only.
17
  |------|-------------|
18
  | **Skull King** | A trick-taking pirate card game for 2-6 players. Track bids and scores across up to 10 rounds. |
19
  | **Dungeon Draft** | A dungeon-crawling card drafting game. Track money, damage, and victory points with full transaction support. |
 
20
 
21
  ---
22
 
 
17
  |------|-------------|
18
  | **Skull King** | A trick-taking pirate card game for 2-6 players. Track bids and scores across up to 10 rounds. |
19
  | **Dungeon Draft** | A dungeon-crawling card drafting game. Track money, damage, and victory points with full transaction support. |
20
+ | **Points Tracker** | Universal tracker for any points-based game. Set a winning score, enter round-by-round points, and view a cumulative progression chart. |
21
 
22
  ---
23
 
pages/points_tracker.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import altair as alt
4
+
5
+
6
+ def _init_state():
7
+ if "pt_players" not in st.session_state:
8
+ st.session_state.pt_players = []
9
+ if "pt_game_started" not in st.session_state:
10
+ st.session_state.pt_game_started = False
11
+ if "pt_scores" not in st.session_state:
12
+ st.session_state.pt_scores = []
13
+ if "pt_winning_score" not in st.session_state:
14
+ st.session_state.pt_winning_score = 100
15
+ if "pt_game_name" not in st.session_state:
16
+ st.session_state.pt_game_name = ""
17
+
18
+
19
+ def _setup_phase():
20
+ st.subheader("Game Setup")
21
+
22
+ st.session_state.pt_game_name = st.text_input(
23
+ "Game name (optional)",
24
+ value=st.session_state.pt_game_name,
25
+ placeholder="e.g. Catan, Ticket to Ride…",
26
+ )
27
+
28
+ st.session_state.pt_winning_score = st.number_input(
29
+ "Winning score (points to win)",
30
+ min_value=1,
31
+ value=st.session_state.pt_winning_score,
32
+ step=10,
33
+ )
34
+
35
+ st.subheader("Players")
36
+
37
+ def _add_player():
38
+ name = st.session_state.pt_new_player.strip()
39
+ if name and name not in st.session_state.pt_players:
40
+ st.session_state.pt_players.append(name)
41
+ st.session_state.pt_new_player = ""
42
+
43
+ col1, col2 = st.columns([3, 1])
44
+ with col1:
45
+ st.text_input("Player name", key="pt_new_player")
46
+ with col2:
47
+ st.write("")
48
+ st.write("")
49
+ st.button("Add Player", key="pt_add_btn", on_click=_add_player)
50
+
51
+ if st.session_state.pt_players:
52
+ st.markdown("**Players:**")
53
+ for i, p in enumerate(st.session_state.pt_players):
54
+ col_name, col_del = st.columns([4, 1])
55
+ col_name.write(f"{i + 1}. {p}")
56
+ if col_del.button("Remove", key=f"pt_rm_{i}"):
57
+ st.session_state.pt_players.pop(i)
58
+ st.rerun()
59
+
60
+ if len(st.session_state.pt_players) >= 2:
61
+ if st.button("Start Game", type="primary"):
62
+ st.session_state.pt_game_started = True
63
+ st.session_state.pt_scores = []
64
+ st.rerun()
65
+ else:
66
+ st.info("Add at least 2 players to start.")
67
+
68
+
69
+ def _compute_cumulative(players, scores):
70
+ cumsum = {p: 0 for p in players}
71
+ history = []
72
+ for round_data in scores:
73
+ for p in players:
74
+ cumsum[p] += round_data.get(p, 0)
75
+ history.append(dict(cumsum))
76
+ return cumsum, history
77
+
78
+
79
+ def _render_scoreboard(players, scores, winning_score):
80
+ cumsum = {p: 0 for p in players}
81
+
82
+ header_cols = ["Round"] + players
83
+ md = "| " + " | ".join(header_cols) + " |\n"
84
+ md += "| " + " | ".join(["---"] * len(header_cols)) + " |\n"
85
+
86
+ for i, round_data in enumerate(scores):
87
+ row = [f"**{i + 1}**"]
88
+ for p in players:
89
+ pts = round_data.get(p, 0)
90
+ prev = cumsum[p]
91
+ cumsum[p] += pts
92
+ change = cumsum[p] - prev
93
+ if change > 0:
94
+ indicator = f' <span style="color:green">&#9650; +{change}</span>'
95
+ elif change < 0:
96
+ indicator = f' <span style="color:red">&#9660; {change}</span>'
97
+ else:
98
+ indicator = ""
99
+ winner_flag = " ★" if cumsum[p] >= winning_score else ""
100
+ row.append(f"**{cumsum[p]}**{indicator}{winner_flag}")
101
+ md += "| " + " | ".join(row) + " |\n"
102
+
103
+ st.markdown(md, unsafe_allow_html=True)
104
+
105
+
106
+ def _render_chart(players, scores):
107
+ if not scores:
108
+ return
109
+
110
+ cumsum = {p: 0 for p in players}
111
+ rows = [{"Round": 0, **{p: 0 for p in players}}]
112
+ for i, round_data in enumerate(scores):
113
+ for p in players:
114
+ cumsum[p] += round_data.get(p, 0)
115
+ rows.append({"Round": i + 1, **dict(cumsum)})
116
+
117
+ df = pd.DataFrame(rows)
118
+ df_long = df.melt(id_vars="Round", var_name="Player", value_name="Points")
119
+
120
+ chart = (
121
+ alt.Chart(df_long)
122
+ .mark_line(point=True)
123
+ .encode(
124
+ x=alt.X("Round:Q", axis=alt.Axis(tickMinStep=1)),
125
+ y=alt.Y("Points:Q"),
126
+ color=alt.Color("Player:N"),
127
+ tooltip=["Round", "Player", "Points"],
128
+ )
129
+ .properties(height=300)
130
+ .interactive()
131
+ )
132
+
133
+ st.altair_chart(chart, use_container_width=True)
134
+
135
+
136
+ def _game_phase():
137
+ players = st.session_state.pt_players
138
+ scores = st.session_state.pt_scores
139
+ winning_score = st.session_state.pt_winning_score
140
+ game_name = st.session_state.pt_game_name
141
+
142
+ if game_name:
143
+ st.subheader(f"Tracking: {game_name}")
144
+
145
+ col_reset, col_undo = st.columns([1, 1])
146
+ with col_reset:
147
+ if st.button("Reset Game"):
148
+ st.session_state.pt_game_started = False
149
+ st.session_state.pt_players = []
150
+ st.session_state.pt_scores = []
151
+ st.rerun()
152
+ with col_undo:
153
+ if scores and st.button("Undo Last Round"):
154
+ st.session_state.pt_scores.pop()
155
+ st.rerun()
156
+
157
+ cumsum, _ = _compute_cumulative(players, scores)
158
+ winners = [p for p in players if cumsum[p] >= winning_score]
159
+
160
+ if scores:
161
+ st.subheader("Scoreboard")
162
+ _render_scoreboard(players, scores, winning_score)
163
+
164
+ with st.expander("Points progression chart", expanded=False):
165
+ _render_chart(players, scores)
166
+
167
+ if winners:
168
+ st.success(f"Game over! Winner{'s' if len(winners) > 1 else ''}: **{', '.join(winners)}** reached {winning_score} points!")
169
+ st.subheader("Final Standings")
170
+ ranked = sorted(cumsum.items(), key=lambda x: x[1], reverse=True)
171
+ for i, (player, total) in enumerate(ranked):
172
+ medal = ["1st", "2nd", "3rd"][i] if i < 3 else f"{i + 1}th"
173
+ st.write(f"**{medal}** - {player}: **{total}** points")
174
+ return
175
+
176
+ next_round = len(scores) + 1
177
+ st.subheader(f"Round {next_round}")
178
+ st.caption(f"Target: {winning_score} points to win.")
179
+
180
+ round_scores = {}
181
+ cols = st.columns(len(players))
182
+ for i, player in enumerate(players):
183
+ with cols[i]:
184
+ current = cumsum[player]
185
+ round_scores[player] = st.number_input(
186
+ f"{player} ({current} pts)",
187
+ value=0,
188
+ step=1,
189
+ key=f"pt_input_r{next_round}_{player}",
190
+ )
191
+
192
+ if st.button("Save Round", type="primary", key=f"pt_save_r{next_round}"):
193
+ st.session_state.pt_scores.append(round_scores)
194
+ st.rerun()
195
+
196
+
197
+ # Page entry point
198
+ st.title("Points Tracker")
199
+ _init_state()
200
+
201
+ if not st.session_state.pt_game_started:
202
+ _setup_phase()
203
+ else:
204
+ _game_phase()