Skip to content

Commit 26c2869

Browse files
committed
Button game added!
Press a button, have a chance of dying that increases as your number gets bigger, see cool stats!
1 parent 287ed0b commit 26c2869

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed

Button.py

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
import tkinter as tk
2+
from tkinter import ttk
3+
import random
4+
import time
5+
import threading
6+
import matplotlib.pyplot as plt
7+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
8+
from collections import Counter
9+
import numpy as np
10+
11+
class ButtonGame:
12+
def __init__(self, root):
13+
self.root = root
14+
self.root.title("Button Game")
15+
self.root.geometry("900x600") # Wider window
16+
17+
# Game variables
18+
self.current_count = 0
19+
self.high_score = 0
20+
self.scores = []
21+
self.score_counter = Counter() # For efficient counting
22+
self.avg_score = 0
23+
self.total_presses = 0
24+
self.auto_press_active = False
25+
self.auto_press_thread = None
26+
self.last_update_time = 0
27+
self.update_interval = 500 # Minimum ms between graph updates
28+
29+
# Create main layout with two columns
30+
self.left_frame = tk.Frame(root)
31+
self.left_frame.pack(side=tk.LEFT, padx=20, pady=10, fill=tk.BOTH)
32+
33+
self.right_frame = tk.Frame(root)
34+
self.right_frame.pack(side=tk.RIGHT, padx=20, pady=10, fill=tk.BOTH, expand=True)
35+
36+
# Canvas for circular button (left frame)
37+
self.canvas = tk.Canvas(self.left_frame, width=150, height=150, highlightthickness=0)
38+
self.canvas.pack(pady=20)
39+
40+
# Create circular button
41+
self.button_bg = self.canvas.create_oval(10, 10, 140, 140, fill="#4CAF50", outline="#2E7D32", width=2)
42+
self.button_text = self.canvas.create_text(75, 75, text="0", font=("Arial", 24, "bold"), fill="white")
43+
44+
# Bind click event to canvas
45+
self.canvas.tag_bind(self.button_bg, "<Button-1>", lambda e: self.press_button())
46+
self.canvas.tag_bind(self.button_text, "<Button-1>", lambda e: self.press_button())
47+
48+
# Score display frame
49+
score_frame = tk.Frame(self.left_frame)
50+
score_frame.pack(pady=15, fill=tk.X)
51+
52+
tk.Label(score_frame, text="High Score:", font=("Arial", 12)).grid(row=0, column=0, padx=5, sticky=tk.W)
53+
self.high_score_label = tk.Label(score_frame, text="0", font=("Arial", 12, "bold"))
54+
self.high_score_label.grid(row=0, column=1, padx=5, sticky=tk.W)
55+
56+
tk.Label(score_frame, text="Average Score:", font=("Arial", 12)).grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
57+
self.avg_score_label = tk.Label(score_frame, text="0.0", font=("Arial", 12, "bold"))
58+
self.avg_score_label.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
59+
60+
# Total presses display
61+
total_frame = tk.Frame(self.left_frame)
62+
total_frame.pack(pady=10, fill=tk.X)
63+
64+
tk.Label(total_frame, text="Total Button Presses:", font=("Arial", 12)).grid(row=0, column=0, padx=5, sticky=tk.W)
65+
self.total_presses_label = tk.Label(total_frame, text="0", font=("Arial", 12, "bold"))
66+
self.total_presses_label.grid(row=0, column=1, padx=5, sticky=tk.W)
67+
68+
# Auto-press buttons frame
69+
auto_frame = tk.LabelFrame(self.left_frame, text="Auto Press Options", font=("Arial", 10, "bold"))
70+
auto_frame.pack(pady=15, fill=tk.X)
71+
72+
for i, speed in enumerate([10, 100, 1000]):
73+
tk.Button(
74+
auto_frame,
75+
text=f"{speed}/s",
76+
bg="#3498db",
77+
fg="white",
78+
command=lambda s=speed: self.toggle_auto_press(s)
79+
).grid(row=0, column=i, padx=10, pady=10, sticky=tk.W)
80+
81+
self.auto_status_label = tk.Label(self.left_frame, text="Auto Press: Off", font=("Arial", 10, "italic"))
82+
self.auto_status_label.pack(pady=5)
83+
84+
# Stop button (initially hidden)
85+
self.stop_button = tk.Button(
86+
self.left_frame,
87+
text="STOP Auto Press",
88+
bg="#e74c3c",
89+
fg="white",
90+
command=self.stop_auto_press
91+
)
92+
93+
# Title for the graph
94+
tk.Label(self.right_frame, text="Score Distribution", font=("Arial", 14, "bold")).pack(pady=5, anchor=tk.W)
95+
96+
# Create the matplotlib figure (right frame)
97+
self.fig, self.ax = plt.subplots(figsize=(7, 4.5))
98+
self.fig.patch.set_facecolor('#F0F0F0') # Match Tkinter background
99+
self.ax.set_title('Score Frequency', fontsize=12)
100+
self.ax.set_xlabel('Score Value', fontsize=10)
101+
self.ax.set_ylabel('Frequency (Count)', fontsize=10)
102+
self.ax.grid(True, linestyle='--', alpha=0.7, axis='y')
103+
104+
# Setup the canvas - using double-buffered rendering can help with performance
105+
self.canvas_graph = FigureCanvasTkAgg(self.fig, master=self.right_frame)
106+
self.canvas_widget = self.canvas_graph.get_tk_widget()
107+
self.canvas_widget.pack(fill=tk.BOTH, expand=True)
108+
109+
# Add stats display below graph
110+
self.stats_frame = tk.Frame(self.right_frame)
111+
self.stats_frame.pack(pady=10, fill=tk.X)
112+
113+
# Create labels for stats in a grid (5 columns)
114+
self.stat_labels = {}
115+
stats = [
116+
('Games', 'Games: 0'),
117+
('High', 'High: 0'),
118+
('Avg', 'Avg: 0.0'),
119+
('Median', 'Median: 0'),
120+
('Mode', 'Mode: N/A')
121+
]
122+
123+
for i, (key, text) in enumerate(stats):
124+
lbl = tk.Label(self.stats_frame, text=text, font=("Arial", 10),
125+
bg="#f0f0f0", relief=tk.GROOVE, padx=5, pady=3)
126+
lbl.grid(row=0, column=i, padx=5, sticky=tk.W+tk.E)
127+
self.stat_labels[key] = lbl
128+
self.stats_frame.grid_columnconfigure(i, weight=1)
129+
130+
# Pre-render empty graph
131+
self.update_graph_initial()
132+
133+
# Create bars collection for efficient updates
134+
self.bars = None
135+
self.x_data = []
136+
self.y_data = []
137+
138+
def update_graph_initial(self):
139+
"""Initial graph setup with placeholder text."""
140+
self.ax.text(0.5, 0.5, 'Play games to see your stats!',
141+
horizontalalignment='center', verticalalignment='center',
142+
transform=self.ax.transAxes, fontsize=12)
143+
self.ax.set_xlim(0, 10)
144+
self.ax.set_ylim(0, 10)
145+
self.fig.tight_layout()
146+
self.canvas_graph.draw()
147+
148+
def press_button(self):
149+
# Increment counters
150+
self.current_count += 1
151+
self.total_presses += 1
152+
153+
# Update UI
154+
self.canvas.itemconfig(self.button_text, text=str(self.current_count))
155+
self.total_presses_label.config(text=str(self.total_presses))
156+
157+
# Check if reset occurs based on probability
158+
if random.randint(1, 100) <= self.current_count:
159+
# Game over - update stats
160+
if self.current_count > self.high_score:
161+
self.high_score = self.current_count
162+
self.high_score_label.config(text=str(self.high_score))
163+
164+
# Update score collection
165+
self.scores.append(self.current_count)
166+
self.score_counter[self.current_count] += 1
167+
168+
# Calculate average
169+
self.avg_score = sum(self.scores) / len(self.scores)
170+
self.avg_score_label.config(text=f"{self.avg_score:.1f}")
171+
172+
# Reset counter
173+
self.current_count = 0
174+
self.canvas.itemconfig(self.button_text, text="0")
175+
176+
# Flash the button to indicate reset
177+
self.flash_button()
178+
179+
# Update stats panel immediately
180+
self.update_stats_panel()
181+
182+
# Check if enough time has passed for graph update
183+
current_time = time.time() * 1000 # Convert to ms
184+
if current_time - self.last_update_time > self.update_interval:
185+
self.update_graph(force_redraw=True)
186+
self.last_update_time = current_time
187+
else:
188+
# Schedule a deferred update
189+
self.root.after(self.update_interval, lambda: self.update_graph(force_redraw=False))
190+
191+
def update_stats_panel(self):
192+
"""Update just the statistics labels without redrawing the graph."""
193+
# Calculate stats
194+
median_score = sorted(self.scores)[len(self.scores)//2] if self.scores else 0
195+
most_common = self.score_counter.most_common(1)[0] if self.scores else (0, 0)
196+
197+
# Update labels
198+
self.stat_labels['Games'].config(text=f"Games: {len(self.scores)}")
199+
self.stat_labels['High'].config(text=f"High: {self.high_score}")
200+
self.stat_labels['Avg'].config(text=f"Avg: {self.avg_score:.1f}")
201+
self.stat_labels['Median'].config(text=f"Median: {median_score}")
202+
self.stat_labels['Mode'].config(text=f"Mode: {most_common[0]} ({most_common[1]}x)")
203+
204+
def update_graph(self, force_redraw=False):
205+
"""Update the graph with current score distribution."""
206+
try:
207+
if not self.scores:
208+
return
209+
210+
# Get sorted score counts
211+
sorted_items = sorted(self.score_counter.items())
212+
new_x = [item[0] for item in sorted_items]
213+
new_y = [item[1] for item in sorted_items]
214+
215+
# Check if data structure changed (requiring full redraw)
216+
structure_changed = new_x != self.x_data
217+
218+
# Store current data
219+
self.x_data = new_x
220+
self.y_data = new_y
221+
222+
# If first time or structure changed, do a full redraw
223+
if self.bars is None or structure_changed or force_redraw:
224+
self.ax.clear()
225+
self.ax.set_title('Score Frequency', fontsize=12)
226+
self.ax.set_xlabel('Score Value', fontsize=10)
227+
self.ax.set_ylabel('Frequency (Count)', fontsize=10)
228+
self.ax.grid(True, linestyle='--', alpha=0.7, axis='y')
229+
230+
# For color gradient
231+
norm = plt.Normalize(min(new_y), max(new_y))
232+
colors = plt.cm.viridis(norm(new_y))
233+
234+
# Create new bars
235+
self.bars = self.ax.bar(new_x, new_y, color=colors, alpha=0.8, width=0.8)
236+
237+
# Add value labels
238+
for bar in self.bars:
239+
height = bar.get_height()
240+
self.ax.text(bar.get_x() + bar.get_width()/2., height + 0.1,
241+
f'{int(height)}', ha='center', va='bottom', fontsize=9)
242+
243+
# Add average line
244+
self.avg_line = self.ax.axvline(x=self.avg_score, color='#e74c3c',
245+
linestyle='--', label=f'Avg: {self.avg_score:.1f}')
246+
247+
# Set limits
248+
max_y = max(new_y)
249+
self.ax.set_ylim(0, max(5, max_y * 1.2))
250+
self.ax.set_xlim(min(new_x) - 0.5, max(new_x) + 0.5)
251+
252+
# Set x-axis ticks
253+
self.ax.set_xticks(new_x)
254+
255+
# Add legend
256+
self.ax.legend(loc='upper right')
257+
258+
# Adjust layout
259+
self.fig.tight_layout()
260+
261+
# Full redraw
262+
self.canvas_graph.draw()
263+
else:
264+
# Efficient update - just update the heights and average line
265+
for bar, new_height in zip(self.bars, new_y):
266+
bar.set_height(new_height)
267+
268+
# Update average line
269+
self.avg_line.set_xdata([self.avg_score, self.avg_score])
270+
self.avg_line.set_label(f'Avg: {self.avg_score:.1f}')
271+
272+
# Update legend
273+
self.ax.legend(loc='upper right')
274+
275+
# Use blit for faster rendering (only update changed parts)
276+
self.canvas_graph.draw_idle()
277+
278+
except Exception as e:
279+
print(f"Error updating graph: {e}")
280+
281+
def flash_button(self):
282+
original_fill = self.canvas.itemcget(self.button_bg, "fill")
283+
self.canvas.itemconfig(self.button_bg, fill="#e74c3c") # Red color
284+
self.root.after(200, lambda: self.canvas.itemconfig(self.button_bg, fill=original_fill))
285+
286+
def toggle_auto_press(self, clicks_per_second):
287+
if self.auto_press_active:
288+
self.stop_auto_press()
289+
else:
290+
self.auto_press_active = True
291+
self.auto_status_label.config(text=f"Auto Press: {clicks_per_second}/s")
292+
self.stop_button.pack(pady=5)
293+
294+
# Limit actual click rate to prevent overwhelming the UI
295+
effective_rate = min(clicks_per_second, 50)
296+
delay = 1.0 / effective_rate
297+
298+
# Adjust graph update interval based on click speed
299+
if clicks_per_second > 50:
300+
self.update_interval = 1000 # ms between updates for fast clicking
301+
302+
self.auto_press_thread = threading.Thread(
303+
target=self.auto_press_loop,
304+
args=(delay, clicks_per_second, effective_rate),
305+
daemon=True
306+
)
307+
self.auto_press_thread.start()
308+
309+
def auto_press_loop(self, delay, requested_rate, actual_rate):
310+
click_batch = max(1, round(requested_rate / actual_rate))
311+
312+
while self.auto_press_active:
313+
for _ in range(click_batch):
314+
if not self.auto_press_active:
315+
break
316+
self.root.after_idle(self.press_button)
317+
time.sleep(delay)
318+
319+
def stop_auto_press(self):
320+
self.auto_press_active = False
321+
self.auto_status_label.config(text="Auto Press: Off")
322+
self.stop_button.pack_forget()
323+
self.update_interval = 500 # Reset to default update interval
324+
325+
if __name__ == "__main__":
326+
root = tk.Tk()
327+
game = ButtonGame(root)
328+
root.mainloop()

0 commit comments

Comments
 (0)