Number-Game / app.py
desolo-2918's picture
Update app.py
308ea93 verified
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} &nbsp;|&nbsp; "
f"**Wins:** {int(wins)} &nbsp;|&nbsp; **Losses:** {int(losses)} &nbsp;|&nbsp; "
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()