import gradio as gr import random import threading import time from datetime import datetime FLIP_LOG = "flips.md" is_flipping = False start_time = None heads_count = 0 tails_count = 0 flip_lock = threading.Lock() # Make sure the flip log exists with open(FLIP_LOG, "a") as f: f.write("## Eternal Coin Flips\n\n") # Add header once if not present # Flip forever in the background def flip_forever(): global heads_count, tails_count while True: result = random.choice(["Heads", "Tails"]) timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") # Update counters thread-safely with flip_lock: if result == "Heads": heads_count += 1 else: tails_count += 1 with open(FLIP_LOG, "a") as f: f.write(f"- {timestamp}: {result}\n") time.sleep(1) # Trigger flipping once def start_flipping(): global is_flipping, start_time if not is_flipping: is_flipping = True start_time = datetime.utcnow() threading.Thread(target=flip_forever, daemon=True).start() return "Started flipping! No going back now." return "Already flipping." # Get runtime duration def get_runtime(): if start_time is None: return "Not started yet" current_time = datetime.utcnow() runtime = current_time - start_time # Format runtime nicely total_seconds = int(runtime.total_seconds()) hours = total_seconds // 3600 minutes = (total_seconds % 3600) // 60 seconds = total_seconds % 60 if hours > 0: return f"{hours}h {minutes}m {seconds}s" elif minutes > 0: return f"{minutes}m {seconds}s" else: return f"{seconds}s" # Get current tally def get_tally(): with flip_lock: total_flips = heads_count + tails_count if total_flips == 0: return "No flips yet" heads_percent = (heads_count / total_flips) * 100 tails_percent = (tails_count / total_flips) * 100 return f"🪙 **Total Flips:** {total_flips}\n" \ f"👑 **Heads:** {heads_count} ({heads_percent:.1f}%)\n" \ f"🔄 **Tails:** {tails_count} ({tails_percent:.1f}%)" # Load last 20 flips from the log def get_latest_flips(): try: with open(FLIP_LOG, "r") as f: lines = f.readlines() return "".join(lines[-20:]) if lines else "No flips yet." except FileNotFoundError: return "No flips yet." # Combined update function for all stats def update_all_stats(): return get_tally(), get_runtime(), get_latest_flips() # Load existing data on startup (count flips from log) def load_existing_data(): global heads_count, tails_count try: with open(FLIP_LOG, "r") as f: lines = f.readlines() # Count existing flips for line in lines: if ": Heads" in line: heads_count += 1 elif ": Tails" in line: tails_count += 1 except FileNotFoundError: pass # Load existing data on startup load_existing_data() # Build UI with gr.Blocks(theme=gr.themes.Soft()) as app: gr.Markdown("# 🪙 Eternal Coin Flipper") gr.Markdown("⚠️ **Zero CPU Limitation**: Click 'Refresh' to see updates (no real-time on free tier)") with gr.Row(): with gr.Column(): flip_button = gr.Button("🚀 Start Flipping", variant="primary", size="lg") refresh_button = gr.Button("🔄 Refresh Stats", variant="secondary") gr.Markdown("💡 *Tip: Bookmark and check back later to see progress!*") with gr.Column(): # Stats display tally_display = gr.Markdown(value=get_tally(), label="Flip Tally") runtime_display = gr.Markdown(value=f"⏱️ **Runtime:** {get_runtime()}") # Flip log gr.Markdown("## Recent Flips") flip_log = gr.Textbox(label="Last 20 Flips", lines=15, max_lines=20) # Event handlers flip_button.click( fn=lambda: (start_flipping(), *update_all_stats()), outputs=[gr.Textbox(visible=False), tally_display, runtime_display, flip_log] ) refresh_button.click( fn=update_all_stats, outputs=[tally_display, runtime_display, flip_log] ) # Auto-refresh when page loads app.load(fn=update_all_stats, outputs=[tally_display, runtime_display, flip_log]) if __name__ == "__main__": app.launch()