import subprocess import sys # Ensure langgraph is installed at runtime (needed for HF Spaces environments) subprocess.check_call([ sys.executable, "-m", "pip", "install", "langgraph==0.2.28", "langchain-core>=0.2.0", "--quiet", "--no-warn-script-location" ]) import random import gradio as gr from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END # ───────────────────────────────────────── # LangGraph Setup # ───────────────────────────────────────── class State(TypedDict): bet: int roll: int game_state: str # "success" | "failure" | "pending" def node_1(state: State) -> dict: return {"game_state": "pending"} def node_2(state: State) -> dict: """Success node.""" return {"game_state": "success"} def node_3(state: State) -> dict: """Failure node.""" return {"game_state": "failure"} def determine_outcome(state: State) -> str: """Route based on roll vs bet.""" return "node_2" if state["roll"] <= state["bet"] else "node_3" # Build graph once at module load builder = StateGraph(State) builder.add_node("node_1", node_1) builder.add_node("node_2", node_2) builder.add_node("node_3", node_3) builder.add_edge(START, "node_1") builder.add_conditional_edges("node_1", determine_outcome) builder.add_edge("node_2", END) builder.add_edge("node_3", END) graph = builder.compile() # ───────────────────────────────────────── # Gradio UI # ───────────────────────────────────────── STARTING_BALANCE = 100.0 with gr.Blocks( title="🎲 LangGraph Dice Bet", theme=gr.themes.Soft(primary_hue="violet"), css=""" #title { text-align: center; } #status_box { font-size: 1.1rem; text-align: center; min-height: 40px; } #stop_btn { background: #ef4444 !important; color: white !important; } """, ) as demo: balance_state = gr.State(value=STARTING_BALANCE) history_state = gr.State(value=[]) gr.Markdown("# 🎲 LangGraph Dice Betting Game", elem_id="title") gr.Markdown( "Bet a number (1–12). Two dice are rolled — if the total is **≤ your bet** you win **$10**, " "otherwise you lose **$5**. Starting balance: **$100**.", elem_id="title", ) with gr.Row(): balance_display = gr.Number(value=STARTING_BALANCE, label="💰 Balance ($)", interactive=False) wins_display = gr.Number(value=0, label="✅ Wins", interactive=False) losses_display = gr.Number(value=0, label="❌ Losses", interactive=False) net_display = gr.Number(value=0.0, label="📈 Net P&L ($)", interactive=False) gr.Markdown("---") with gr.Row(): bet_input = gr.Slider(minimum=1, maximum=12, step=1, value=7, label="Your Bet (1–12)") with gr.Column(): roll_btn = gr.Button("🎲 Roll!", variant="primary") stop_btn = gr.Button("🛑 Stop Playing", elem_id="stop_btn") status_box = gr.Markdown("Place your bet and roll!", elem_id="status_box") gr.Markdown("### 📜 Round History") history_box = gr.Dataframe( headers=["Result"], datatype=["str"], value=[], interactive=False, wrap=True, ) stopped_msg = gr.Markdown(visible=False) def on_roll(bet, balance, history, wins, losses): bet = int(bet) die1 = random.randint(1, 6) die2 = random.randint(1, 6) roll = die1 + die2 from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(graph.invoke, {"bet": bet, "roll": roll, "game_state": "pending"}) result = future.result() won = result["game_state"] == "success" delta = 10 if won else -5 new_balance = balance + delta new_wins = wins + (1 if won else 0) new_losses = losses + (0 if won else 1) net = new_balance - STARTING_BALANCE emoji = "🎉 WIN" if won else "💀 LOSS" entry = f"{emoji} | Bet ≤{bet} | Roll {die1}+{die2}={roll} | {'+ $10' if won else '- $5'} | Balance ${new_balance:.2f}" new_history = [entry] + history status = f"{'🟢 WIN! +$10' if won else '🔴 LOSS. -$5'} — rolled **{die1} + {die2} = {roll}** vs bet ≤{bet}" return ( new_balance, new_history, status, new_wins, new_losses, new_balance, new_wins, new_losses, round(net, 2), [[r] for r in new_history], gr.update(visible=False), ) def on_stop(balance, wins, losses): net = balance - STARTING_BALANCE sign = "+ " if net >= 0 else "" summary = ( f"### 🏁 Game Over!\n\n" f"**Final Balance:** ${balance:.2f}  |  " f"**Wins:** {int(wins)}  |  **Losses:** {int(losses)}  |  " f"**Net P&L:** {sign}${net:.2f}" ) return gr.update(value=summary, visible=True), gr.update(interactive=False), gr.update(interactive=False) roll_btn.click( fn=on_roll, inputs=[bet_input, balance_state, history_state, wins_display, losses_display], outputs=[ balance_state, history_state, status_box, wins_display, losses_display, balance_display, wins_display, losses_display, net_display, history_box, stopped_msg, ], ) stop_btn.click( fn=on_stop, inputs=[balance_state, wins_display, losses_display], outputs=[stopped_msg, roll_btn, bet_input], ) if __name__ == "__main__": demo.launch()