Spaces:
Sleeping
Sleeping
import gradio as gr | |
import matplotlib.pyplot as plt | |
from domains.coordination.game_coordination import BayesianGame, GamePhase | |
from domains.environment.environment_domain import EvidenceType | |
class GradioInterface: | |
"""Gradio interface for the Bayesian Game.""" | |
def __init__(self): | |
"""Initialize the Gradio interface.""" | |
self.game = None | |
self.reset_game() | |
def reset_game( | |
self, | |
dice_sides: int = 6, | |
max_rounds: int = 10, | |
evidence_type_str: str = "Basic", | |
) -> tuple[str, plt.Figure, str]: | |
"""Reset the game with new parameters. | |
Args: | |
dice_sides: Number of sides on the dice | |
max_rounds: Maximum number of rounds | |
evidence_type_str: Evidence type ("Basic" or "Extended") | |
Returns: | |
Tuple of (status, belief_chart, game_log) | |
""" | |
evidence_type = ( | |
EvidenceType.EXTENDED | |
if evidence_type_str == "Extended" | |
else EvidenceType.BASIC | |
) | |
self.game = BayesianGame( | |
dice_sides=dice_sides, max_rounds=max_rounds, evidence_type=evidence_type | |
) | |
return self._get_interface_state() | |
def start_new_game(self, target_value: str = "") -> tuple[str, plt.Figure, str]: | |
"""Start a new game. | |
Args: | |
target_value: Optional specific target value | |
Returns: | |
Tuple of (status, belief_chart, game_log) | |
""" | |
try: | |
target = int(target_value) if target_value.strip() else None | |
if target is not None and not (1 <= target <= self.game.dice_sides): | |
return ( | |
f"β Target value must be between 1 and {self.game.dice_sides}", | |
self._create_empty_chart(), | |
"", | |
) | |
self.game.start_new_game(target_value=target) | |
return self._get_interface_state() | |
except ValueError as e: | |
return f"β Error: {e!s}", self._create_empty_chart(), "" | |
def play_round(self) -> tuple[str, plt.Figure, str]: | |
"""Play one round of the game. | |
Returns: | |
Tuple of (status, belief_chart, game_log) | |
""" | |
try: | |
# Check if game is already finished - but still show the final state | |
if self.game.is_game_finished(): | |
# Get the current final state but with a message about being finished | |
status, belief_chart, game_log = self._get_interface_state() | |
return ( | |
"π Game completed! All rounds finished. Start a new game to play again.", | |
belief_chart, | |
game_log, | |
) | |
if self.game.game_state.phase != GamePhase.PLAYING: | |
return ( | |
"β Game not in playing phase. Start a new game first.", | |
self._create_empty_chart(), | |
"", | |
) | |
self.game.play_round() | |
return self._get_interface_state() | |
except ValueError as e: | |
return f"β Error: {e!s}", self._create_empty_chart(), "" | |
def _get_interface_state(self) -> tuple[str, plt.Figure, str]: | |
"""Get current interface state. | |
Returns: | |
Tuple of (status, belief_chart, game_log) | |
""" | |
state = self.game.get_current_state() | |
# Status message | |
if state.phase == GamePhase.SETUP: | |
status = "π― Ready to start new game" | |
elif state.phase == GamePhase.PLAYING: | |
entropy = state.belief_entropy | |
status = f"π² Playing - Round {state.round_number}/{state.max_rounds} - Entropy: {entropy:.2f} bits" | |
else: # FINISHED | |
correct = "β " if self.game.was_final_guess_correct() else "β" | |
accuracy = self.game.get_final_guess_accuracy() | |
entropy = state.belief_entropy | |
status = f"{correct} Game finished! Final guess: {state.most_likely_target} (True: {state.target_value}) - Accuracy: {accuracy:.2f} - Entropy: {entropy:.2f} bits" | |
# Round information - removed for cleaner UI | |
# Belief visualization | |
belief_chart = self._create_belief_chart() | |
# Game log | |
game_log = self._create_game_log() | |
return status, belief_chart, game_log | |
def _create_belief_chart(self) -> plt.Figure: | |
"""Create belief distribution chart. | |
Returns: | |
Matplotlib figure showing belief distribution | |
""" | |
# Close any existing figures to prevent memory leaks | |
plt.close("all") | |
fig, ax = plt.subplots(figsize=(10, 6)) | |
if self.game.game_state.current_beliefs: | |
targets = list(range(1, len(self.game.game_state.current_beliefs) + 1)) | |
beliefs = self.game.game_state.current_beliefs | |
bars = ax.bar( | |
targets, beliefs, alpha=0.7, color="skyblue", edgecolor="navy" | |
) | |
# Highlight the most likely target | |
if self.game.game_state.most_likely_target: | |
most_likely_idx = self.game.game_state.most_likely_target - 1 | |
bars[most_likely_idx].set_color("orange") | |
bars[most_likely_idx].set_alpha(1.0) | |
# Highlight true target if known | |
if self.game.game_state.target_value: | |
true_target_idx = self.game.game_state.target_value - 1 | |
bars[true_target_idx].set_edgecolor("red") | |
bars[true_target_idx].set_linewidth(3) | |
ax.set_xlabel("Target Value") | |
ax.set_ylabel("Belief Probability") | |
# Enhanced title based on game state | |
if self.game.game_state.phase == GamePhase.FINISHED: | |
correct_indicator = ( | |
"β " if self.game.was_final_guess_correct() else "β" | |
) | |
ax.set_title(f"Final Belief Distribution {correct_indicator}") | |
else: | |
ax.set_title("Player 2's Belief Distribution") | |
ax.set_xticks(targets) | |
ax.set_ylim(0, 1) | |
ax.grid(True, alpha=0.3) | |
# Add legend | |
legend_elements = [] | |
if self.game.game_state.most_likely_target: | |
legend_elements.append( | |
plt.Rectangle( | |
(0, 0), 1, 1, fc="orange", alpha=1.0, label="Most Likely" | |
) | |
) | |
if self.game.game_state.target_value: | |
legend_elements.append( | |
plt.Rectangle( | |
(0, 0), 1, 1, fc="skyblue", ec="red", lw=3, label="True Target" | |
) | |
) | |
if legend_elements: | |
ax.legend(handles=legend_elements) | |
else: | |
ax.text( | |
0.5, | |
0.5, | |
"Start a game to see beliefs", | |
transform=ax.transAxes, | |
ha="center", | |
va="center", | |
fontsize=14, | |
) | |
ax.set_xlim(0, 1) | |
ax.set_ylim(0, 1) | |
plt.tight_layout() | |
return fig | |
def _create_empty_chart(self) -> plt.Figure: | |
"""Create an empty chart for error states. | |
Returns: | |
Matplotlib figure with error message | |
""" | |
# Close any existing figures to prevent memory leaks | |
plt.close("all") | |
fig, ax = plt.subplots(figsize=(10, 6)) | |
ax.text( | |
0.5, | |
0.5, | |
"Error: Unable to display chart", | |
transform=ax.transAxes, | |
ha="center", | |
va="center", | |
fontsize=14, | |
color="red", | |
) | |
ax.set_xlim(0, 1) | |
ax.set_ylim(0, 1) | |
ax.set_title("Chart Error") | |
plt.tight_layout() | |
return fig | |
def _create_game_log(self) -> str: | |
"""Create game log showing evidence history. | |
Returns: | |
Formatted string with game log | |
""" | |
if not self.game.game_state.evidence_history: | |
return "No evidence yet. Start playing rounds to see the log." | |
log_lines = ["**Evidence History:**\n"] | |
for i, evidence in enumerate(self.game.game_state.evidence_history, 1): | |
# Handle multiple evidence types | |
evidence_display = [] | |
for result in evidence.comparison_results: | |
emoji = { | |
"higher": "β¬οΈ", | |
"lower": "β¬οΈ", | |
"same": "π―", | |
"half": "Β½", | |
"double": "x2", | |
}.get(result, "β") | |
evidence_display.append(f"{result} {emoji}") | |
evidence_str = ", ".join(evidence_display) | |
log_lines.append(f"Round {i}: Rolled {evidence.dice_roll} β {evidence_str}") | |
# Add completion message if game is finished | |
if self.game.game_state.phase == GamePhase.FINISHED: | |
log_lines.append("") | |
log_lines.append("**π Game Completed!**") | |
if self.game.was_final_guess_correct(): | |
log_lines.append( | |
"π **Congratulations!** Player 2 correctly identified the target!" | |
) | |
else: | |
log_lines.append( | |
"π **Learning opportunity!** Player 2's beliefs converged but missed the target." | |
) | |
# Add some Bayesian insights | |
final_accuracy = self.game.get_final_guess_accuracy() | |
# Accuracy thresholds | |
STRONG_EVIDENCE_THRESHOLD = 0.5 | |
MODERATE_EVIDENCE_THRESHOLD = 0.3 | |
if final_accuracy > STRONG_EVIDENCE_THRESHOLD: | |
log_lines.append( | |
f"π― Strong evidence: {final_accuracy:.1%} confidence in true target" | |
) | |
elif final_accuracy > MODERATE_EVIDENCE_THRESHOLD: | |
log_lines.append( | |
f"π€ Moderate evidence: {final_accuracy:.1%} confidence in true target" | |
) | |
else: | |
log_lines.append( | |
f"π«οΈ Conflicting evidence: Only {final_accuracy:.1%} confidence in true target" | |
) | |
return "\n".join(log_lines) | |
def create_interface() -> gr.Interface: | |
"""Create and return the Gradio interface. | |
Returns: | |
Configured Gradio interface | |
""" | |
interface = GradioInterface() | |
with gr.Blocks(title="Bayesian Game", theme=gr.themes.Soft()) as demo: | |
gr.Markdown("# π² Bayesian Game") | |
gr.Markdown( | |
""" | |
**Game Rules:** | |
- Judge and Player 1 can see the target die value | |
- Player 2 must deduce the target value using Bayesian inference | |
- Each round: Player 1 rolls dice and reports evidence based on selected type | |
- **Basic Evidence**: higher/lower/same compared to target | |
- **Extended Evidence**: higher/lower/same/half/double (multiple types can apply) | |
- Game runs for a specified number of rounds | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown("### Game Controls") | |
with gr.Row(): | |
dice_sides = gr.Number( | |
value=6, label="Dice Sides", minimum=2, maximum=20, precision=0 | |
) | |
max_rounds = gr.Number( | |
value=10, label="Max Rounds", minimum=1, maximum=50, precision=0 | |
) | |
evidence_type_dropdown = gr.Dropdown( | |
choices=["Basic", "Extended"], | |
value="Basic", | |
label="Evidence Type", | |
info="Basic: higher/lower/same only. Extended: adds half/double evidence.", | |
) | |
reset_btn = gr.Button("π Reset Game", variant="secondary") | |
target_input = gr.Textbox( | |
label="Target Value (optional)", | |
placeholder="Leave empty for random target", | |
max_lines=1, | |
) | |
start_btn = gr.Button("π― Start New Game", variant="primary") | |
play_btn = gr.Button("π² Play Round", variant="secondary") | |
with gr.Column(scale=2): | |
status_output = gr.Textbox(label="Game Status", interactive=False) | |
belief_plot = gr.Plot(label="Belief Distribution") | |
game_log = gr.Markdown("Game log will appear here.") | |
# Event handlers | |
reset_btn.click( | |
interface.reset_game, | |
inputs=[dice_sides, max_rounds, evidence_type_dropdown], | |
outputs=[status_output, belief_plot, game_log], | |
) | |
start_btn.click( | |
interface.start_new_game, | |
inputs=[target_input], | |
outputs=[status_output, belief_plot, game_log], | |
) | |
play_btn.click( | |
interface.play_round, | |
outputs=[status_output, belief_plot, game_log], | |
) | |
# Initialize interface | |
demo.load( | |
interface._get_interface_state, | |
outputs=[status_output, belief_plot, game_log], | |
) | |
return demo | |
if __name__ == "__main__": | |
demo = create_interface() | |
demo.launch() | |