Spaces:
Build error
Build error
File size: 26,990 Bytes
b3a9ec4 898b55a 185e9d2 898b55a 842d62b 898b55a b3a9ec4 185e9d2 c59d6c7 898b55a db6e385 28b6f0f 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 842d62b 898b55a 4420646 898b55a 6be63cd 898b55a 28b6f0f 898b55a 842d62b 898b55a 28b6f0f 898b55a 842d62b 898b55a ee800d8 898b55a 28b6f0f 898b55a 28b6f0f 898b55a 28b6f0f ee800d8 898b55a 28b6f0f 898b55a 28b6f0f 898b55a 28b6f0f 898b55a 28b6f0f c59d6c7 898b55a 28b6f0f 898b55a 28b6f0f 898b55a 28b6f0f 898b55a a3e1550 28b6f0f 898b55a 5c7fb25 898b55a 5c7fb25 898b55a 5c7fb25 898b55a ce03581 28b6f0f ce03581 842d62b 898b55a 9671560 898b55a 9671560 b634cba 898b55a ce03581 898b55a 9671560 898b55a 28b6f0f 842d62b 898b55a 28b6f0f 898b55a 842d62b db6e385 ce03581 898b55a 7102d41 28b6f0f 7102d41 ce03581 7102d41 ce03581 7102d41 ce03581 28b6f0f 7102d41 898b55a 7102d41 28b6f0f 898b55a 7102d41 28b6f0f 898b55a 7102d41 28b6f0f 7102d41 898b55a 7102d41 28b6f0f 898b55a ce03581 898b55a 9671560 ce03581 28b6f0f 9671560 898b55a ce03581 898b55a ce03581 7102d41 28b6f0f 898b55a 842d62b 898b55a ce03581 28b6f0f 898b55a 842d62b ce03581 28b6f0f ce03581 898b55a 28b6f0f 898b55a 842d62b 6be63cd 842d62b 9671560 28b6f0f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
"""
SPIRAL: Strategic Business Competition Simulator
This demo has been updated to more intuitively demonstrate the key concepts from the
"Self-Play in Zero-Sum Games Incentivizes Reasoning" (SPIRAL) research paper.
Instead of Tic-Tac-Toe, this simulation uses a zero-sum business competition to showcase
complex, multi-turn strategic reasoning in a more practical and relatable context.
"""
import gradio as gr
import numpy as np
import pandas as pd
import plotly.express as px
import spaces
import json
# --- Game Configuration ---
INITIAL_BUDGET = 1000
INITIAL_MARKET_SHARE = 50
INITIAL_PRODUCT_QUALITY = 50
NUM_QUARTERS = 12
TITLE = "SPIRAL: Strategic Business Competition"
# --- Game Environment ---
class BusinessCompetitionEnv:
"""Manages the state of the strategic business competition."""
def __init__(self):
self.reset()
def reset(self):
"""Resets the game to its initial state."""
self.quarter = 0
self.game_over = False
self.player_stats = {
"budget": INITIAL_BUDGET,
"market_share": INITIAL_MARKET_SHARE,
"product_quality": INITIAL_PRODUCT_QUALITY,
}
self.ai_stats = {
"budget": INITIAL_BUDGET,
"market_share": INITIAL_MARKET_SHARE,
"product_quality": INITIAL_PRODUCT_QUALITY,
}
# History stores the state at the *end* of each quarter
self.history = []
self._add_to_history() # Initial state at quarter 0
return self.get_state()
def _add_to_history(self):
"""Adds the current state to the history log."""
self.history.append({
"Quarter": self.quarter,
"Player Budget": self.player_stats["budget"],
"AI Budget": self.ai_stats["budget"],
"Player Market Share": self.player_stats["market_share"],
"AI Market Share": self.ai_stats["market_share"],
"Player Product Quality": self.player_stats["product_quality"],
"AI Product Quality": self.ai_stats["product_quality"],
})
def get_state(self):
"""Returns the complete current state of the game."""
return {
"quarter": self.quarter,
"player_stats": self.player_stats,
"ai_stats": self.ai_stats,
"game_over": self.game_over,
"history": self.history
}
def get_winner(self):
"""Determines the winner at the end of the game."""
if not self.game_over:
return None
if self.player_stats["market_share"] > self.ai_stats["market_share"]:
return "You"
elif self.ai_stats["market_share"] > self.player_stats["market_share"]:
return "AI"
else:
return "It's a Draw"
def step(self, player_allocation, ai_allocation):
"""Executes one quarter of the game."""
if self.game_over:
return self.get_state()
self.quarter += 1
# 1. Update Product Quality from R&D investment
self.player_stats["product_quality"] += int(np.sqrt(player_allocation["rd"]) * 1.5)
self.ai_stats["product_quality"] += int(np.sqrt(ai_allocation["rd"]) * 1.5)
# 2. Calculate market share shift from Marketing and Quality
mkt_diff = player_allocation["marketing"] - ai_allocation["marketing"]
quality_diff = self.player_stats["product_quality"] - self.ai_stats["product_quality"]
# Marketing has a direct but temporary effect, quality has a persistent effect
market_share_shift = (mkt_diff / 100.0) + (quality_diff / 50.0)
market_share_shift = np.clip(market_share_shift, -7, 7) # Cap shifts per quarter
self.player_stats["market_share"] += market_share_shift
self.ai_stats["market_share"] -= market_share_shift
self.player_stats["market_share"] = np.clip(self.player_stats["market_share"], 0, 100)
self.ai_stats["market_share"] = 100 - self.player_stats["market_share"]
# 3. Calculate next quarter's budget from Sales investment and market share
player_remaining_budget = self.player_stats['budget'] - sum(player_allocation.values())
ai_remaining_budget = self.ai_stats['budget'] - sum(ai_allocation.values())
player_sales_roi = 1.2 + (self.player_stats["market_share"] / 200.0)
ai_sales_roi = 1.2 + (self.ai_stats["market_share"] / 200.0)
self.player_stats["budget"] = int(player_allocation["sales"] * player_sales_roi + player_remaining_budget)
self.ai_stats["budget"] = int(ai_allocation["sales"] * ai_sales_roi + ai_remaining_budget)
# Error Handling: Clamp budgets to >=0
self.player_stats["budget"] = max(0, self.player_stats["budget"])
self.ai_stats["budget"] = max(0, self.ai_stats["budget"])
if self.quarter >= NUM_QUARTERS:
self.game_over = True
self._add_to_history()
return self.get_state()
# --- AI Logic ---
def ai_strategy(ai_stats, player_stats, quarter):
"""
A heuristic-based AI to simulate a strategic opponent.
This mimics the kind of robust strategy that would emerge from self-play,
reacting to the opponent and planning for the long term.
"""
budget = ai_stats["budget"]
reasoning = []
# Default balanced strategy
allocation = {"rd": 0.33, "marketing": 0.34, "sales": 0.33}
# --- Strategic Adjustments based on SPIRAL principles ---
# Dynamic thresholds: Tighten as game progresses (simulates adaptive curriculum)
quality_gap_threshold = 15 - (quarter // 3) # E.g., starts at 15, drops to 9 by quarter 9
market_share_threshold = 10 - (quarter // 4) # Starts at 10, drops to 7 by quarter 8
quality_advantage_threshold = 20 - (quarter // 3)
budget_threshold = 0.8 + (quarter / 100.0) # Slightly increases to make AI more conservative later
# 1. React to quality gap (long-term planning)
if ai_stats["product_quality"] < player_stats["product_quality"] - quality_gap_threshold:
allocation["rd"] += 0.2
allocation["marketing"] -= 0.1
allocation["sales"] -= 0.1
reasoning.append(f"Quarter {quarter}: My analysis indicates a growing product quality gap (threshold: {quality_gap_threshold}). I'm increasing R&D investment to innovate and secure a long-term competitive advantage.")
# 2. React to market share loss (short-term defense)
elif ai_stats["market_share"] < player_stats["market_share"] - market_share_threshold:
allocation["marketing"] += 0.2
allocation["rd"] -= 0.1
allocation["sales"] -= 0.1
reasoning.append(f"Quarter {quarter}: You've recently captured significant market share (threshold: {market_share_threshold}). I'm launching an aggressive marketing campaign to win back customers and regain my position.")
# 3. Exploit a quality advantage (pressing an advantage)
if ai_stats["product_quality"] > player_stats["product_quality"] + quality_advantage_threshold:
allocation["marketing"] += 0.15
allocation["rd"] -= 0.15
reasoning.append(f"Quarter {quarter}: My product quality ({ai_stats['product_quality']:.0f}) is superior (threshold: {quality_advantage_threshold}). I will leverage this with a marketing push to translate product leadership into market dominance.")
# 4. Manage budget (resource management)
if ai_stats["budget"] < player_stats["budget"] * budget_threshold:
allocation["sales"] += 0.15
allocation["rd"] -= 0.15
reasoning.append(f"Quarter {quarter}: My projections show a potential budget shortfall (threshold: {budget_threshold:.2f}). I am focusing on sales to ensure strong revenue growth for future quarters.")
if not reasoning:
reasoning.append(f"Quarter {quarter}: I am pursuing a balanced strategy, investing across R&D, Marketing, and Sales to ensure steady, long-term growth and market presence.")
# Normalize allocations
total_allocation = sum(allocation.values())
final_allocation = {key: int(budget * (val / total_allocation)) for key, val in allocation.items()}
# Simulate RAE-inspired stability: Average with a "role-reversed" allocation
role_reversed_alloc = {"rd": allocation["rd"], "marketing": allocation["sales"], "sales": allocation["marketing"]} # Simple swap for variance reduction
reversed_total = sum(role_reversed_alloc.values())
reversed_final = {key: int(budget * (val / reversed_total)) for key, val in role_reversed_alloc.items()}
for key in final_allocation:
final_allocation[key] = int((final_allocation[key] + reversed_final[key]) / 2)
# Ensure the sum is exactly the budget
diff = budget - sum(final_allocation.values())
final_allocation['sales'] += diff
return final_allocation, " ".join(reasoning)
# --- Gradio UI ---
def create_interface():
"""Creates the Gradio web interface for the simulator."""
with gr.Blocks(title=TITLE, theme=gr.themes.Soft()) as demo:
game_env = gr.State(BusinessCompetitionEnv())
gr.Markdown(f"# 🎮 {TITLE}")
with gr.Accordion("ℹ️ What is this app about & How to play", open=False):
gr.Markdown("""
### What is this app about?
**For Business Strategists, Product Managers, and Students:**
This simulator is a hands-on sandbox for exploring the core trade-offs of business strategy. You are in control of a company competing against a strategic AI. By allocating your budget each quarter, you can directly see the impact of your decisions:
- **Short-term vs. Long-term:** Feel the tension between investing in Marketing for immediate market share gains versus investing in R&D for a long-term product advantage.
- **Resource Management:** Learn how investing in Sales grows your future budget, enabling more significant investments later on.
- **Competitive Dynamics:** The AI opponent doesn't play a fixed strategy. It analyzes your moves and adapts, forcing you to think multiple turns ahead. This provides an intuitive feel for how competitive landscapes evolve.
**For AI/ML Engineers and Data Scientists:**
This demo provides a practical look at the principles of advanced AI reasoning described in the SPIRAL research paper. The AI opponent is not just a set of `if/else` rules; it uses a strategy model that mimics the outcomes of self-play reinforcement learning.
- **Emergent Strategy:** The AI's decision-making process illustrates how an agent can learn to balance priorities, react to threats, and press advantages—all without being explicitly programmed for each scenario. This is a core concept of self-play.
- **Multi-Turn Reasoning:** Observe the AI's rationale. It often makes decisions based on future projections (e.g., potential budget shortfalls or quality gaps), showcasing a capacity for long-term planning.
- **Zero-Sum Dynamics:** The simulation is a zero-sum game for market share, creating the competitive pressure that, according to the SPIRAL paper, is essential for incentivizing robust reasoning.
### Key Links to SPIRAL Paper Takeaways
- **Transferable Reasoning:** Your R&D investments build long-term planning skills, transferable to real-world logic problems (Takeaway 2).
- **Diverse Skills:** Marketing encourages probabilistic thinking (like Poker), while Sales focuses on resource foresight (Takeaway 4).
- **Synergy from Multi-Game Training:** Combining these creates a well-rounded strategy, better than focusing on one area (Takeaway 5).
### How to Use the App
1. **Your Goal:** Achieve a higher market share than the AI by the end of 12 quarters.
2. **Choose Your Mode:** Select either "Raw Values" or "Percentages" to allocate your budget.
3. **Allocate Budget:** Use the sliders to decide how much of your quarterly budget to invest in three key areas.
- `R&D`: Improves your product quality, giving you a persistent, long-term edge.
- `Marketing`: Provides an immediate boost to your market share for the current quarter.
- `Sales`: Increases your budget for the *next* quarter, fueling future growth.
4. **End the Quarter:** Click the "End Quarter" button to submit your decisions.
5. **Analyze the Results:**
- The charts on the left will update to show the new market landscape.
- The "AI Strategic Reasoning" box will explain the logic behind the AI's counter-move.
- Your budget for the next quarter will be updated.
6. **Adapt and Win:** Continue making decisions for 12 quarters, adapting your strategy to counter the AI and win the market.
""")
with gr.Row():
with gr.Column(scale=3):
gr.Markdown("### 📈 Market Dashboard")
plot_market_share = gr.Plot()
with gr.Row():
plot_budget = gr.Plot()
plot_quality = gr.Plot()
with gr.Column(scale=2):
gr.Markdown("### 📊 Your Decisions")
status_box = gr.Textbox(f"Quarter 1 of {NUM_QUARTERS}. Your move.", label="Game Status", interactive=False)
with gr.Group():
player_budget_display = gr.Label(f"Your Budget: ${INITIAL_BUDGET}")
allocation_mode_radio = gr.Radio(["Raw Values", "Percentages"], label="Allocation Mode", value="Raw Values")
with gr.Group() as raw_values_group:
rd_slider_raw = gr.Slider(0, INITIAL_BUDGET, label="R&D Investment", value=333, step=10)
mkt_slider_raw = gr.Slider(0, INITIAL_BUDGET, label="Marketing Investment", value=333, step=10)
sales_slider_raw = gr.Slider(0, INITIAL_BUDGET, label="Sales Investment", value=334, step=10)
total_allocated_raw_display = gr.Label("Total Allocated: $1000")
with gr.Group(visible=False) as percentage_group:
rd_slider_pct = gr.Slider(0, 100, label="R&D Allocation (%)", value=33, step=1)
mkt_slider_pct = gr.Slider(0, 100, label="Marketing Allocation (%)", value=33, step=1)
sales_slider_pct = gr.Slider(0, 100, label="Sales Allocation (%)", value=34, step=1)
total_allocated_pct_display = gr.Label("Total Allocated: 100%")
with gr.Row():
submit_btn = gr.Button("End Quarter", variant="primary")
new_game_btn = gr.Button("Start New Game")
ai_vs_ai_btn = gr.Button("Simulate AI vs AI")
with gr.Row():
save_btn = gr.Button("Save Game")
load_file = gr.File(label="Load Game JSON")
gr.Markdown("### 🧠 AI Strategic Reasoning")
ai_reasoning_box = gr.Textbox("", label="AI Decision Rationale", lines=5, interactive=False)
gr.Markdown("### 📝 Post-Game Analysis")
analysis_box = gr.Textbox("", label="Strategy Insights", lines=3, interactive=False)
def create_plots(history):
df = pd.DataFrame(history)
if df.empty:
return None, None, None
fig_ms = px.line(df, x="Quarter", y=["Player Market Share", "AI Market Share"], title="Market Share (%)", markers=True, color_discrete_map={"Player Market Share": "#3b82f6", "AI Market Share": "#ef4444"})
fig_ms.update_layout(yaxis_range=[0,100], legend_title_text='')
fig_b = px.line(df, x="Quarter", y=["Player Budget", "AI Budget"], title="Budget ($)", markers=True, color_discrete_map={"Player Budget": "#3b82f6", "AI Budget": "#ef4444"})
fig_b.update_layout(legend_title_text='')
fig_q = px.line(df, x="Quarter", y=["Player Product Quality", "AI Product Quality"], title="Product Quality Index", markers=True, color_discrete_map={"Player Product Quality": "#3b82f6", "AI Product Quality": "#ef4444"})
fig_q.update_layout(legend_title_text='')
return fig_ms, fig_b, fig_q
@spaces.GPU
def game_step_and_update(env, mode, rd_raw, mkt_raw, sales_raw, rd_pct, mkt_pct, sales_pct):
player_budget = env.player_stats["budget"]
# Helper to create a return tuple for user input errors
def create_error_return(status_text):
return (
env, status_text, env.ai_stats.get("last_reasoning", ""), *create_plots(env.history),
gr.update(value=f"Your Budget: ${player_budget}"),
gr.update(), gr.update(), gr.update(), # Raw sliders
gr.update(), gr.update(), gr.update(), # Pct sliders
gr.update(interactive=True), # Submit button
gr.update() # Analysis box
)
if mode == "Percentages":
if rd_pct + mkt_pct + sales_pct != 100:
return create_error_return("Error: Percentage allocations must sum to 100%.")
rd_alloc_val = int(player_budget * rd_pct / 100)
mkt_alloc_val = int(player_budget * mkt_pct / 100)
sales_alloc_val = int(player_budget * sales_pct / 100)
total = rd_alloc_val + mkt_alloc_val + sales_alloc_val
sales_alloc_val += player_budget - total
else: # Raw Values
rd_alloc_val, mkt_alloc_val, sales_alloc_val = rd_raw, mkt_raw, sales_raw
if (rd_alloc_val + mkt_alloc_val + sales_alloc_val) > player_budget:
return create_error_return(f"Error: Allocation (${rd_alloc_val + mkt_alloc_val + sales_alloc_val}) exceeds budget (${player_budget}).")
player_alloc = {"rd": rd_alloc_val, "marketing": mkt_alloc_val, "sales": sales_alloc_val}
ai_alloc, ai_reasoning = ai_strategy(env.ai_stats, env.player_stats, env.quarter + 1) # Pass next quarter
env.ai_stats["last_reasoning"] = ai_reasoning
env.step(player_alloc, ai_alloc)
state = env.get_state()
plots = create_plots(state["history"])
submit_btn_update = gr.update(interactive=True)
analysis_text = ""
if state["game_over"]:
winner = env.get_winner()
status_text = f"Game Over! Winner: {winner}. Final market share: You ({state['player_stats']['market_share']:.1f}%) vs AI ({state['ai_stats']['market_share']:.1f}%)."
submit_btn_update = gr.update(interactive=False)
# Post-game analysis
final_history = state["history"][-1]
rd_invest = final_history["Player Product Quality"] - INITIAL_PRODUCT_QUALITY
sales_focus = final_history["Player Budget"] > INITIAL_BUDGET
analysis_text = f"Post-Game Analysis: Your strategy showed synergy by balancing skills—e.g., high R&D (quality gain: {rd_invest}) with Sales (budget growth: {sales_focus}) led to transferable reasoning advantages."
else:
status_text = f"End of Quarter {state['quarter']}. Your turn."
new_budget = state["player_stats"]["budget"]
return (
state, status_text, ai_reasoning, *plots,
gr.update(value=f"Your Budget: ${new_budget}"),
gr.update(maximum=new_budget, value=int(new_budget/3)),
gr.update(maximum=new_budget, value=int(new_budget/3)),
gr.update(maximum=new_budget, value=new_budget - 2 * int(new_budget/3)),
gr.update(value=33), gr.update(value=33), gr.update(value=34),
submit_btn_update,
analysis_text
)
def on_new_game():
env = BusinessCompetitionEnv()
state = env.get_state()
plots = create_plots(state["history"])
return (
env, f"Quarter 1 of {NUM_QUARTERS}. Your move.", "", *plots,
gr.update(value=f"Your Budget: ${INITIAL_BUDGET}"),
gr.update(maximum=INITIAL_BUDGET, value=333),
gr.update(maximum=INITIAL_BUDGET, value=333),
gr.update(maximum=INITIAL_BUDGET, value=334),
gr.update(value=33), gr.update(value=33), gr.update(value=34),
gr.update(interactive=True),
""
)
def update_total_raw_display(rd, mkt, sales):
return gr.Label(f"Total Allocated: ${rd + mkt + sales}")
def update_total_pct_display(rd, mkt, sales):
return gr.Label(f"Total Allocated: {rd + mkt + sales}%")
def toggle_allocation_mode(mode):
return gr.update(visible=mode == "Raw Values"), gr.update(visible=mode == "Percentages")
def adjust_pct_sliders(rd, mkt):
return gr.update(value=100 - rd - mkt)
def simulate_ai_vs_ai():
env = BusinessCompetitionEnv()
all_reasoning = []
for q in range(1, NUM_QUARTERS + 1):
player_alloc, player_reasoning = ai_strategy(env.player_stats, env.ai_stats, q) # Player as AI copy
ai_alloc, ai_reasoning = ai_strategy(env.ai_stats, env.player_stats, q)
env.step(player_alloc, ai_alloc)
all_reasoning.append(f"Quarter {q}: AI1 Reasoning: {player_reasoning} | AI2 Reasoning: {ai_reasoning}")
state = env.get_state()
winner = env.get_winner()
plots = create_plots(state["history"])
analysis_text = f"AI vs AI Simulation: Synergy in self-play led to balanced strategies. Winner: {winner}."
return "\n\n".join(all_reasoning), *plots, f"AI vs AI Simulation Complete! Winner: {winner}", analysis_text
def save_game(env):
return json.dumps(env.get_state()["history"])
def load_game(file):
if file is None:
return None, "No file uploaded."
with open(file.name, "r") as f:
history = json.load(f)
env = BusinessCompetitionEnv()
env.history = history
env.quarter = history[-1]["Quarter"]
env.player_stats = {
"budget": history[-1]["Player Budget"],
"market_share": history[-1]["Player Market Share"],
"product_quality": history[-1]["Player Product Quality"],
}
env.ai_stats = {
"budget": history[-1]["AI Budget"],
"market_share": history[-1]["AI Market Share"],
"product_quality": history[-1]["AI Product Quality"],
}
env.game_over = env.quarter >= NUM_QUARTERS
plots = create_plots(env.history)
status = f"Loaded game at Quarter {env.quarter}. Your move." if not env.game_over else "Loaded completed game."
return env, status, "", *plots, gr.update(value=f"Your Budget: ${env.player_stats['budget']}"), *([gr.update()] * 6), gr.update(interactive=not env.game_over), ""
# --- Event Handlers ---
submit_btn.click(
fn=game_step_and_update,
inputs=[game_env, allocation_mode_radio, rd_slider_raw, mkt_slider_raw, sales_slider_raw, rd_slider_pct, mkt_slider_pct, sales_slider_pct],
outputs=[
game_env, status_box, ai_reasoning_box,
plot_market_share, plot_budget, plot_quality,
player_budget_display,
rd_slider_raw, mkt_slider_raw, sales_slider_raw,
rd_slider_pct, mkt_slider_pct, sales_slider_pct,
submit_btn,
analysis_box
]
)
new_game_btn.click(
fn=on_new_game,
inputs=[],
outputs=[
game_env, status_box, ai_reasoning_box,
plot_market_share, plot_budget, plot_quality,
player_budget_display,
rd_slider_raw, mkt_slider_raw, sales_slider_raw,
rd_slider_pct, mkt_slider_pct, sales_slider_pct,
submit_btn,
analysis_box
]
)
ai_vs_ai_btn.click(
fn=simulate_ai_vs_ai,
inputs=[],
outputs=[ai_reasoning_box, plot_market_share, plot_budget, plot_quality, status_box, analysis_box]
)
save_btn.click(
fn=save_game,
inputs=game_env,
outputs=gr.File(label="Download Game JSON")
)
load_file.change(
fn=load_game,
inputs=load_file,
outputs=[
game_env, status_box, ai_reasoning_box,
plot_market_share, plot_budget, plot_quality,
player_budget_display,
rd_slider_raw, mkt_slider_raw, sales_slider_raw,
rd_slider_pct, mkt_slider_pct, sales_slider_pct,
submit_btn,
analysis_box
]
)
# Handlers for updating total displays
for slider in [rd_slider_raw, mkt_slider_raw, sales_slider_raw]:
slider.change(fn=update_total_raw_display, inputs=[rd_slider_raw, mkt_slider_raw, sales_slider_raw], outputs=total_allocated_raw_display)
for slider in [rd_slider_pct, mkt_slider_pct, sales_slider_pct]:
slider.change(fn=update_total_pct_display, inputs=[rd_slider_pct, mkt_slider_pct, sales_slider_pct], outputs=total_allocated_pct_display)
# Auto-adjust percentage sliders
rd_slider_pct.change(fn=adjust_pct_sliders, inputs=[rd_slider_pct, mkt_slider_pct], outputs=sales_slider_pct)
mkt_slider_pct.change(fn=adjust_pct_sliders, inputs=[rd_slider_pct, mkt_slider_pct], outputs=sales_slider_pct)
# Handler for toggling allocation modes
allocation_mode_radio.change(
fn=toggle_allocation_mode,
inputs=allocation_mode_radio,
outputs=[raw_values_group, percentage_group]
)
demo.load(on_new_game, outputs=[game_env, status_box, ai_reasoning_box, plot_market_share, plot_budget, plot_quality, player_budget_display, rd_slider_raw, mkt_slider_raw, sales_slider_raw, rd_slider_pct, mkt_slider_pct, sales_slider_pct, submit_btn, analysis_box])
return demo
if __name__ == "__main__":
spiral_demo = create_interface()
spiral_demo.launch() |