bayesian_game / ui /gradio_interface.py
thompsonson's picture
feat: add entropy display to game status messages
1b86466
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()