Spaces:
Sleeping
Sleeping
| 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() |