Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from squid_game_core import ( | |
| parse_tier_map, | |
| get_expected_value, | |
| compute_ev_win_lose_two_extremes, | |
| finite_squid_game_probabilities, | |
| dp_ev, | |
| infinite_squid_game_expected_counts_partial, | |
| get_expected_value_finite, | |
| compute_ev_win_lose_finite_tuple, | |
| ) | |
| from typing import List, Tuple | |
| def validate_distribution(dist_str: str) -> Tuple[bool, str, List[int]]: | |
| """Validate the distribution string and convert to list of integers""" | |
| try: | |
| dist = [int(x.strip()) for x in dist_str.split(',')] | |
| if any(x < 0 for x in dist): | |
| return False, "Distribution cannot contain negative numbers", [] | |
| if not dist: | |
| return False, "Distribution cannot be empty.", [] | |
| return True, "", dist | |
| except ValueError: | |
| return False, "Distribution must be comma-separated integers (e.g., '0,1,2')", [] | |
| def validate_folded_players(folded_str: str, num_players: int) -> Tuple[bool, str, List[int]]: | |
| """Validate the folded players string.""" | |
| try: | |
| folded = [int(x.strip()) for x in folded_str.split(',')] | |
| if len(folded) != num_players: | |
| return False, f"Folded players list must have {num_players} entries, but it has {len(folded)}.", [] | |
| if any(x not in [0, 1] for x in folded): | |
| return False, "Folded players list can only contain 0s and 1s.", [] | |
| return True, "", folded | |
| except ValueError: | |
| return False, "Folded players must be comma-separated integers (e.g., '0,0,1,0')", [] | |
| def validate_tier_map(tier_str: str) -> Tuple[bool, str]: | |
| """Validate the tier map string format""" | |
| try: | |
| lines = tier_str.strip().splitlines() | |
| for line in lines: | |
| if ':' not in line: | |
| return False, "Each line must contain a colon (e.g., '1-2:1.5')" | |
| range_part, mult_part = line.split(':') | |
| float(mult_part.strip()) # Check multiplier is a valid number | |
| if '-' in range_part: | |
| low_str, high_str = range_part.split('-') | |
| int(low_str), int(high_str) | |
| else: | |
| int(range_part.strip()) | |
| return True, "" | |
| except ValueError: | |
| return False, "Invalid format. Example: '1:1.0\\n2-4:2.0\\n5-6:3.0'" | |
| def solve_game(distribution: str, folded_players_str: str, total_squids: int, tier_map_str: str) -> str: | |
| """Main function to solve the game and return formatted results""" | |
| # Validate distribution | |
| valid_dist, error_msg, dist = validate_distribution(distribution) | |
| if not valid_dist: | |
| return error_msg | |
| # Validate folded players | |
| valid_folded, error_msg, folded = validate_folded_players(folded_players_str, len(dist)) | |
| if not valid_folded: | |
| return error_msg | |
| # Validate tier map | |
| valid_tier, error_msg = validate_tier_map(tier_map_str) | |
| if not valid_tier: | |
| return error_msg | |
| # Validate total squids | |
| try: | |
| X = int(total_squids) | |
| if X < 0: | |
| return "Total squids cannot be negative" | |
| if X < sum(dist): | |
| return "Total squids cannot be less than current distribution sum" | |
| except ValueError: | |
| return "Total squids must be an integer" | |
| # Parse tier map and convert to tuple for caching | |
| try: | |
| tier_map = parse_tier_map(tier_map_str) | |
| tier_map_tuple = tuple((a, b, c) for a, b, c in tier_map) | |
| dist_tuple = tuple(dist) | |
| folded_tuple = tuple(folded) | |
| # Calculate remaining squids to distribute | |
| remaining = X - sum(dist) | |
| # Get unforced expected values (full random assignment) | |
| get_expected_value.cache_clear() | |
| unforced_ev = get_expected_value(dist_tuple, remaining, tier_map_tuple) | |
| result = "Unforced Expected Values:\n" | |
| for i, ev in enumerate(unforced_ev): | |
| player_status = "(Folded)" if folded[i] == 1 else "" | |
| result += f"Player {i+1}: {ev:.3f} {player_status}\n" | |
| # Compute each player's forced win/lose EV extremes: | |
| win_lose_results = compute_ev_win_lose_two_extremes(dist_tuple, remaining, tier_map_tuple, folded_tuple) | |
| result += "\nForced Win/Lose Results (for non-folded players):\n" | |
| for i,r in enumerate(win_lose_results): | |
| if folded_tuple[r['player']] == 0: | |
| result += (f"Player {r['player']+1}: forcedWinEV = {r['forcedWinEV']:.3f}, " | |
| f"forcedLoseEV = {r['forcedLoseEV']:.3f}, Diff = {r['difference']:.3f}\n") | |
| # Add a human-friendly interpretation of the tier map | |
| result += "\nTier Map Interpretation:\n" | |
| for low, high, mult in tier_map: | |
| if low == high: | |
| result += f"• {low} squid(s): multiplier = {mult:.1f}\n" | |
| else: | |
| result += f"• {low}-{high} squids: multiplier = {mult:.1f}\n" | |
| return result | |
| except Exception as e: | |
| return f"Error occurred: {str(e)}" | |
| def solve_finite_game(distribution: str, folded_players_str: str, total_squids: int) -> str: | |
| """Calculate the Expected Value (EV) for each player in the finite variant using tuple-based state.""" | |
| # Validate distribution | |
| valid_dist, error_msg, dist = validate_distribution(distribution) | |
| if not valid_dist: | |
| return error_msg | |
| # Validate folded players | |
| valid_folded, error_msg, folded = validate_folded_players(folded_players_str, len(dist)) | |
| if not valid_folded: | |
| return error_msg | |
| # Validate finite variant rules | |
| if any(x > 1 for x in dist): | |
| return "Error: In the finite variant, players can only have 0 or 1 squid." | |
| # Validate total squids | |
| try: | |
| X = int(total_squids) | |
| current_squids = sum(dist) | |
| if X < 0: | |
| return "Total squids cannot be negative." | |
| if X > len(dist): | |
| return f"Error: Total squids ({X}) cannot exceed the number of players ({len(dist)})." | |
| if X < current_squids: | |
| return f"Total squids ({X}) cannot be less than current distribution sum ({current_squids})." | |
| except ValueError: | |
| return "Total squids must be an integer." | |
| try: | |
| get_expected_value_finite.cache_clear() | |
| remaining = X - sum(dist) | |
| dist_tuple = tuple(dist) | |
| folded_tuple = tuple(folded) | |
| from squid_game_core import _is_terminal_finite, _compute_final_payout_finite_tuple | |
| if _is_terminal_finite(dist_tuple, remaining): | |
| final_payoffs = _compute_final_payout_finite_tuple(dist_tuple) | |
| result = "Finite Squid Game Final Payouts:\n(Game has ended or no squids left)\n\n" | |
| for i, payoff in enumerate(final_payoffs): | |
| player_status = "(Folded)" if folded[i] == 1 else "" | |
| result += f"Player {i+1}: {payoff:.4f} {player_status}\n" | |
| return result | |
| base_ev, win_lose_results = compute_ev_win_lose_finite_tuple(dist_tuple, remaining, folded_tuple) | |
| result = "Finite Squid Game Expected Value (EV):\n" | |
| result += "(Payout Rule: Losers pay for all winners' squids)\n\n" | |
| result += "Baseline EV (fully random):\n" | |
| for i, ev in enumerate(base_ev): | |
| player_status = "(Folded)" if folded[i] == 1 else "" | |
| result += f"Player {i+1}: {ev:.4f} {player_status}\n" | |
| if win_lose_results: | |
| result += "\nForced Win/Lose EV (for players without a squid who haven't folded):\n" | |
| for i,r in enumerate(win_lose_results): | |
| if folded_tuple[r['player']] == 0: | |
| result += (f"Player {r['player']+1}: forcedWinEV = {r['forcedWinEV']:.4f}, " | |
| f"forcedLoseEV = {r['forcedLoseEV']:.4f}, Diff = {r['difference']:.4f}\n") | |
| return result | |
| except Exception as e: | |
| return f"Error occurred: {str(e)}" | |
| def solve_infinite_game(distribution: str) -> str: | |
| """Calculate the expected final squid count for each player in the infinite variant""" | |
| # Validate distribution | |
| valid_dist, error_msg, dist = validate_distribution(distribution) | |
| if not valid_dist: | |
| return error_msg | |
| try: | |
| # Calculate expected final counts | |
| expected_counts = infinite_squid_game_expected_counts_partial(dist) | |
| result = "Infinite Squid Game Expected Final Counts:\n" | |
| result += "(Expected number of squids each player will have at the end)\n\n" | |
| for i, count in enumerate(expected_counts): | |
| if count == float('inf'): | |
| result += f"Player {i+1}: ∞ (infinite)\n" | |
| else: | |
| result += f"Player {i+1}: {count:.4f}\n" | |
| # Add explanation based on zero count | |
| zero_count = sum(1 for x in dist if x == 0) | |
| if zero_count == 1: | |
| result += "\nExplanation: Game ends immediately as there is exactly one player with 0 squids." | |
| elif zero_count == 0: | |
| result += "\nExplanation: Game never ends (infinite loop) as there are no players with 0 squids." | |
| else: | |
| H_z = sum(1.0 / k for k in range(1, zero_count+1)) | |
| increment = (H_z - 1.0) | |
| result += f"\nExplanation: Each player is expected to receive {increment:.4f} additional squids before the game ends." | |
| return result | |
| except Exception as e: | |
| return f"Error occurred: {str(e)}" | |
| # Default value for tier map used in interface | |
| DEFAULT_TIER_MAP = """0-0:0 | |
| 1-2:1 | |
| 3-4:1 | |
| 5-6:1 | |
| 7-7:1 | |
| 8-8:1 | |
| 9-9:1 | |
| 10-100:1""" | |
| with gr.Blocks(title="Squid Game Calculator") as iface: | |
| gr.Markdown(""" | |
| # Squid Game Expected Value Calculator | |
| Calculate the expected payoff for each player in the Squid Game. | |
| **Classic Variant Rules:** | |
| 1. Players take turns collecting squids randomly. | |
| 2. The game ends when either: | |
| - Exactly one player has 0 squids, OR | |
| - There are no squids left to distribute. | |
| **Finite Variant Rules:** | |
| 1. Players take turns collecting squids randomly. | |
| 2. Once a player gets a squid, they can't get another one. | |
| 3. The game ends when all squids are distributed or only one player remains without a squid. | |
| **Infinite Variant Rules:** | |
| 1. Players take turns collecting squids randomly (unlimited supply). | |
| 2. Players accumulate squids over time. | |
| 3. The game ends only when exactly one player has 0 squids. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| distribution_input = gr.Textbox( | |
| label="Players' Current Squids", | |
| placeholder="0,0", | |
| value="0,0", | |
| info="""Enter each player's current squids, separated by commas. | |
| Example: '1,0,1,2,0' represents 5 players.""" | |
| ) | |
| folded_players_input = gr.Textbox( | |
| label="Folded Players (0 = Playing, 1 = Folded)", | |
| placeholder="0,0", | |
| value="0,0", | |
| info="""Enter 0 for playing, 1 for folded. Must match the number of players. | |
| Example: '0,0,1,0,0' means Player 3 has folded and won't receive any more squids.""" | |
| ) | |
| total_squids_input = gr.Number( | |
| label="Total Squids in Game (Classic & Finite Variants Only)", | |
| value=9, | |
| minimum=0, | |
| step=1, | |
| precision=0, | |
| info="The total number of squids to be distributed (must be ≥ sum of current squids for classic variant)" | |
| ) | |
| tier_map_input = gr.Textbox( | |
| label="Squid Value Tiers (Classic Variant Only)", | |
| placeholder=DEFAULT_TIER_MAP, | |
| value=DEFAULT_TIER_MAP, | |
| lines=8, | |
| info="""Define the value tiers for squids. | |
| Format: range:multiplier (one per line)""" | |
| ) | |
| with gr.Column(): | |
| results_output = gr.Textbox(label="Results", lines=20) | |
| with gr.Row(): | |
| classic_btn = gr.Button("Calculate Classic Variant", variant="primary") | |
| finite_btn = gr.Button("Calculate Finite Variant", variant="stop") | |
| infinite_btn = gr.Button("Calculate Infinite Variant", variant="secondary") | |
| gr.Examples( | |
| examples=[ | |
| ["0,0", "0,0", 9, DEFAULT_TIER_MAP], | |
| ["1,0,1", "0,0,0", 12, DEFAULT_TIER_MAP], | |
| ["2,0,2,0", "0,1,0,0", 14, DEFAULT_TIER_MAP], | |
| ], | |
| inputs=[distribution_input, folded_players_input, total_squids_input, tier_map_input], | |
| ) | |
| classic_btn.click( | |
| fn=solve_game, | |
| inputs=[distribution_input, folded_players_input, total_squids_input, tier_map_input], | |
| outputs=results_output | |
| ) | |
| finite_btn.click( | |
| fn=solve_finite_game, | |
| inputs=[distribution_input, folded_players_input, total_squids_input], | |
| outputs=results_output | |
| ) | |
| infinite_btn.click( | |
| fn=solve_infinite_game, | |
| inputs=[distribution_input], | |
| outputs=results_output | |
| ) | |
| if __name__ == "__main__": | |
| iface.launch() |